You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by as...@apache.org on 2007/11/14 00:44:17 UTC

svn commit: r594720 [7/9] - in /incubator/buildr/trunk: ./ bin/ doc/ doc/css/ doc/images/ doc/pages/ lib/ lib/buildr/ lib/buildr/jetty/ lib/core/ lib/java/ lib/tasks/ test/

Added: incubator/buildr/trunk/lib/tasks/zip.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/tasks/zip.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/tasks/zip.rb (added)
+++ incubator/buildr/trunk/lib/tasks/zip.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,671 @@
+require "zip/zip"
+require "zip/zipfilesystem"
+
+
+module Buildr
+
+  # Base class for ZipTask, TarTask and other archives.
+  class ArchiveTask < Rake::FileTask
+
+    # Which files go where. All the rules for including, excluding and merging files
+    # are handled by this object.
+    class Path #:nodoc:
+
+      # Returns the archive from this path.
+      attr_reader :root
+      
+      def initialize(root, path)
+        @root = root
+        @path = path.blank? ? path : "#{path}/"
+        @includes = FileList[]
+        @excludes = []
+        # Expand source files added to this path.
+        expand_src = proc { @includes.map{ |file| file.to_s }.uniq }
+        @sources = [ expand_src ]
+        # Add files and directories added to this path.
+        @actions = [] << proc do |file_map|
+          expand_src.call.each do |path|
+            unless excluded?(path)
+              if File.directory?(path)
+                in_directory path do |file, rel_path|
+                  dest = "#{@path}#{rel_path}"
+                  puts "Adding #{dest}" if Rake.application.options.trace
+                  file_map[dest] = file
+                end
+              else
+                puts "Adding #{@path}#{File.basename(path)}" if Rake.application.options.trace
+                file_map["#{@path}#{File.basename(path)}"] = path
+              end
+            end
+          end
+        end
+      end
+
+      # :call-seq:
+      #   include(*files) => self
+      #   include(*files, :path=>path) => self
+      #   include(file, :as=>name) => self
+      #   include(:from=>path) => self
+      #   include(*files, :merge=>true) => self
+      def include(*args)
+        options = args.pop if Hash === args.last
+        files = args.flatten
+
+        if options.nil? || options.empty?
+          @includes.include *files.flatten
+        elsif options[:path]
+          sans_path = options.reject { |k,v| k == :path }
+          path(options[:path]).include *files + [sans_path]
+        elsif options[:as]
+          raise "You can only use the :as option in combination with the :path option" unless options.size == 1
+          raise "You can only use one file with the :as option" unless files.size == 1
+          include_as files.first.to_s, options[:as]
+        elsif options[:from]
+          raise "You can only use the :from option in combination with the :path option" unless options.size == 1
+          raise "You canont use the :from option with file names" unless files.empty?
+          [options[:from]].flatten.each { |path| include_as path.to_s, "." }
+        elsif options[:merge]
+          raise "You can only use the :merge option in combination with the :path option" unless options.size == 1
+          files.each { |file| merge file }
+        else
+          raise "Unrecognized option #{options.keys.join(", ")}"
+        end
+        self
+      end
+      alias :add :include
+
+      # :call-seq:
+      #   exclude(*files) => self
+      def exclude(*files)
+        files = files.flatten.map(&:to_s) 
+        @excludes |= files
+        @excludes |= files.reject { |f| f =~ /\*$/ }.map { |f| "#{f}/*" }
+        self
+      end
+
+      # :call-seq:
+      #   merge(*files) => Merge
+      #   merge(*files, :path=>name) => Merge
+      def merge(*args)
+        options = args.pop if Hash === args.last
+        files = args.flatten
+
+        if options.nil? || options.empty?
+          files.collect do |file|
+            @sources << proc { file.to_s }
+            expander = ZipExpander.new(file)
+            @actions << proc { |file_map| expander.expand(file_map, @path) }
+            expander
+          end.first
+        elsif options[:path]
+          sans_path = options.reject { |k,v| k == :path }
+          path(options[:path]).merge *files + [sans_path]
+          self
+        else
+          raise "Unrecognized option #{options.keys.join(", ")}"
+        end
+      end
+
+      # Returns a Path relative to this one.
+      def path(path)
+        return self if path.blank?
+        return root.path(path[1..-1]) if path[0] == ?/
+        root.path("#{@path}#{path}")
+      end
+
+      # Returns all the source files.
+      def sources() #:nodoc:
+        @sources.map{ |source| source.call }.flatten
+      end
+
+      def add_files(file_map) #:nodoc:
+        @actions.each { |action| action.call(file_map) }
+      end
+
+      def to_s()
+        @path
+      end
+
+    protected
+
+      def include_as(source, as)
+        @sources << proc { source }
+        @actions << proc do |file_map|
+          file = source.to_s
+          unless excluded?(file)
+            if File.directory?(file)
+              in_directory file do |file, rel_path|
+                path = rel_path.split("/")[1..-1]
+                path.unshift as unless as == "."
+                dest = "#{@path}#{path.join('/')}"
+                puts "Adding #{dest}" if Rake.application.options.trace
+                file_map[dest] = file
+              end
+            else
+              puts "Adding #{@path}#{as}" if Rake.application.options.trace
+              file_map["#{@path}#{as}"] = file
+            end
+          end
+        end
+      end
+
+      def in_directory(dir)
+        prefix = Regexp.new("^" + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
+        FileList.recursive(dir).reject { |file| excluded?(file) }.
+          each { |file| yield file, file.sub(prefix, "") }
+      end
+
+      def excluded?(file)
+        @excludes.any? { |exclude| File.fnmatch(exclude, file) }
+      end
+
+    end
+
+
+    # Extend one Zip file into another.
+    class ZipExpander #:nodoc:
+
+      def initialize(zip_file)
+        @zip_file = zip_file.to_s
+        @includes = []
+        @excludes = []
+      end
+
+      def include(*files)
+        @includes |= files
+        self
+      end
+
+      def exclude(*files)
+        @excludes |= files
+        self
+      end
+
+      def expand(file_map, path)
+        @includes = ["*"] if @includes.empty?
+        Zip::ZipFile.open(@zip_file) do |source|
+          source.entries.reject { |entry| entry.directory? }.each do |entry|
+            if @includes.any? { |pattern| File.fnmatch(pattern, entry.name) } &&
+               !@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) }
+              dest = "#{path}#{entry.name}"
+              puts "Adding #{dest}" if Rake.application.options.trace
+              file_map[dest] = lambda { |output| output.write source.read(entry) }
+            end
+          end
+        end
+      end
+
+    end
+
+
+    def initialize(*args) #:nodoc:
+      super
+      @paths = { ""=>Path.new(self, "") }
+      @prepares = []
+
+      # Make sure we're the last enhancements, so other enhancements can add content.
+      enhance do
+        @file_map = {}
+        enhance do
+          send "create" if respond_to?(:create)
+          # We're here because the archive file does not exist, or one of the files is newer than the archive contents;
+          # we need to make sure the archive doesn't exist (e.g. opening an existing Zip will add instead of create).
+          # We also want to protect against partial updates.
+          rm name, :verbose=>false rescue nil
+          mkpath File.dirname(name), :verbose=>false
+          begin
+            @paths.each do |name, object|
+              @file_map[name] = nil unless name.blank?
+              object.add_files(@file_map)
+            end
+            create_from @file_map
+          rescue
+            rm name, :verbose=>false rescue nil
+            raise
+          end
+        end
+      end
+    end
+
+    # :call-seq:
+    #   include(*files) => self
+    #   include(*files, :path=>path) => self
+    #   include(file, :as=>name) => self
+    #   include(:from=>path) => self
+    #   include(*files, :merge=>true) => self
+    #
+    # Include files in this archive, or when called on a path, within that path. Returns self.
+    #
+    # The first form accepts a list of files, directories and glob patterns and adds them to the archive.
+    # For example, to include the file foo, directory bar (including all files in there) and all files under baz:
+    #   zip(..).include("foo", "bar", "baz/*")
+    #
+    # The second form is similar but adds files/directories under the specified path. For example,
+    # to add foo as bar/foo:
+    #   zip(..).include("foo", :path=>"bar")
+    # The :path option is the same as using the path method:
+    #   zip(..).path("bar").include("foo")
+    # All other options can be used in combination with the :path option.
+    #
+    # The third form adds a file or directory under a different name. For example, to add the file foo under the
+    # name bar:
+    #   zip(..).include("foo", :as=>"bar")
+    #
+    # The fourth form adds the contents of a directory using the directory as a prerequisite:
+    #   zip(..).include(:from=>"foo")
+    # Unlike <code>include("foo")</code> it includes the contents of the directory, not the directory itself.
+    # Unlike <code>include("foo/*")</code>, it uses the directory timestamp for dependency management.
+    #
+    # The fifth form includes the contents of another archive by expanding it into this archive. For example:
+    #   zip(..).include("foo.zip", :merge=>true).include("bar.zip")
+    # You can also use the method #merge.
+    def include(*files)
+      @paths[""].include *files
+      self
+    end 
+    alias :add :include
+   
+    # :call-seq:
+    #   exclude(*files) => self
+    # 
+    # Excludes files and returns self. Can be used in combination with include to prevent some files from being included.
+    def exclude(*files)
+      @paths[""].exclude *files
+      self
+    end 
+
+    # :call-seq:
+    #   merge(*files) => Merge
+    #   merge(*files, :path=>name) => Merge
+    #
+    # Merges another archive into this one by including the individual files from the merged archive.
+    #
+    # Returns an object that supports two methods: include and exclude. You can use these methods to merge
+    # only specific files. For example:
+    #   zip(..).merge("src.zip").include("module1/*")
+    def merge(*files)
+      @paths[""].merge *files
+    end 
+
+    # :call-seq:
+    #   path(name) => Path
+    #
+    # Returns a path object. Use the path object to include files under a path, for example, to include
+    # the file "foo" as "bar/foo":
+    #   zip(..).path("bar").include("foo")
+    #
+    # Returns a Path object. The Path object implements all the same methods, like include, exclude, merge
+    # and so forth. It also implements path and root, so that:
+    #   path("foo").path("bar") == path("foo/bar")
+    #   path("foo").root == root
+    def path(name)
+      return @paths[""] if name.blank?
+      normalized = name.split("/").inject([]) do |path, part|
+        case part
+        when ".", nil, ""
+          path
+        when ".."
+          path[0...-1]
+        else
+          path << part
+        end
+      end.join("/")
+      @paths[normalized] ||= Path.new(self, normalized)
+    end
+
+    # :call-seq:
+    #   root() => ArchiveTask
+    #
+    # Call this on an archive to return itself, and on a path to return the archive.
+    def root()
+      self
+    end
+
+    # :call-seq:
+    #   with(options) => self
+    #
+    # Passes options to the task and returns self. Some tasks support additional options, for example,
+    # the WarTask supports options like :manifest, :libs and :classes.
+    #
+    # For example:
+    #   package(:jar).with(:manifest=>"MANIFEST_MF")
+    def with(options)
+      options.each do |key, value|
+        begin
+          send "#{key}=", value
+        rescue NameError
+          if respond_to?(:[]=) # Backward compatible with Buildr 1.1.
+            warn_deprecated "The []= method is deprecated, please use attribute accessors instead."
+            self[key] = value
+          else
+            raise ArgumentError, "This task does not support the option #{key}."
+          end
+        end
+      end
+      self
+    end
+
+    def invoke_prerequisites() #:nodoc:
+      @prepares.each { |prepare| prepare.call(self) }
+      @prepares.clear
+      @prerequisites |= @paths.collect { |name, path| path.sources }.flatten
+      super
+    end
+    
+    def needed?() #:nodoc:
+      return true unless File.exist?(name)
+      # You can do something like:
+      #   include("foo", :path=>"foo").exclude("foo/bar", path=>"foo").
+      #     include("foo/bar", :path=>"foo/bar")
+      # This will play havoc if we handled all the prerequisites together
+      # under the task, so instead we handle them individually for each path.
+      #
+      # We need to check that any file we include is not newer than the
+      # contents of the Zip. The file itself but also the directory it's
+      # coming from, since some tasks touch the directory, e.g. when the
+      # content of target/classes is included into a WAR.
+      most_recent = @paths.collect { |name, path| path.sources }.flatten.
+        each { |src| File.directory?(src) ? FileList.recursive(src) | [src] : src }.flatten.
+        select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
+      File.stat(name).mtime < (most_recent || Rake::EARLY) || super
+    end
+
+  protected
+
+    # Adds a prepare block. These blocks are called early on for adding more content to
+    # the archive, before invoking prerequsities. Anything you add here will be invoked
+    # as a prerequisite and used to determine whether or not to generate this archive.
+    # In contrast, enhance blocks are evaluated after it was decided to create this archive.
+    def prepare(&block)
+      @prepares << block
+    end
+
+    def []=(key, value) #:nodoc:
+      raise ArgumentError, "This task does not support the option #{key}."
+    end
+
+  end
+
+  # The ZipTask creates a new Zip file. You can include any number of files and and directories,
+  # use exclusion patterns, and include files into specific directories.
+  #
+  # For example:
+  #   zip("test.zip").tap do |task|
+  #     task.include "srcs"
+  #     task.include "README", "LICENSE"
+  #   end
+  #
+  # See Buildr#zip and ArchiveTask.
+  class ZipTask < ArchiveTask
+
+  private
+
+    def create_from(file_map)
+      Zip::ZipOutputStream.open name do |zip|
+        seen = {}
+        mkpath = lambda do |dir|
+          unless dir == "." || seen[dir]
+            mkpath.call File.dirname(dir)
+            zip.put_next_entry dir + '/'
+            seen[dir] = true
+          end
+        end
+      
+        file_map.each do |path, content|
+          mkpath.call File.dirname(path)
+          if content.respond_to?(:call)
+            zip.put_next_entry path
+            content.call zip
+          elsif content.nil? || File.directory?(content.to_s)
+            mkpath.call path
+          else
+            zip.put_next_entry path
+            File.open content.to_s, "rb" do |is|
+              while data = is.read(4096)
+                zip << data
+              end
+            end
+          end
+        end
+      end
+    end
+
+  end
+
+
+  # :call-seq:
+  #    zip(file) => ZipTask
+  #
+  # The ZipTask creates a new Zip file. You can include any number of files and
+  # and directories, use exclusion patterns, and include files into specific
+  # directories.
+  #
+  # For example:
+  #   zip("test.zip").tap do |task|
+  #     task.include "srcs"
+  #     task.include "README", "LICENSE"
+  #   end
+  def zip(file)
+    ZipTask.define_task(file)
+  end
+
+
+  # An object for unzipping a file into a target directory. You can tell it to include
+  # or exclude only specific files and directories, and also to map files from particular
+  # paths inside the zip file into the target directory. Once ready, call #extract.
+  #
+  # Usually it is more convenient to create a file task for extracting the zip file
+  # (see #unzip) and pass this object as a prerequisite to other tasks.
+  #
+  # See Buildr#unzip.
+  class Unzip
+
+    # The zip file to extract.
+    attr_accessor :zip_file
+    # The target directory to extract to.
+    attr_accessor :target
+
+    # Initialize with hash argument of the form target=>zip_file.
+    def initialize(args)
+      @target, @zip_file = Rake.application.resolve_args(args)
+      @paths = {}
+    end
+
+    # :call-seq:
+    #   extract()
+    #
+    # Extract the zip file into the target directory.
+    #
+    # You can call this method directly. However, if you are using the #unzip method,
+    # it creates a file task for the target directory: use that task instead as a
+    # prerequisite. For example:
+    #   build unzip(dir=>zip_file)
+    # Or:
+    #   unzip(dir=>zip_file).target.invoke
+    def extract()
+      # If no paths specified, then no include/exclude patterns
+      # specified. Nothing will happen unless we include all files.
+      if @paths.empty?
+        @paths[nil] = FromPath.new(self, nil)
+        @paths[nil].include "*"
+      end
+
+      # Otherwise, empty unzip creates target as a file when touching.
+      mkpath target.to_s, :verbose=>false
+      Zip::ZipFile.open(zip_file.to_s) do |zip|
+        entries = zip.collect
+        @paths.each do |path, patterns|
+          patterns.map(entries).each do |dest, entry|
+            next if entry.directory?
+            dest = File.expand_path(dest, target.to_s)
+            puts "Extracting #{dest}" if Rake.application.options.trace
+            mkpath File.dirname(dest), :verbose=>false rescue nil
+            entry.extract(dest) { true }
+          end
+        end
+      end
+      # Let other tasks know we updated the target directory.
+      touch target.to_s, :verbose=>false
+    end
+
+    # :call-seq:
+    #   include(*files) => self
+    #   include(*files, :path=>name) => self
+    #
+    # Include all files that match the patterns and returns self.
+    #
+    # Use include if you only want to unzip some of the files, by specifying
+    # them instead of using exclusion. You can use #include in combination
+    # with #exclude.
+    def include(*files)
+      if Hash === files.last
+        from_path(files.pop[:path]).include *files
+      else
+        from_path(nil).include *files
+      end
+      self
+    end
+    alias :add :include
+
+    # :call-seq:
+    #   exclude(*files) => self
+    #
+    # Exclude all files that match the patterns and return self.
+    #
+    # Use exclude to unzip all files except those that match the pattern.
+    # You can use #exclude in combination with #include.
+    def exclude(*files)
+      if Hash === files.last
+        from_path(files.pop[:path]).exclude *files
+      else
+        from_path(nil).exclude *files
+      end
+      self
+    end
+
+    # :call-seq:
+    #   from_path(name) => Path
+    #
+    # Allows you to unzip from a path. Returns an object you can use to
+    # specify which files to include/exclude relative to that path.
+    # Expands the file relative to that path.
+    #
+    # For example:
+    #   unzip(Dir.pwd=>"test.jar").from_path("etc").include("LICENSE")
+    # will unzip etc/LICENSE into ./LICENSE.
+    #
+    # This is different from:
+    #  unzip(Dir.pwd=>"test.jar").include("etc/LICENSE")
+    # which unzips etc/LICENSE into ./etc/LICENSE.
+    def from_path(name)
+      @paths[name] ||= FromPath.new(self, name)
+    end
+    alias :path :from_path
+
+    # :call-seq:
+    #   root() => Unzip
+    #
+    # Returns the root path, essentially the Unzip object itself. In case you are wondering
+    # down paths and want to go back.
+    def root()
+      self
+    end
+
+    # Returns the path to the target directory.
+    def to_s()
+      target.to_s
+    end
+
+    class FromPath #:nodoc:
+
+      def initialize(unzip, path)
+        @unzip = unzip
+        if path
+          @path = path[-1] == ?/ ? path : path + "/"
+        else
+          @path = ""
+        end
+      end
+
+      # See UnzipTask#include
+      def include(*files) #:doc:
+        @include ||= []
+        @include |= files
+        self
+      end
+
+      # See UnzipTask#exclude
+      def exclude(*files) #:doc:
+        @exclude ||= []
+        @exclude |= files
+        self
+      end
+
+      def map(entries)
+        includes = @include || ["*"]
+        excludes = @exclude || []
+        entries.inject({}) do |map, entry|
+          short = entry.name.sub(@path, "")
+          if includes.any? { |pat| File.fnmatch(pat, short) } &&
+             !excludes.any? { |pat| File.fnmatch(pat, short) }
+            map[short] = entry
+          end
+          map
+        end
+      end
+
+      # Documented in Unzip.
+      def root()
+        @unzip
+      end
+
+      # The target directory to extract to.
+      def target()
+        @unzip.target
+      end
+
+    end
+
+  end
+
+  # :call-seq:
+  #    unzip(to_dir=>zip_file) => Zip
+  #
+  # Creates a task that will unzip a file into the target directory. The task name
+  # is the target directory, the prerequisite is the file to unzip.
+  #
+  # This method creates a file task to expand the zip file. It returns an Unzip object
+  # that specifies how the file will be extracted. You can include or exclude specific
+  # files from within the zip, and map to different paths.
+  #
+  # The Unzip object's to_s method return the path to the target directory, so you can
+  # use it as a prerequisite. By keeping the Unzip object separate from the file task,
+  # you overlay additional work on top of the file task.
+  #
+  # For example:
+  #   unzip("all"=>"test.zip")
+  #   unzip("src"=>"test.zip").include("README", "LICENSE") 
+  #   unzip("libs"=>"test.zip").from_path("libs")
+  def unzip(args)
+    target, zip_file = Rake.application.resolve_args(args)
+    task = file(File.expand_path(target.to_s)=>zip_file)
+    Unzip.new(task=>zip_file).tap do |setup|
+      task.enhance { setup.extract }
+    end
+  end
+
+end
+
+
+module Zip #:nodoc:
+
+  class ZipCentralDirectory #:nodoc:
+    # Patch to add entries in alphabetical order.
+    def write_to_stream(io)
+      offset = io.tell
+      @entrySet.sort { |a,b| a.name <=> b.name }.each { |entry| entry.write_c_dir_entry(io) }
+      write_e_o_c_d(io, offset)
+    end
+  end
+
+end 

Added: incubator/buildr/trunk/test/archive.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/test/archive.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/test/archive.rb (added)
+++ incubator/buildr/trunk/test/archive.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,427 @@
+require File.join(File.dirname(__FILE__), 'sandbox')
+
+
+describe "ArchiveTask", :shared=>true do
+  before do
+    @dir = File.expand_path("test")
+    @files = %w{Test1.txt Text2.html}.map { |file| File.expand_path(file, @dir) }.
+      each { |file| write file, content_for(file) }
+  end
+
+  # Not too smart, we just create some content based on file name to make sure you read what you write.
+  def content_for(file)
+    "Content for #{File.basename(file)}"
+  end
+
+  # Create an archive not using the archive task, this way we do have a file in existence, but we don't
+  # have an already invoked task.  Yield an archive task to the block which can use it to include files,
+  # set options, etc.
+  def create_without_task()
+    archive(@archive + ".tmp").tap do |task|
+      yield task if block_given?
+      task.invoke
+      mv task.name, @archive
+    end
+  end
+
+  def create_for_merge()
+    zip(@archive + ".src").include(@files).tap do |task|
+      task.invoke
+      yield task
+    end
+  end
+
+  it "should point to archive file" do
+    archive(@archive).name.should eql(@archive)
+  end
+
+  it "should create file" do
+    lambda { archive(@archive).invoke }.should change { File.exist?(@archive) }.to(true)
+  end
+
+  it "should create empty archive if no files included" do
+    archive(@archive).invoke
+    inspect_archive { |archive| archive.should be_empty }
+  end
+
+  it "should archive all included files" do
+    archive(@archive).include(@files).invoke
+    inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } }
+    inspect_archive.size.should eql(@files.size)
+  end
+
+  it "should include entry for directory" do
+    archive(@archive).include(@dir).invoke
+    inspect_archive { |archive| @files.each { |f| archive["test/" + File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should not archive any excluded files" do
+    archive(@archive).include(@files).exclude(@files.last).invoke
+    inspect_archive do |archive|
+      archive.keys.should include(File.basename(@files.first))
+      archive.keys.should_not include(File.basename(@files.last))
+    end
+  end
+
+  it "should not archive any excluded files in included directories" do
+    archive(@archive).include(@dir).exclude(@files.last).invoke
+    inspect_archive do |archive|
+      archive.keys.should include("test/" + File.basename(@files.first))
+      archive.keys.should_not include("test/" + File.basename(@files.last))
+    end
+  end
+
+  it "should not archive any excluded files when using :from/:as" do
+    archive(@archive).include(:from=>@dir).exclude(@files.last).invoke
+    inspect_archive do |archive|
+      archive.keys.should include(File.basename(@files.first))
+      archive.keys.should_not include(File.basename(@files.last))
+    end
+  end
+
+  it "should exclude entire directory and all its children" do
+    mkpath "#{@dir}/sub"
+    write "#{@dir}/sub/test"
+    archive(@archive).include(@dir).exclude("#{@dir}/sub").invoke
+    inspect_archive do |archive|
+      archive.keys.select { |file| file =~ /sub/ }.should be_empty 
+    end
+  end
+
+  it "should archive files into specified path" do
+    archive(@archive).include(@files, :path=>"code").invoke
+    inspect_archive { |archive| @files.each { |f| archive["code/" + File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should include entry for directory" do
+    archive(@archive).include(@dir).invoke
+    inspect_archive { |archive| @files.each { |f| archive["test/" + File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should archive files into specified path" do
+    archive(@archive).include(@files, :path=>"code").invoke
+    inspect_archive { |archive| @files.each { |f| archive["code/" + File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should archive directories into specified path" do
+    archive(@archive).include(@dir, :path=>"code").invoke
+    inspect_archive { |archive| @files.each { |f| archive["code/test/" + File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should understand . in path" do
+    archive(@archive).path(".").should == archive(@archive).path("")
+    archive(@archive).path("foo").path(".").should == archive(@archive).path("foo")
+  end
+
+  it "should understand .. in path" do
+    archive(@archive).path("..").should == archive(@archive).path("")
+    archive(@archive).path("foo").path("..").should == archive(@archive).path("")
+    archive(@archive).path("foo/bar").path("..").should == archive(@archive).path("foo")
+  end
+
+  it "should understand leading / in path" do
+    archive(@archive).path("/").should == archive(@archive).path("")
+    archive(@archive).path("foo/bar").path("/").should == archive(@archive).path("")
+  end
+
+  it "should archive file into specified name" do
+    archive(@archive).include(@files.first, :as=>"test/sample").invoke
+    inspect_archive { |archive| @files.each { |f| archive["test/sample"].should eql(content_for(@files.first)) } }
+  end
+
+  it "should archive file into specified name/path" do
+    archive(@archive).include(@files.first, :as=>"test/sample", :path=>"path").invoke
+    inspect_archive { |archive| @files.each { |f| archive["path/test/sample"].should eql(content_for(@files.first)) } }
+  end
+
+  it "should archive files starting with dot" do
+    write "test/.config", "# configuration"
+    archive(@archive).include("test").invoke
+    inspect_archive { |archive| @files.each { |f| archive["test/.config"].should eql("# configuration") } }
+  end
+
+  it "should archive directory into specified name" do
+    archive(@archive).include(@dir, :as=>"code").invoke
+    inspect_archive { |archive| @files.each { |f| archive["code/" + File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should archive directory into specified name/path" do
+    archive(@archive).include(@dir, :as=>"code", :path=>"path").invoke
+    inspect_archive { |archive| @files.each { |f| archive["path/code/" + File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should archive directory contents" do
+    archive(@archive).include(@dir, :as=>".").invoke
+    inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should archive directory contents into specified path" do
+    archive(@archive).include(@dir, :as=>".", :path=>"path").invoke
+    inspect_archive { |archive| @files.each { |f| archive["path/" + File.basename(f)].should eql(content_for(f)) } }
+  end
+
+  it "should not allow two files with the :as argument" do
+    lambda { archive(@archive).include(@files.first, @files.last, :as=>"test/sample") }.should raise_error(RuntimeError, /one file/)
+  end
+
+  it "should expand another archive file" do
+    create_for_merge do |src|
+      archive(@archive).merge(src)
+      archive(@archive).invoke
+      inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } }
+    end
+  end
+
+  it "should expand another archive file with include pattern" do
+    create_for_merge do |src|
+      archive(@archive).merge(src).include(File.basename(@files.first))
+      archive(@archive).invoke
+      inspect_archive { |archive| archive[File.basename(@files.first)].should eql(content_for(@files.first)) }
+    end
+  end
+
+  it "should expand another archive file with exclude pattern" do
+    create_for_merge do |src|
+      archive(@archive).merge(src).exclude(File.basename(@files.first))
+      archive(@archive).invoke
+      inspect_archive { |archive| @files[1..-1].each { |f| archive[File.basename(f)].should eql(content_for(f)) } }
+    end
+  end
+
+  it "should expand another archive file into path" do
+    create_for_merge do |src|
+      archive(@archive).path("test").merge(src)
+      archive(@archive).invoke
+      inspect_archive { |archive| @files.each { |f| archive["test/" + File.basename(f)].should eql(content_for(f)) } }
+    end
+  end
+
+  it "should expand another archive file into path with merge option" do
+    create_for_merge do |src|
+      archive(@archive).include(src, :merge=>true)
+      archive(@archive).invoke
+      inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } }
+    end
+  end
+
+  it "should update if one of the files is recent" do
+    create_without_task { |archive| archive.include(@files) }
+    # Touch archive file to some point in the past. This effectively makes
+    # all included files newer.
+    File.utime Time.now - 100, Time.now - 100, @archive
+    archive(@archive).include(@files).invoke
+    File.stat(@archive).mtime.should be_close(Time.now, 10) 
+  end
+
+  it "should do nothing if all files are uptodate" do
+    create_without_task { |archive| archive.include(@files) }
+    # By touching all files in the past, there's nothing new to update.
+    (@files + [@archive]).each { |f| File.utime Time.now - 100, Time.now - 100, f }
+    archive(@archive).include(@files).invoke
+    File.stat(@archive).mtime.should be_close(Time.now - 100, 10) 
+  end
+
+  it "should update if one of the files is recent" do
+    create_without_task { |archive| archive.include(@files) }
+    # Change files, we expect to see new content.
+    write @files.first, "/* Refreshed */"
+    File.utime(Time.now - 100, Time.now - 100, @archive) # Touch archive file to some point in the past.
+    archive(@archive).include(@files).invoke
+    inspect_archive { |archive| archive[File.basename(@files.first)].should eql("/* Refreshed */") }
+  end
+
+  it "should create new archive when updating" do
+    create_without_task { |archive| archive.include(@files) }
+    File.utime(Time.now - 100, Time.now - 100, @archive) # Touch archive file to some point in the past.
+    archive(@archive).include(@files[1..-1]).invoke
+    inspect_archive.size.should be(@files.size - 1)
+  end
+
+  it "should not accept invalid options" do
+    archive(@archive).include(@files)
+    lambda { archive(@archive).with :option=>true }.should raise_error
+  end
+end
+
+
+describe TarTask do
+  it_should_behave_like "ArchiveTask"
+  before { @archive = File.expand_path("test.tar") }
+  define_method(:archive) { |file| tar(file) }
+
+  def inspect_archive()
+    entries = {}
+    Archive::Tar::Minitar.open @archive, "r" do |reader|
+      reader.each { |entry| entries[entry.directory ? "#{entry.name}/" : entry.name] = entry.read }
+    end
+    yield entries if block_given?
+    entries
+  end
+end
+
+
+describe TarTask, " gzipped" do
+  it_should_behave_like "ArchiveTask"
+  before { @archive = File.expand_path("test.tgz") }
+  define_method(:archive) { |file| tar(file) }
+
+  def inspect_archive()
+    entries = {}
+    Zlib::GzipReader.open @archive do |gzip| 
+      Archive::Tar::Minitar.open gzip, "r" do |reader|
+        reader.each { |entry| entries[entry.directory ? "#{entry.name}/" : entry.name] = entry.read }
+      end
+    end
+    yield entries if block_given?
+    entries
+  end
+end
+
+
+describe ZipTask do
+  it_should_behave_like "ArchiveTask"
+  before { @archive = File.expand_path("test.zip") }
+  define_method(:archive) { |file| zip(file) }
+
+  def inspect_archive()
+    entries = {}
+    Zip::ZipFile.open @archive do |zip|
+      zip.entries.each do |entry|
+        entries[entry.to_s] = zip.read(entry)
+      end
+    end
+    yield entries if block_given?
+    entries
+  end
+
+  it "should work with path object" do
+    archive(@archive).path("code").include(@files)
+    archive(@archive).invoke
+    inspect_archive { |archive| archive.keys.should include("code/") }
+  end
+end
+
+
+describe Unzip do
+  before do
+    @zip = File.expand_path("test.zip")
+    @dir = File.expand_path("test")
+    @files = %w{Test1.txt Text2.html}.map { |file| File.join(@dir, file) }.
+      each { |file| write file, content_for(file) }
+    @target = File.expand_path("target")
+  end
+
+  # Not too smart, we just create some content based on file name to
+  # make sure you read what you write.
+  def content_for(file)
+    "Content for #{File.basename(file)}"
+  end
+
+  def with_zip(*args)
+    zip(@zip).include(*args.empty? ? @files : args).invoke
+    yield
+  end
+    
+  it "should touch target directory" do
+    with_zip do
+      mkdir @target
+      File.utime(Time.now - 100, Time.now - 100, @target)
+      unzip(@target=>@zip).target.invoke
+    end
+    File.stat(@target).mtime.should be_close(Time.now, 5)
+  end
+
+  it "should expand files" do
+    with_zip do
+      unzip(@target=>@zip).target.invoke
+      @files.each { |f| File.read(File.join(@target, File.basename(f))).should eql(content_for(f)) }
+    end
+  end
+
+  it "should expand all files" do
+    with_zip do
+      unzip(@target=>@zip).target.invoke
+      FileList[File.join(@target, "*")].size.should be(@files.size)
+    end
+  end
+
+  it "should expand only included files" do
+    with_zip do
+      only = File.basename(@files.first)
+      unzip(@target=>@zip).include(only).target.invoke
+      FileList[File.join(@target, "*")].should include(File.expand_path(only, @target))
+      FileList[File.join(@target, "*")].size.should be(1)
+    end
+  end
+
+  it "should expand all but excluded files" do
+    with_zip do
+      except = File.basename(@files.first)
+      unzip(@target=>@zip).exclude(except).target.invoke
+      FileList[File.join(@target, "*")].should_not include(File.expand_path(except, @target))
+      FileList[File.join(@target, "*")].size.should be(@files.size - 1)
+    end
+  end
+
+  it "should include with nested path patterns" do
+    with_zip @files, :path=>"test/path" do
+      only = File.basename(@files.first)
+      unzip(@target=>@zip).include(only).target.invoke
+      FileList[File.join(@target, "*")].should be_empty
+
+      Rake::Task.clear ; rm_rf @target
+      unzip(@target=>@zip).include("test/path/" + only).target.invoke
+      FileList[File.join(@target, "test/path/*")].size.should be(1)
+
+      Rake::Task.clear ; rm_rf @target
+      unzip(@target=>@zip).include("test/**").target.invoke
+      FileList[File.join(@target, "test/path/*")].size.should be(2)
+    end
+  end
+
+  it "should include with relative path" do
+    with_zip @files, :path=>"test/path" do
+      only = File.basename(@files.first)
+      unzip(@target=>@zip).tap { |unzip| unzip.from_path("test").include(only) }.target.invoke
+      FileList[File.join(@target, "*")].should be_empty
+
+      Rake::Task.clear ; rm_rf @target
+      unzip(@target=>@zip).tap { |unzip| unzip.from_path("test").include("test/*") }.target.invoke
+      FileList[File.join(@target, "path/*")].should be_empty
+
+      Rake::Task.clear ; rm_rf @target
+      unzip(@target=>@zip).tap { |unzip| unzip.from_path("test").include("path/*" + only) }.target.invoke
+      FileList[File.join(@target, "path/*")].size.should be(1)
+
+      Rake::Task.clear ; rm_rf @target
+      unzip(@target=>@zip).tap { |unzip| unzip.from_path("test").include("path/*") }.target.invoke
+      FileList[File.join(@target, "path/*")].size.should be(2)
+    end
+  end
+
+  it "should exclude with relative path" do
+    with_zip @files, :path=>"test" do
+      except = File.basename(@files.first)
+      unzip(@target=>@zip).tap { |unzip| unzip.from_path("test").exclude(except) }.target.invoke
+      FileList[File.join(@target, "*")].should include(File.join(@target, File.basename(@files[1])))
+      FileList[File.join(@target, "*")].size.should be(@files.size - 1)
+    end
+  end
+
+  it "should return itself from root method" do
+    task = unzip(@target=>@zip)
+    task.root.should be(task)
+    task.from_path("foo").root.should be(task)
+  end
+
+  it "should return target task from target method" do
+    task = unzip(@target=>@zip)
+    task.target.should be(file(@target))
+    task.from_path("foo").target.should be(file(@target))
+  end
+
+  it "should alias from_path as path" do
+    task = unzip(@target=>@zip)
+    task.from_path("foo").should be(task.path("foo"))
+  end
+end

Added: incubator/buildr/trunk/test/artifact.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/test/artifact.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/test/artifact.rb (added)
+++ incubator/buildr/trunk/test/artifact.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,681 @@
+require File.join(File.dirname(__FILE__), 'sandbox')
+
+
+describe Artifact do
+  before do
+    @spec = { :group=>"com.example", :id=>"library", :type=>:jar, :version=>"2.0" }
+    @artifact = artifact(@spec)
+    @classified = artifact(@spec.merge(:classifier=>"all"))
+    @snapshot = artifact(@spec.merge({ :version=>"2.1-SNAPSHOT" }))
+  end
+
+  it "should act as one" do
+    @artifact.should respond_to(:to_spec)
+  end
+
+  it "should have an artifact identifier" do
+    @artifact.id.should eql("library")
+  end
+
+  it "should have a group identifier" do
+    @artifact.group.should eql("com.example")
+  end
+
+  it "should have a version number" do
+    @artifact.version.should eql("2.0")
+  end
+
+  it "should know if it is a snapshot" do
+    @artifact.should_not be_snapshot
+    @classified.should_not be_snapshot
+    @snapshot.should be_snapshot
+  end
+
+  it "should have a file type" do
+    @artifact.type.should eql(:jar)
+  end
+
+  it "should understand classifier" do
+    @artifact.classifier.should be_nil
+    @classified.classifier.should eql("all")
+  end
+
+  it "should return hash specification" do
+    @artifact.to_hash.should == @spec
+    @artifact.to_spec_hash.should == @spec
+    @classified.to_hash.should == @spec.merge(:classifier=>"all")
+  end
+
+  it "should return string specification" do
+    @artifact.to_spec.should eql("com.example:library:jar:2.0")
+    @classified.to_spec.should eql("com.example:library:jar:all:2.0")
+  end
+
+  it "should have associated POM artifact" do
+    @artifact.pom.to_hash.should == @artifact.to_hash.merge(:type=>:pom)
+    @classified.pom.to_hash.should == @classified.to_hash.merge(:type=>:pom)
+  end
+
+  it "should download file if file does not exist" do
+    lambda { @artifact.invoke }.should raise_error(Exception, /No remote repositories/)
+    lambda { @classified.invoke }.should raise_error(Exception, /No remote repositories/)
+  end
+
+  it "should not download file if file exists" do
+    write repositories.locate(@artifact)
+    lambda { @artifact.invoke }.should_not raise_error
+    write repositories.locate(@classified)
+    lambda { @classified.invoke }.should_not raise_error
+  end
+
+  it "should handle lack of POM gracefully" do
+    repositories.remote = "http://example.com"
+    URI.should_receive(:download).twice { |uri, target, options| raise URI::NotFoundError if uri.to_s.ends_with(".pom") }
+    lambda { @artifact.invoke }.should_not raise_error
+  end
+
+  it "should pass if POM provided" do
+    repositories.remote = "http://example.com"
+    @artifact.pom.enhance { |task| write task.name, @artifact.pom_xml }
+    write repositories.locate(@artifact)
+    lambda { @artifact.invoke }.should_not raise_error
+  end
+
+  it "should pass if POM not required" do
+    repositories.remote = "http://example.com"
+    class << @artifact ; def pom() ; end ; end
+    write repositories.locate(@artifact)
+    lambda { @artifact.invoke }.should_not raise_error
+  end
+
+  it "should not download file if dry-run" do
+    dryrun do 
+      lambda { @artifact.invoke }.should_not raise_error
+      lambda { @classified.invoke }.should_not raise_error
+    end
+  end
+
+  it "should resolve to path in local repository" do
+    @artifact.to_s.should == File.join(repositories.local, "com/example/library/2.0/library-2.0.jar")
+    @classified.to_s.should == File.join(repositories.local, "com/example/library/2.0/library-2.0-all.jar")
+  end
+
+  it "should return a list of all registered artifact specifications" do
+    define("foo", :version=>"1.0") { package :jar }
+    Artifact.list.should include(@artifact.to_spec)
+    Artifact.list.should include(@classified.to_spec)
+    Artifact.list.should include("foo:foo:jar:1.0")
+  end
+end
+
+
+describe "repositories.local" do
+  it "should default to .m2 path" do
+    # For convenience, sandbox actually sets the local repository to a temp directory
+    repositories.local = nil
+    repositories.local.should eql(File.expand_path("~/.m2/repository"))
+  end
+
+  it "should be settable" do
+    repositories.local = ".m2/local"
+    repositories.local.should eql(File.expand_path(".m2/local"))
+  end
+
+  it "should reset to default" do
+    repositories.local = ".m2/local"
+    repositories.local = nil
+    repositories.local.should eql(File.expand_path("~/.m2/repository"))
+  end
+
+  it "should locate file from string specification" do
+    repositories.local = nil
+    repositories.locate("com.example:library:jar:2.0").should eql(
+      File.expand_path("~/.m2/repository/com/example/library/2.0/library-2.0.jar"))
+  end
+
+  it "should locate file from hash specification" do
+    repositories.local = nil
+    repositories.locate(:group=>"com.example", :id=>"library", :version=>"2.0").should eql(
+      File.expand_path("~/.m2/repository/com/example/library/2.0/library-2.0.jar"))
+  end
+end
+
+
+describe "repositories.remote" do
+  before do
+    @repos = [ "http://www.ibiblio.org/maven2", "http://repo1.maven.org/maven2" ]
+  end
+
+  it "should be empty initially" do
+    repositories.remote.should be_empty
+  end
+
+  it "should be settable" do
+    repositories.remote = @repos.first
+    repositories.remote.should eql([@repos.first])
+  end
+
+  it "should be settable from array" do
+    repositories.remote = @repos
+    repositories.remote.should eql(@repos)
+  end
+
+  it "should add and return repositories in order" do
+    @repos.each { |url| repositories.remote << url }
+    repositories.remote.should eql(@repos)
+  end
+
+  it "should be used to download artifact" do
+    repositories.remote = "http://example.com"
+    URI.should_receive(:download).twice.and_return { |uri, target, options| write target }
+    lambda { artifact("com.example:library:jar:2.0").invoke }.
+      should change { File.exist?(File.join(repositories.local, "com/example/library/2.0/library-2.0.jar")) }.to(true)
+  end
+
+  it "should lookup in array order" do
+    repositories.remote = [ "http://example.com", "http://example.org" ]
+    order = ["com", "org"]
+    URI.should_receive(:download).any_number_of_times do |uri, target, options|
+      order.shift if order.first && uri.to_s[order.first]
+      fail URI::NotFoundError unless order.empty?
+      write target
+    end
+    lambda { artifact("com.example:library:jar:2.0").invoke }.should change { order.empty? }
+  end
+
+  it "should fail if artifact not found" do
+    repositories.remote = "http://example.com"
+    URI.should_receive(:download).once.ordered.and_return { fail URI::NotFoundError }
+    lambda { artifact("com.example:library:jar:2.0").invoke }.should raise_error(RuntimeError, /Failed to download/)
+    File.exist?(File.join(repositories.local, "com/example/library/2.0/library-2.0.jar")).should be_false
+  end
+
+  it "should support artifact classifier" do
+    repositories.remote = "http://example.com"
+    URI.should_receive(:download).twice.and_return { |uri, target, options| write target }
+    lambda { artifact("com.example:library:jar:all:2.0").invoke }.
+      should change { File.exist?(File.join(repositories.local, "com/example/library/2.0/library-2.0-all.jar")) }.to(true)
+  end
+
+  it "should deal well with repositories URL that lack the last slash" do
+    repositories.remote = "http://example.com/base"
+    uri = nil
+    URI.should_receive(:download).twice.and_return { |uri, target, options| }
+    artifact("group:id:jar:1.0").invoke
+    uri.to_s.should eql("http://example.com/base/group/id/1.0/id-1.0.pom")
+  end
+
+  it "should deal well with repositories URL that have the last slash" do
+    repositories.remote = "http://example.com/base/"
+    uri = nil
+    URI.should_receive(:download).twice.and_return { |uri, target, options| }
+    artifact("group:id:jar:1.0").invoke
+    uri.to_s.should eql("http://example.com/base/group/id/1.0/id-1.0.pom")
+  end
+  
+  it "should resolve m2-style deployed snapshots" do
+    metadata = <<-XML
+    <?xml version="1.0" encoding="UTF-8"?>
+    <metadata>
+      <groupId>com.example</groupId>
+      <artifactId>library</artifactId>
+      <version>2.1-SNAPSHOT</version>
+      <versioning>
+        <snapshot>
+          <timestamp>20071012.190008</timestamp>
+          <buildNumber>8</buildNumber>
+        </snapshot>
+        <lastUpdated>20071012190008</lastUpdated>
+      </versioning>
+    </metadata>
+    XML
+    repositories.remote = "http://example.com"
+    URI.should_receive(:download).twice.with(uri(/2.1-SNAPSHOT\/library-2.1-SNAPSHOT.(jar|pom)$/), anything()).
+      and_return { fail URI::NotFoundError }
+    URI.should_receive(:download).twice.with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)).
+      and_return { |uri, target, options| target.write(metadata) }
+    URI.should_receive(:download).twice.with(uri(/2.1-SNAPSHOT\/library-2.1-20071012.190008-8.(jar|pom)$/), /2.1-SNAPSHOT\/library-2.1-SNAPSHOT.(jar|pom)$/).
+      and_return { |uri, target, options| write target }
+    lambda { artifact("com.example:library:jar:2.1-SNAPSHOT").invoke }.
+      should change { File.exist?(File.join(repositories.local, "com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT.jar")) }.to(true)
+  end
+  
+  it "should handle missing maven metadata by reporting the artifact unavailable" do
+    repositories.remote = "http://example.com"
+    URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/library-2.1-SNAPSHOT.jar$/), anything()).
+      and_return { fail URI::NotFoundError }
+    URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)).
+      and_return { fail URI::NotFoundError }
+    lambda { artifact("com.example:library:jar:2.1-SNAPSHOT").invoke }.should raise_error(RuntimeError, /Failed to download/)
+    File.exist?(File.join(repositories.local, "com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT.jar")).should be_false
+  end
+  
+  it "should handle missing m2 snapshots by reporting the artifact unavailable" do
+    metadata = <<-XML
+    <?xml version="1.0" encoding="UTF-8"?>
+    <metadata>
+      <groupId>com.example</groupId>
+      <artifactId>library</artifactId>
+      <version>2.1-SNAPSHOT</version>
+      <versioning>
+        <snapshot>
+          <timestamp>20071012.190008</timestamp>
+          <buildNumber>8</buildNumber>
+        </snapshot>
+        <lastUpdated>20071012190008</lastUpdated>
+      </versioning>
+    </metadata>
+    XML
+    repositories.remote = "http://example.com"
+    URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/library-2.1-SNAPSHOT.jar$/), anything()).
+      and_return { fail URI::NotFoundError }
+    URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)).
+      and_return { |uri, target, options| target.write(metadata) }
+    URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/library-2.1-20071012.190008-8.jar$/), anything()).
+      and_return { fail URI::NotFoundError }
+    lambda { artifact("com.example:library:jar:2.1-SNAPSHOT").invoke }.should raise_error(RuntimeError, /Failed to download/)
+    File.exist?(File.join(repositories.local, "com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT.jar")).should be_false
+  end
+end
+
+
+describe "repositories.proxy" do
+  before do
+    repositories.remote = "http://example.com"
+  end
+
+  it "should be empty initially" do
+    repositories.proxy.should be_nil
+  end
+
+  it "should be used when downloading" do
+    repositories.proxy = "http://myproxy:8080"
+    Net::HTTP.should_receive(:start).with(anything, 80, "myproxy", 8080, nil, nil).twice
+    artifact("com.example:library:jar:all:2.0").invoke
+  end
+end
+
+
+describe "repositories.release_to" do
+  it "should accept URL as first argument" do
+    repositories.release_to = "http://example.com"
+    repositories.release_to.should == { :url=>"http://example.com" }
+  end
+
+  it "should accept hash with options" do
+    repositories.release_to = { :url=>"http://example.com", :username=>"john" }
+    repositories.release_to.should == { :url=>"http://example.com", :username=>"john" }
+  end
+
+  it "should allow the hash to be manipulated" do
+    repositories.release_to = "http://example.com"
+    repositories.release_to.should == { :url=>"http://example.com" }
+    repositories.release_to[:username] = "john"
+    repositories.release_to.should == { :url=>"http://example.com", :username=>"john" }
+  end
+end
+
+
+describe Buildr, "#artifact" do
+  before { @spec = { :group=>"com.example", :id=>"library", :type=>"jar", :version=>"2.0" } }
+
+  it "should accept hash specification" do
+    artifact(:group=>"com.example", :id=>"library", :type=>"jar", :version=>"2.0").should respond_to(:invoke)
+  end
+
+  it "should reject partial hash specifier" do
+    lambda { artifact(@spec.merge(:group=>nil)) }.should raise_error
+    lambda { artifact(@spec.merge(:id=>nil)) }.should raise_error
+    lambda { artifact(@spec.merge(:version=>nil)) }.should raise_error
+  end
+
+  it "should complain about invalid key" do
+    lambda { artifact(@spec.merge(:error=>true)) }.should raise_error(ArgumentError, /no such option/i)
+  end
+  
+  it "should use JAR type by default" do
+    artifact(@spec.merge(:type=>nil)).should respond_to(:invoke)
+  end
+
+  it "should accept string specification" do
+    artifact("com.example:library:jar:2.0").should respond_to(:invoke)
+  end
+
+  it "should reject partial string specifier" do
+    lambda { artifact("com.example:library:jar") }.should raise_error
+    lambda { artifact("com.example:library:jar:") }.should raise_error
+    lambda { artifact("com.example:library::2.0") }.should_not raise_error
+    lambda { artifact("com.example::jar:2.0") }.should raise_error
+    lambda { artifact(":library:jar:2.0") }.should raise_error
+  end
+
+  it "should create a task naming the artifact in the local repository" do
+    file = File.join(repositories.local, "com", "example", "library", "2.0", "library-2.0.jar")
+    Rake::Task.task_defined?(file).should be_false
+    artifact("com.example:library:jar:2.0").name.should eql(file)
+  end
+
+  it "should use from method to install artifact from existing file" do
+    write "test.jar"
+    artifact = artifact("group:id:jar:1.0").from("test.jar")
+    lambda { artifact.invoke }.should change { File.exist?(artifact.to_s) }.to(true)
+  end
+end
+
+
+describe Buildr, "#artifacts" do
+  it "should return a list of artifacts from all its arguments" do
+    specs = [ "saxon:saxon:jar:8.4", "saxon:saxon-dom:jar:8.4", "saxon:saxon-xpath:jar:8.4" ]
+    artifacts(*specs).should eql(specs.map { |spec| artifact(spec) })
+  end
+
+  it "should accept nested arrays" do
+    specs = [ "saxon:saxon:jar:8.4", "saxon:saxon-dom:jar:8.4", "saxon:saxon-xpath:jar:8.4" ]
+    artifacts([[specs[0]]], [[specs[1]], specs[2]]).should eql(specs.map { |spec| artifact(spec) })
+  end
+
+  it "should accept struct" do
+    specs = struct(:main=>"saxon:saxon:jar:8.4", :dom=>"saxon:saxon-dom:jar:8.4", :xpath=>"saxon:saxon-xpath:jar:8.4")
+    artifacts(specs).should eql(specs.values.map { |spec| artifact(spec) })
+  end
+
+  it "should ignore duplicates" do
+    artifacts("saxon:saxon:jar:8.4", "saxon:saxon:jar:8.4").size.should be(1)
+  end
+
+  it "should accept and return existing tasks" do
+    artifacts(task("foo"), task("bar")).should eql([task("foo"), task("bar")])
+  end
+
+  it "should accept filenames and expand them" do
+    artifacts("test").map(&:to_s).should eql([File.expand_path("test")])
+  end
+
+  it "should accept filenames and return filenames" do
+    artifacts("c:test").first.should be_kind_of(String)
+  end
+
+  it "should accept project and return all its packaging tasks" do
+    define "foobar", :group=>"group", :version=>"1.0" do
+      package :jar, :id=>"code"
+      package :war, :id=>"webapp"
+    end
+    foobar = project("foobar")
+    artifacts(foobar).should eql([
+      task(foobar.path_to("target/code-1.0.jar")),
+      task(foobar.path_to("target/webapp-1.0.war"))
+    ])
+  end
+
+  it "should complain about an invalid specification" do
+    lambda { artifacts(5) }.should raise_error
+    lambda { artifacts("group:no:version:") }.should raise_error
+  end
+end
+
+
+describe Buildr, "#group" do
+  it "should accept list of artifact identifiers" do
+    list = group("saxon", "saxon-dom", "saxon-xpath", :under=>"saxon", :version=>"8.4")
+    list.should include(artifact("saxon:saxon:jar:8.4"))
+    list.should include(artifact("saxon:saxon-dom:jar:8.4"))
+    list.should include(artifact("saxon:saxon-xpath:jar:8.4"))
+    list.size.should be(3)
+  end
+
+  it "should accept array with artifact identifiers" do
+    list = group(%w{saxon saxon-dom saxon-xpath}, :under=>"saxon", :version=>"8.4")
+    list.should include(artifact("saxon:saxon:jar:8.4"))
+    list.should include(artifact("saxon:saxon-dom:jar:8.4"))
+    list.should include(artifact("saxon:saxon-xpath:jar:8.4"))
+    list.size.should be(3)
+  end
+end
+
+
+describe Builder, '#install' do
+  before do
+    @spec = 'group:id:jar:1.0'
+    write @file = 'test.jar'
+  end
+
+  it 'should return the install task' do
+    install.should be(task('install'))
+  end
+
+  it 'should accept artifacts to install' do
+    install artifact(@spec)
+    lambda { install @file }.should raise_error(ArgumentError)
+  end
+
+  it 'should install artifact when install task is run' do
+    write @file
+    install artifact(@spec).from(@file)
+    lambda { install.invoke }.should change { File.exist?(artifact(@spec).to_s) }.to(true)
+  end
+
+  it 'should install POM alongside artifact' do
+    write @file
+    install artifact(@spec).from(@file)
+    lambda { install.invoke }.should change { File.exist?(artifact(@spec).pom.to_s) }.to(true)
+  end
+end
+
+
+describe Builder, '#upload' do
+  before do
+    @spec = 'group:id:jar:1.0'
+    write @file = 'test.jar'
+    repositories.deploy_to = 'sftp://example.com/base'
+  end
+
+  it 'should return the upload task' do
+    upload.should be(task('upload'))
+  end
+
+  it 'should accept artifacts to upload' do
+    upload artifact(@spec)
+    lambda { upload @file }.should raise_error(ArgumentError)
+  end
+
+  it 'should upload artifact when upload task is run' do
+    write @file
+    upload artifact(@spec).from(@file)
+    URI.should_receive(:upload).once.
+      with(URI.parse('sftp://example.com/base/group/id/1.0/id-1.0.jar'), artifact(@spec).to_s, anything)
+    URI.should_receive(:upload).once.
+      with(URI.parse('sftp://example.com/base/group/id/1.0/id-1.0.pom'), artifact(@spec).pom.to_s, anything)
+    upload.invoke
+  end
+end
+
+
+describe Buildr, "#deploy" do
+  before do
+    repositories.deploy_to = "sftp://example.com/base"
+    @file = file("README")
+    write @file.to_s
+  end
+
+  it "should deploy a file to a path using its basename" do
+    URI.should_receive(:upload).once.with(URI.parse("sftp://example.com/base/README"), @file.to_s, anything)
+    deploy @file
+  end
+
+  it "should deploy a file to specified path and basename" do
+    URI.should_receive(:upload).once.with(URI.parse("sftp://example.com/base/foo/bar/README"), @file.to_s, anything)
+    deploy @file, :path=>"foo/bar"
+  end
+end
+
+
+describe ActsAsArtifact, "#upload" do
+  it "should be used to upload artifact" do
+    test_upload artifact("com.example:library:jar:2.0"), "com/example/library/2.0/library-2.0.jar"
+  end
+
+  it "should support artifact classifier" do
+    test_upload artifact("com.example:library:jar:all:2.0"), "com/example/library/2.0/library-2.0-all.jar"
+  end
+
+  def test_upload(artifact, path)
+    # Prevent artifact from downloading anything.
+    write repositories.locate(artifact)
+    URI.should_receive(:upload).once.with(URI.parse("sftp://example.com/base/#{path}"), artifact.to_s, anything)
+    verbose(false) { artifact.upload(:url=>"sftp://example.com/base") }
+  end
+
+  it "should complain without any repository configuration" do
+    artifact = artifact("com.example:library:jar:2.0")
+    # Prevent artifact from downloading anything.
+    write repositories.locate(artifact)
+    lambda { artifact.upload }.should raise_error(Exception, /where to upload/)
+  end
+
+  it "should accept repositories.upload setting" do
+    artifact = artifact("com.example:library:jar:2.0")
+    # Prevent artifact from downloading anything.
+    write repositories.locate(artifact)
+    URI.should_receive(:upload).once
+    repositories.release_to = "sftp://example.com/base"
+    lambda { artifact.upload }.should_not raise_error
+  end
+end
+
+
+describe Rake::Task, " artifacts" do
+  it "should download all specified artifacts" do
+    artifact "group:id:jar:1.0"
+    repositories.remote = "http://example.com"
+    URI.should_receive(:download).twice.and_return { |uri, target, options| write target }
+    task("artifacts").invoke
+  end
+
+  it "should fail if failed to download an artifact" do
+    artifact "group:id:jar:1.0"
+    lambda { task("artifacts").invoke }.should raise_error(RuntimeError, /No remote repositories/)
+  end
+
+  it "should succeed if artifact already exists" do
+    write repositories.locate(artifact("group:id:jar:1.0"))
+    suppress_stdout do
+      lambda { task("artifacts").invoke }.should_not raise_error
+    end
+  end
+end
+
+
+describe Buildr, "#transitive" do
+  before do
+    repositories.remote = "http://example.com"
+    @simple = [ "saxon:saxon:jar:8.4", "saxon:saxon-dom:jar:8.4", "saxon:saxon-xpath:jar:8.4" ]
+    @simple.map { |spec| artifact(spec).pom }.each { |task| write task.name, task.pom_xml }
+    @provided = @simple.first
+    @complex = "group:app:jar:1.0"
+    write artifact(@complex).pom.to_s, <<-XML
+<project>
+  <artifactId>app</artifactId>
+  <groupId>group</groupId>
+  <dependencies>
+    <dependency>
+      <artifactId>saxon</artifactId>
+      <groupId>saxon</groupId>
+      <version>8.4</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <artifactId>saxon-dom</artifactId>
+      <groupId>saxon</groupId>
+      <version>8.4</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <artifactId>saxon-xpath</artifactId>
+      <groupId>saxon</groupId>
+      <version>8.4</version>
+    </dependency>
+    <dependency>
+      <artifactId>saxon-nosuch</artifactId>
+      <groupId>saxon</groupId>
+      <version>8.4</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
+XML
+    @transitive = "master:app:war:1.0"
+    write artifact(@transitive).pom.to_s, <<-XML
+<project>
+  <artifactId>app</artifactId>
+  <groupId>group</groupId>
+  <dependencies>
+    <dependency>
+      <artifactId>app</artifactId>
+      <groupId>group</groupId>
+      <version>1.0</version>
+    </dependency>
+  </dependencies>
+</project>
+XML
+  end
+
+  it "should return a list of artifacts from all its arguments" do
+    specs = [ "saxon:saxon:jar:8.4", "saxon:saxon-dom:jar:8.4", "saxon:saxon-xpath:jar:8.4" ]
+    transitive(*specs).should eql(specs.map { |spec| artifact(spec) })
+  end
+
+  it "should accept nested arrays" do
+    specs = [ "saxon:saxon:jar:8.4", "saxon:saxon-dom:jar:8.4", "saxon:saxon-xpath:jar:8.4" ]
+    transitive([[specs[0]]], [[specs[1]], specs[2]]).should eql(specs.map { |spec| artifact(spec) })
+  end
+
+  it "should accept struct" do
+    specs = struct(:main=>"saxon:saxon:jar:8.4", :dom=>"saxon:saxon-dom:jar:8.4", :xpath=>"saxon:saxon-xpath:jar:8.4")
+    transitive(specs).should eql(specs.values.map { |spec| artifact(spec) })
+  end
+
+  it "should ignore duplicates" do
+    transitive("saxon:saxon:jar:8.4", "saxon:saxon:jar:8.4").size.should be(1)
+  end
+
+  it "should accept and return existing tasks" do
+    transitive(task("foo"), task("bar")).should eql([task("foo"), task("bar")])
+  end
+
+  it "should accept filenames and expand them" do
+    transitive("test").map(&:to_s).should eql([File.expand_path("test")])
+  end
+
+  it "should accept filenames and return file task" do
+    transitive("c:test").first.should be_kind_of(Rake::FileTask)
+  end
+
+  it "should accept project and return all its packaging tasks" do
+    define "foobar", :group=>"group", :version=>"1.0" do
+      package :jar, :id=>"code"
+      package :war, :id=>"webapp"
+    end
+    foobar = project("foobar")
+    transitive(foobar).should eql([
+      task(foobar.path_to("target/code-1.0.jar")),
+      task(foobar.path_to("target/webapp-1.0.war"))
+    ])
+  end
+
+  it "should complain about an invalid specification" do
+    lambda { transitive(5) }.should raise_error
+    lambda { transitive("group:no:version:") }.should raise_error
+  end
+
+  it "should bring artifact and its dependencies" do
+    transitive(@complex).should eql(artifacts(@complex, @simple))
+  end
+
+  it "should bring dependencies of POM without artifact itself" do
+    transitive(@complex.sub(/jar/, "pom")).should eql(artifacts(@simple))
+  end
+
+  it "should bring artifact and transitive depenencies" do
+    transitive(@transitive).should eql(artifacts(@transitive, @complex, @simple - [@provided]))
+  end
+end

Added: incubator/buildr/trunk/test/build.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/test/build.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/test/build.rb (added)
+++ incubator/buildr/trunk/test/build.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,33 @@
+require File.join(File.dirname(__FILE__), 'sandbox')
+
+
+describe "Local directory build task" do
+  it "should execute build task for current project" do
+    define "foobar"
+    lambda { task("build").invoke }.should run_task("foobar:build")
+  end
+
+  it "should not execute build task for other projects" do
+    define "foobar", :base_dir=>"elsewhere"
+    lambda { task("build").invoke }.should_not run_task("foobar:build")
+  end
+end
+
+
+describe Project, " build task" do
+  it "should execute build task for sub-project" do
+    define("foo") { define "bar" }
+    lambda { task("foo:build").invoke }.should run_task("foo:bar:build")
+  end
+
+  it "should not execute build task of other projects" do
+    define "foo"
+    define "bar"
+    lambda { task("foo:build").invoke }.should_not run_task("bar:build")
+  end
+
+  it "should be accessible as build method" do
+    define "boo"
+    project("boo").build.should be(task("boo:build"))
+  end
+end

Added: incubator/buildr/trunk/test/checks.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/test/checks.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/test/checks.rb (added)
+++ incubator/buildr/trunk/test/checks.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,545 @@
+require File.join(File.dirname(__FILE__), 'sandbox')
+
+
+module BuildChecks
+  def should_pass()
+    lambda { check }.should_not raise_error
+  end
+
+  def should_fail()
+    lambda { check }.should raise_error(RuntimeError, /Checks failed/)
+  end
+
+  def check()
+    project("foo").task("package").invoke
+  end
+end
+
+
+describe Project, " check task" do
+  include BuildChecks
+
+  it "should execute last thing from package task" do
+    task "action"
+    define "foo", :version=>"1.0" do
+      package :jar
+      task("package").enhance { task("action").invoke }
+    end
+    lambda { check }.should run_tasks(["foo:package", "action", "foo:check"])
+  end
+
+  it "should execute all project's expectations" do
+    task "expectation"
+    define "foo", :version=>"1.0" do
+      check  { task("expectation").invoke } 
+    end
+    lambda { check }.should run_task("expectation")
+  end
+
+  it "should succeed if there are no expectations" do
+    define "foo", :version=>"1.0"
+    should_pass
+  end
+
+  it "should succeed if all expectations passed" do
+    define "foo", :version=>"1.0" do
+      check { true }
+      check { false }
+    end
+    should_pass
+  end
+
+  it "should fail if any expectation failed" do
+    define "foo", :version=>"1.0" do
+      check
+      check { fail "sorry" } 
+      check
+    end
+    should_fail
+  end
+end
+
+
+describe Project, "#check" do
+  include BuildChecks
+
+  it "should add expectation" do
+    define "foo" do
+      expectations.should be_empty
+      check
+      expectations.size.should be(1)
+    end
+  end
+
+  it "should treat no arguments as expectation against project" do
+    define "foo" do
+      subject = self
+      check do
+        it.should be(subject)
+        description.should eql(subject.to_s)
+      end
+    end
+    should_pass
+  end
+
+  it "should treat single string argument as description, expectation against project" do
+    define "foo" do
+      subject = self
+      check "should be project" do
+        it.should be(subject)
+        description.should eql("#{subject} should be project")
+      end
+    end
+    should_pass
+  end
+
+  it "should treat single object argument as subject" do
+    define "foo" do
+      subject = Object.new
+      check subject do
+        it.should be(subject)
+        description.should eql(subject.to_s)
+      end
+    end
+    should_pass
+  end
+
+  it "should treat first object as subject, second object as description" do
+    define "foo" do
+      subject = Object.new
+      check subject, "should exist" do
+        it.should be(subject)
+        description.should eql("#{subject} should exist")
+      end
+    end
+    should_pass
+  end
+
+  it "should work without block" do
+    define "foo" do
+      check "implement later"
+    end
+    should_pass
+  end
+end
+
+
+describe BuildChecks::Expectation, " matchers" do
+  include BuildChecks
+
+  it "should include Buildr matchers exist and contain" do
+    define "foo" do
+      check do
+        self.should respond_to(:exist)
+        self.should respond_to(:contain)
+      end
+    end
+    should_pass
+  end
+
+  it "should include RSpec matchers like be and eql" do
+    define "foo" do
+      check do
+        self.should respond_to(:be)
+        self.should respond_to(:eql)
+      end
+    end
+    should_pass
+  end
+
+  it "should include RSpec predicates like be_nil and be_empty" do
+    define "foo" do
+      check do
+        nil.should be_nil
+        [].should be_empty
+      end
+    end
+    should_pass
+  end
+end
+
+
+describe BuildChecks::Expectation, " exist" do
+  include BuildChecks
+
+  it "should pass if file exists" do
+    define "foo" do
+      build file("test") { |task| write task.name }
+      check(file("test")) { it.should exist }
+    end
+    should_pass
+  end
+
+  it "should fail if file does not exist" do
+    define "foo" do
+      check(file("test")) { it.should exist }
+    end
+    should_fail
+  end
+
+  it "should not attempt to invoke task" do
+    define "foo" do
+      file("test") { |task| write task.name }
+      check(file("test")) { it.should exist }
+    end
+    should_fail
+  end
+
+  it "should pass if ZIP path exists" do
+    write "resources/test"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).path("resources")) { it.should exist }
+    end
+    should_pass
+  end
+
+  it "should fail if ZIP path does not exist" do
+    mkpath "resources"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar)) { it.path("not-resources").should exist }
+    end
+    should_fail
+  end
+
+  it "should pass if ZIP entry exists" do
+    write "resources/test"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should exist }
+      check(package(:jar).path("resources").entry("test")) { it.should exist }
+    end
+    should_pass
+  end
+
+  it "should fail if ZIP path does not exist" do
+    mkpath "resources"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should exist }
+    end
+    should_fail
+  end
+end
+
+
+describe BuildChecks::Expectation, " exist" do
+  include BuildChecks
+
+  it "should pass if file has no content" do
+    define "foo" do
+      build file("test") { write "test" }
+      check(file("test")) { it.should be_empty }
+    end
+    should_pass
+  end
+
+  it "should fail if file has content" do
+    define "foo" do
+      build file("test") { write "test", "something" }
+      check(file("test")) { it.should be_empty }
+    end
+    should_fail
+  end
+
+  it "should fail if file does not exist" do
+    define "foo" do
+      check(file("test")) { it.should be_empty }
+    end
+    should_fail
+  end
+
+  it "should pass if directory is empty" do
+    define "foo" do
+      build file("test") { mkpath "test" }
+      check(file("test")) { it.should be_empty }
+    end
+    should_pass
+  end
+
+  it "should fail if directory has any files" do
+    define "foo" do
+      build file("test") { write "test/file" }
+      check(file("test")) { it.should be_empty }
+    end
+    should_fail
+  end
+
+  it "should pass if ZIP path is empty" do
+    mkpath "resources"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).path("resources")) { it.should be_empty }
+    end
+    should_pass
+  end
+
+  it "should fail if ZIP path has any entries" do
+    write "resources/test"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).path("resources")) { it.should be_empty }
+    end
+    should_fail
+  end
+
+  it "should pass if ZIP entry has no content" do
+    write "resources/test"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should be_empty }
+      check(package(:jar).path("resources").entry("test")) { it.should be_empty }
+    end
+    should_pass
+  end
+
+  it "should fail if ZIP entry has content" do
+    write "resources/test", "something"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should be_empty }
+    end
+    should_fail
+  end
+
+  it "should fail if ZIP entry does not exist" do
+    mkpath "resources"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should be_empty }
+    end
+    should_fail
+  end
+end
+
+
+describe BuildChecks::Expectation, " contain(file)" do
+  include BuildChecks
+
+  it "should pass if file content matches string" do
+    define "foo" do
+      build file("test") { write "test", "something" }
+      check(file("test")) { it.should contain("thing") }
+    end
+    should_pass
+  end
+
+  it "should pass if file content matches pattern" do
+    define "foo" do
+      build file("test") { write "test", "something\nor\nanother" }
+      check(file("test")) { it.should contain(/or/) }
+    end
+    should_pass
+  end
+
+  it "should pass if file content matches all arguments" do
+    define "foo" do
+      build file("test") { write "test", "something\nor\nanother" }
+      check(file("test")) { it.should contain(/or/, /other/) }
+    end
+    should_pass
+  end
+
+  it "should fail unless file content matchs all arguments" do
+    define "foo" do
+      build file("test") { write "test", "something" }
+      check(file("test")) { it.should contain(/some/, /other/) }
+    end
+    should_fail
+  end
+
+  it "should fail if file content does not match" do
+    define "foo" do
+      build file("test") { write "test", "something" }
+      check(file("test")) { it.should contain(/other/) }
+    end
+    should_fail
+  end
+
+  it "should fail if file does not exist" do
+    define "foo" do
+      check(file("test")) { it.should contain(/anything/) }
+    end
+    should_fail
+  end
+end
+
+
+describe BuildChecks::Expectation, " contain(directory)" do
+  include BuildChecks
+
+  it "should pass if directory contains file" do
+    write "resources/test"
+    define "foo" do
+      check(file("resources")) { it.should contain("test") }
+    end
+    should_pass
+  end
+
+  it "should pass if directory contains glob pattern" do
+    write "resources/with/test"
+    define "foo" do
+      check(file("resources")) { it.should contain("**/t*st") }
+    end
+    should_pass
+  end
+
+  it "should pass if directory contains all arguments" do
+    write "resources/with/test"
+    define "foo" do
+      check(file("resources")) { it.should contain("**/test", "**/*") }
+    end
+    should_pass
+  end
+
+  it "should fail unless directory contains all arguments" do
+    write "resources/test"
+    define "foo" do
+      check(file("resources")) { it.should contain("test", "or-not") }
+    end
+    should_fail
+  end
+
+  it "should fail if directory is empty" do
+    mkpath "resources"
+    define "foo" do
+      check(file("resources")) { it.should contain("test") }
+    end
+    should_fail
+  end
+
+  it "should fail if directory does not exist" do
+    define "foo" do
+      check(file("resources")) { it.should contain }
+    end
+    should_fail
+  end
+end
+
+
+describe BuildChecks::Expectation, " contain(zip.entry)" do
+  include BuildChecks
+
+  it "should pass if ZIP entry content matches string" do
+    write "resources/test", "something"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should contain("thing") }
+      #check(package(:jar)) { it.entry("resources/test").should contain("thing") }
+    end
+    should_pass
+  end
+
+  it "should pass if ZIP entry content matches pattern" do
+    write "resources/test", "something\nor\another"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should contain(/or/) }
+      #check(package(:jar)) { it.entry("resources/test").should contain(/or/) }
+    end
+    should_pass
+  end
+
+  it "should pass if ZIP entry content matches all arguments" do
+    write "resources/test", "something\nor\nanother"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should contain(/or/, /other/) }
+      #check(package(:jar)) { it.entry("resources/test").should contain(/or/, /other/) }
+    end
+    should_pass
+  end
+
+  it "should fail unless ZIP path contains all arguments" do
+    write "resources/test", "something"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should contain(/some/, /other/) }
+      #check(package(:jar)) { it.entry("resources/test").should contain(/some/, /other/) }
+    end
+    should_fail
+  end
+
+  it "should fail if ZIP entry content does not match" do
+    write "resources/test", "something"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should contain(/other/) }
+      #check(package(:jar)) { it.entry("resources/test").should contain(/other/) }
+    end
+    should_fail
+  end
+
+  it "should fail if ZIP entry does not exist" do
+    mkpath "resources"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).entry("resources/test")) { it.should contain(/anything/) }
+      #check(package(:jar)) { it.entry("resources/test").should contain(/anything/) }
+    end
+    should_fail
+  end
+end
+
+
+describe BuildChecks::Expectation, " contain(zip.path)" do
+  include BuildChecks
+
+  it "should pass if ZIP path contains file" do
+    write "resources/test"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).path("resources")) { it.should contain("test") }
+    end
+    should_pass
+  end
+
+  it "should handle deep nesting" do
+    write "resources/test/test2.efx"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("*")
+      check(package(:jar)) { it.should contain("resources/test/test2.efx") }
+      check(package(:jar).path("resources")) { it.should contain("test/test2.efx") }
+      check(package(:jar).path("resources/test")) { it.should contain("test2.efx") }
+    end
+    should_pass
+  end
+
+
+  it "should pass if ZIP path contains pattern" do
+    write "resources/with/test"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).path("resources")) { it.should contain("**/t*st") }
+    end
+    should_pass
+  end
+
+  it "should pass if ZIP path contains all arguments" do
+    write "resources/with/test"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).path("resources")) { it.should contain("**/test", "**/*") }
+    end
+    should_pass
+  end
+
+  it "should fail unless ZIP path contains all arguments" do
+    write "resources/test"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).path("resources")) { it.should contain("test", "or-not") }
+    end
+    should_fail
+  end
+
+  it "should fail if ZIP path is empty" do
+    mkpath "resources"
+    define "foo", :version=>"1.0" do
+      package(:jar).include("resources")
+      check(package(:jar).path("resources")) { it.should contain("test") }
+    end
+    should_fail
+  end
+end