Please review pull request #504: Ticket/2.7.x/12256 module tool core changes opened by (mmrobins)

Description:

These are the changes the puppet module tool will need that potentially affect other parts of Puppet.

  • Opened: Wed Feb 15 19:38:33 UTC 2012
  • Based on: puppetlabs:2.7.x (fef777b60ffca37cd3a41dd642c6b0838e32b492)
  • Requested merge: mmrobins:ticket/2.7.x/12256-module_tool_core_changes (360d4b7751532aa92d18735a765a4d417a2a637a)

Diff follows:

diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb
index 4282fc6..b68cd87 100644
--- a/lib/puppet/module.rb
+++ b/lib/puppet/module.rb
@@ -1,5 +1,6 @@
 require 'puppet/util/logging'
 require 'semver'
+require 'puppet/module_tool/applications'
 
 # Support for modules
 class Puppet::Module
@@ -31,7 +32,7 @@ def self.find(modname, environment = nil)
   attr_reader :name, :environment
   attr_writer :environment
 
-  attr_accessor :dependencies
+  attr_accessor :dependencies, :forge_name
   attr_accessor :source, :author, :version, :license, :puppetversion, :summary, :description, :project_page
 
   def has_metadata?
@@ -40,6 +41,8 @@ def has_metadata?
     return false unless FileTest.exist?(metadata_file)
 
     metadata = PSON.parse File.read(metadata_file)
+
+
     return metadata.is_a?(Hash) && !metadata.keys.empty?
   end
 
@@ -108,6 +111,8 @@ def license_file
 
   def load_metadata
     data = "" File.read(metadata_file)
+    @forge_name = data['name'].gsub('-', '/') if data['name']
+
     [:source, :author, :version, :license, :puppetversion, :dependencies].each do |attr|
       unless value = data[attr.to_s]
         unless attr == :puppetversion
@@ -155,6 +160,26 @@ def to_s
     result
   end
 
+  def dependencies_as_modules
+    dependent_modules = []
+    dependencies and dependencies.each do |dep|
+      author, dep_name = dep["name"].split('/')
+      found_module = environment.module(dep_name)
+      dependent_modules << found_module if found_module
+    end
+
+    dependent_modules
+  end
+
+  def required_by
+    environment.module_requirements[self.forge_name] || {}
+  end
+
+  def has_local_changes?
+    changes = Puppet::Module::Tool::Applications::Checksummer.run(path)
+    !changes.empty?
+  end
+
   def unmet_dependencies
     return [] unless dependencies
 
diff --git a/lib/puppet/module_tool/applications.rb b/lib/puppet/module_tool/applications.rb
index 24bcbe2..d5eb7f5 100644
--- a/lib/puppet/module_tool/applications.rb
+++ b/lib/puppet/module_tool/applications.rb
@@ -1,13 +1,17 @@
-module Puppet::Module::Tool
-  module Applications
-    require 'puppet/module_tool/applications/application'
-    require 'puppet/module_tool/applications/builder'
-    require 'puppet/module_tool/applications/checksummer'
-    require 'puppet/module_tool/applications/cleaner'
-    require 'puppet/module_tool/applications/generator'
-    require 'puppet/module_tool/applications/installer'
-    require 'puppet/module_tool/applications/searcher'
-    require 'puppet/module_tool/applications/unpacker'
-    require 'puppet/module_tool/applications/uninstaller'
+require 'puppet/module'
+
+class Puppet::Module
+  module Tool
+    module Applications
+      require 'puppet/module_tool/applications/application'
+      require 'puppet/module_tool/applications/builder'
+      require 'puppet/module_tool/applications/checksummer'
+      require 'puppet/module_tool/applications/cleaner'
+      require 'puppet/module_tool/applications/generator'
+      require 'puppet/module_tool/applications/installer'
+      require 'puppet/module_tool/applications/searcher'
+      require 'puppet/module_tool/applications/unpacker'
+      require 'puppet/module_tool/applications/uninstaller'
+    end
   end
 end
diff --git a/lib/puppet/module_tool/applications/application.rb b/lib/puppet/module_tool/applications/application.rb
index 43d5c04..fd398da 100644
--- a/lib/puppet/module_tool/applications/application.rb
+++ b/lib/puppet/module_tool/applications/application.rb
@@ -1,10 +1,11 @@
 require 'net/http'
 require 'semver'
+require 'puppet/module_tool/utils/interrogation'
 
 module Puppet::Module::Tool
   module Applications
     class Application
-      include Utils::Interrogation
+      include Puppet::Module::Tool::Utils::Interrogation
 
       def self.run(*args)
         new(*args).run
diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb
index 06be16d..2a64f01 100644
--- a/lib/puppet/node/environment.rb
+++ b/lib/puppet/node/environment.rb
@@ -93,6 +93,14 @@ def module(name)
     mod
   end
 
+  def module_by_forge_name(forge_name)
+    author, modname = forge_name.split('/')
+    found_mod = self.module(modname)
+    found_mod and found_mod.forge_name == forge_name ?
+      found_mod :
+      nil
+  end
+
   # Cache the modulepath, so that we aren't searching through
   # all known directories all the time.
   cached_attr(:modulepath, Puppet[:filetimeout]) do
@@ -128,6 +136,24 @@ def modules_by_path
     modules_by_path
   end
 
+  def module_requirements
+    deps = {}
+    modules.each do |mod|
+      next unless mod.forge_name
+      deps[mod.forge_name] ||= []
+      mod.dependencies and mod.dependencies.sort_by {|mod_dep| mod_dep['name']}.each do |mod_dep|
+        deps[mod_dep['name']] ||= []
+        dep_details = {
+          'name'    => mod.forge_name,
+          'version' => mod.version,
+          'version_requirement' => mod_dep['version_requirement']
+        }
+        deps[mod_dep['name']] << dep_details
+      end
+    end
+    deps
+  end
+
   def to_s
     name.to_s
   end
diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb
index f4dd0cf..14cb4a7 100644
--- a/lib/puppet/util/monkey_patches.rb
+++ b/lib/puppet/util/monkey_patches.rb
@@ -145,6 +145,24 @@ def self.binwrite(name, string, offset = 0)
 
 end
 
+class Range
+  def intersection(other)
+    raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
+    return unless other === self.first || self === other.first
+
+    start = [self.first, other.first].max
+    if self.exclude_end? && self.last <= other.last
+      start ... self.last
+    elsif other.exclude_end? && self.last >= other.last
+      start ... other.last
+    else
+      start .. [ self.last, other.last ].min
+    end
+  end
+
+  alias_method :&, :intersection
+end
+
 # Ruby 1.8.5 doesn't have tap
 module Kernel
   def tap
diff --git a/lib/semver.rb b/lib/semver.rb
index ef9435a..5029d96 100644
--- a/lib/semver.rb
+++ b/lib/semver.rb
@@ -1,9 +1,14 @@
-class SemVer
-  VERSION = /^v?(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/
-  SIMPLE_RANGE = /^v?(\d+|[xX])(?:\.(\d+|[xX])(?:\.(\d+|[xX]))?)?$/
+require 'puppet/util/monkey_patches'
 
+# We need to subclass Numeric to force range comparisons not to try to iterate over SemVer
+# and instead use numeric comparisons (eg >, <, >=, <=)
+# Ruby 1.8 already did this for all ranges, but Ruby 1.9 changed range include behavior
+class SemVer < Numeric
   include Comparable
 
+  VERSION = /^v?(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z-]*|)$/
+  SIMPLE_RANGE = /^v?(\d+|[xX])(?:\.(\d+|[xX])(?:\.(\d+|[xX]))?)?$/
+
   def self.valid?(ver)
     VERSION =~ ver
   end
@@ -12,6 +17,45 @@ def self.find_matching(pattern, versions)
     versions.select { |v| v.matched_by?("#{pattern}") }.sort.last
   end
 
+  def self.[](range)
+    range.gsub(/([><=])\s+/, '\1').split(/\b\s+(?!-)/).map do |r|
+      case r
+      when SemVer::VERSION
+        SemVer.new(r + '-') .. SemVer.new(r)
+      when SemVer::SIMPLE_RANGE
+        r += ".0" unless SemVer.valid?(r.gsub(/x/i, '0'))
+        SemVer.new(r.gsub(/x/i, '0'))...SemVer.new(r.gsub(/(\d+)\.x/i) { "#{$1.to_i + 1}.0" } + '-')
+      when /\s+-\s+/
+        a, b = r.split(/\s+-\s+/)
+        SemVer.new(a + '-') .. SemVer.new(b)
+      when /^~/
+        ver = r.sub(/~/, '').split('.').map(&:to_i)
+        start = (ver + [0] * (3 - ver.length)).join('.')
+
+        ver.pop unless ver.length == 1
+        ver[-1] = ver.last + 1
+
+        finish = (ver + [0] * (3 - ver.length)).join('.')
+        SemVer.new(start + '-') ... SemVer.new(finish + '-')
+      when /^>=/
+        ver = r.sub(/^>=/, '')
+        SemVer.new(ver + '-') .. SemVer::MAX
+      when /^<=/
+        ver = r.sub(/^<=/, '')
+        SemVer::MIN .. SemVer.new(ver)
+      when /^>/
+        ver = r.sub(/^>/, '').split('.').map(&:to_i)
+        ver[2] = ver.last + 1
+        SemVer.new(ver.join('.') + '-') .. SemVer::MAX
+      when /^</
+        ver = r.sub(/^</, '')
+        SemVer::MIN ... SemVer.new(ver + '-')
+      else
+        (1..1)
+      end
+    end.inject { |a,e| a & e }
+  end
+
   attr_reader :major, :minor, :tiny, :special
 
   def initialize(ver)
@@ -59,7 +103,14 @@ def matched_by?(pattern)
   end
 
   def inspect
-    "v#{@major}.#{@minor}.#{@tiny}#{@special}"
+    @vstring || "v#{@major}.#{@minor}.#{@tiny}#{@special}"
   end
   alias :to_s :inspect
+
+  MIN = SemVer.new('0.0.0-')
+  MIN.instance_variable_set(:@vstring, 'vMIN')
+
+  MAX = SemVer.new('8.0.0')
+  MAX.instance_variable_set(:@major, (1.0/0)) # => Infinity
+  MAX.instance_variable_set(:@vstring, 'vMAX')
 end
diff --git a/spec/lib/puppet_spec/modules.rb b/spec/lib/puppet_spec/modules.rb
new file mode 100644
index 0000000..156e466
--- /dev/null
+++ b/spec/lib/puppet_spec/modules.rb
@@ -0,0 +1,26 @@
+module PuppetSpec::Modules
+  class << self
+    def create(name, dir, options = {})
+      module_dir = File.join(dir, name)
+      FileUtils.mkdir_p(module_dir)
+
+      environment = Puppet::Node::Environment.new(options[:environment])
+
+      if metadata = options[:metadata]
+        metadata[:source]  ||= 'github'
+        metadata[:author]  ||= 'puppetlabs'
+        metadata[:version] ||= '9.9.9'
+        metadata[:license] ||= 'to kill'
+        metadata[:dependencies] ||= []
+
+        metadata[:name] = "#{metadata[:author]}/#{name}"
+
+        File.open(File.join(module_dir, 'metadata.json'), 'w') do |f|
+          f.write(metadata.to_pson)
+        end
+      end
+
+      Puppet::Module.new(name, :environment => environment, :path => module_dir)
+    end
+  end
+end
diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb
index 695f648..657ca40 100755
--- a/spec/unit/module_spec.rb
+++ b/spec/unit/module_spec.rb
@@ -1,6 +1,8 @@
 #!/usr/bin/env rspec
 require 'spec_helper'
 require 'puppet_spec/files'
+require 'puppet_spec/modules'
+require 'puppet/module_tool/checksums'
 
 describe Puppet::Module do
   include PuppetSpec::Files
@@ -532,13 +534,14 @@
 end
 
 describe Puppet::Module do
+  include PuppetSpec::Files
   before do
-    Puppet::Module.any_instance.stubs(:path).returns "/my/mod/path"
-    @module = Puppet::Module.new("foo")
+    @modpath = tmpdir('modpath')
+    @module = PuppetSpec::Modules.create('mymod', @modpath)
   end
 
   it "should use 'License' in its current path as its metadata file" do
-    @module.license_file.should == "/my/mod/path/License"
+    @module.license_file.should == "#{@modpath}/mymod/License"
   end
 
   it "should return nil as its license file when the module has no path" do
@@ -547,13 +550,13 @@
   end
 
   it "should cache the license file" do
-    Puppet::Module.any_instance.expects(:path).once.returns nil
-    mod = Puppet::Module.new("foo")
-    mod.license_file.should == mod.license_file
+    @module.expects(:path).once.returns nil
+    @module.license_file
+    @module.license_file
   end
 
   it "should use 'metadata.json' in its current path as its metadata file" do
-    @module.metadata_file.should == "/my/mod/path/metadata.json"
+    @module.metadata_file.should == "#{@modpath}/mymod/metadata.json"
   end
 
   it "should return nil as its metadata file when the module has no path" do
@@ -657,4 +660,72 @@
 
     it "should fail if the discovered name is different than the metadata name"
   end
+
+  it "should be able to tell if there are local changes" do
+    modpath = tmpdir('modpath')
+    foo_checksum = 'acbd18db4cc2f85cedef654fccc4a4d8'
+    checksummed_module = PuppetSpec::Modules.create(
+      'changed',
+      modpath,
+      :metadata => {
+        :checksums => {
+          "foo" => foo_checksum,
+        }
+      }
+    )
+
+    foo_path = Pathname.new(File.join(checksummed_module.path, 'foo'))
+
+    IO.binwrite(foo_path, 'notfoo')
+    Puppet::Module::Tool::Checksums.new(foo_path).checksum(foo_path).should_not == foo_checksum
+    checksummed_module.has_local_changes?.should be_true
+
+    IO.binwrite(foo_path, 'foo')
+
+    Puppet::Module::Tool::Checksums.new(foo_path).checksum(foo_path).should == foo_checksum
+    checksummed_module.has_local_changes?.should be_false
+  end
+
+  it "should know what other modules require it" do
+    Puppet.settings[:modulepath] = @modpath
+    dependable = PuppetSpec::Modules.create(
+      'dependable',
+      @modpath,
+      :metadata => {:author => 'puppetlabs'}
+    )
+    PuppetSpec::Modules.create(
+      'needy',
+      @modpath,
+      :metadata => {
+        :author => 'beggar',
+        :dependencies => [{
+            "version_requirement" => ">= 2.2.0",
+            "name" => "puppetlabs/dependable"
+        }]
+      }
+    )
+    PuppetSpec::Modules.create(
+      'wantit',
+      @modpath,
+      :metadata => {
+        :author => 'spoiled',
+        :dependencies => [{
+            "version_requirement" => "< 5.0.0",
+            "name" => "puppetlabs/dependable"
+        }]
+      }
+    )
+    dependable.required_by.should =~ [
+      {
+        "name"    => "beggar/needy",
+        "version" => "9.9.9",
+        "version_requirement" => ">= 2.2.0"
+      },
+      {
+        "name"    => "spoiled/wantit",
+        "version" => "9.9.9",
+        "version_requirement" => "< 5.0.0"
+      }
+    ]
+  end
 end
diff --git a/spec/unit/module_tool/application_spec.rb b/spec/unit/module_tool/application_spec.rb
index 22d3632..b86ec5c 100644
--- a/spec/unit/module_tool/application_spec.rb
+++ b/spec/unit/module_tool/application_spec.rb
@@ -4,9 +4,9 @@
 describe Puppet::Module::Tool::Applications::Application do
   describe 'app' do
 
-    good_versions = %w{ 1.2.4 0.0.1 0.0.0 0.0.2git-8-g3d316d1 0.0.3b1 10.100.10000
-                         0.1.2rc1 0.1.2dev-1 0.1.2svn12345 }
-    bad_versions = %w{ 0.1.2-3 0.1 0 0.1.2.3 dev }
+    good_versions = %w{ 1.2.4 0.0.1 0.0.0 0.0.2-git-8-g3d316d1 0.0.3-b1 10.100.10000
+                         0.1.2-rc1 0.1.2-dev-1 0.1.2-svn12345 0.1.2-3 }
+    bad_versions = %w{ 0.1 0 0.1.2.3 dev 0.1.2beta }
 
     before do
       @app = Class.new(described_class).new
diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb
index d5d3068..4adc5a6 100755
--- a/spec/unit/node/environment_spec.rb
+++ b/spec/unit/node/environment_spec.rb
@@ -5,6 +5,7 @@
 
 require 'puppet/node/environment'
 require 'puppet/util/execution'
+require 'puppet_spec/modules'
 
 describe Puppet::Node::Environment do
   let(:env) { Puppet::Node::Environment.new("testing") }
@@ -124,8 +125,10 @@
 
   describe "when validating modulepath or manifestdir directories" do
     before :each do
-      @path_one = make_absolute('/one')
-      @path_two = make_absolute('/two')
+      @path_one = tmpdir("path_one")
+      @path_two = tmpdir("path_one")
+      sep = File::PATH_SEPARATOR
+      Puppet[:modulepath] = "#{@path_one}#{sep}#{@path_two}"
     end
 
     it "should not return non-directories" do
@@ -167,15 +170,13 @@
     end
 
     it "should return nil if asked for a module that does not exist in its path" do
-
-      mod = mock 'module'
-      Puppet::Module.expects(:new).with("one", :environment => env).returns mod
-      mod.expects(:exist?).returns false
+      modpath = tmpdir('modpath')
+      env.modulepath = [modpath]
 
       env.module("one").should be_nil
     end
 
-    describe ".modules_by_path" do
+    describe "module data" do
       before do
         dir = tmpdir("deep_path")
 
@@ -187,75 +188,153 @@
         FileUtils.mkdir_p(@second)
       end
 
-      it "should return an empty list if there are no modules" do
-        env.modules_by_path.should == {
-          @first  => [],
-          @second => []
-        }
-      end
-
-      it "should include modules even if they exist in multiple dirs in the modulepath" do
-        modpath1 = File.join(@first, "foo")
-        FileUtils.mkdir_p(modpath1)
-        modpath2 = File.join(@second, "foo")
-        FileUtils.mkdir_p(modpath2)
-
-        env.modules_by_path.should == {
-          @first  => [Puppet::Module.new('foo', :environment => env, :path => modpath1)],
-          @second => [Puppet::Module.new('foo', :environment => env, :path => modpath2)]
-        }
-      end
-    end
-
-    describe ".modules" do
-      it "should return an empty list if there are no modules" do
-        env.modulepath = %w{/a /b}
-        Dir.expects(:entries).with("/a").returns []
-        Dir.expects(:entries).with("/b").returns []
-
-        env.modules.should == []
-      end
-
-      it "should return a module named for every directory in each module path" do
-        env.modulepath = %w{/a /b}
-        Dir.expects(:entries).with("/a").returns %w{foo bar}
-        Dir.expects(:entries).with("/b").returns %w{bee baz}
-
-        env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort
+      describe "#modules_by_path" do
+        it "should return an empty list if there are no modules" do
+          env.modules_by_path.should == {
+            @first  => [],
+            @second => []
+          }
+        end
+
+        it "should include modules even if they exist in multiple dirs in the modulepath" do
+          modpath1 = File.join(@first, "foo")
+          FileUtils.mkdir_p(modpath1)
+          modpath2 = File.join(@second, "foo")
+          FileUtils.mkdir_p(modpath2)
+
+          env.modules_by_path.should == {
+            @first  => [Puppet::Module.new('foo', :environment => env, :path => modpath1)],
+            @second => [Puppet::Module.new('foo', :environment => env, :path => modpath2)]
+          }
+        end
       end
 
-      it "should remove duplicates" do
-        env.modulepath = %w{/a /b}
-        Dir.expects(:entries).with("/a").returns %w{foo}
-        Dir.expects(:entries).with("/b").returns %w{foo}
-
-        env.modules.collect{|mod| mod.name}.sort.should == %w{foo}
+      describe "#module_requirements" do
+        it "should return a list of what modules depend on other modules" do
+          PuppetSpec::Modules.create(
+            'foo',
+            @first,
+            :metadata => {
+              :author       => 'puppetlabs',
+              :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }]
+            }
+          )
+          PuppetSpec::Modules.create(
+            'bar',
+            @second,
+            :metadata => {
+              :author       => 'puppetlabs',
+              :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }]
+            }
+          )
+          PuppetSpec::Modules.create(
+            'baz',
+            @first,
+            :metadata => {
+              :author       => 'puppetlabs',
+              :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "3.0.0" }]
+            }
+          )
+
+          env.module_requirements.should == {
+            'puppetlabs/foo' => [
+              {
+                "name"    => "puppetlabs/bar",
+                "version" => "9.9.9",
+                "version_requirement" => "<= 2.0.0"
+              }
+            ],
+            'puppetlabs/bar' => [
+              {
+                "name"    => "puppetlabs/baz",
+                "version" => "9.9.9",
+                "version_requirement" => "3.0.0"
+              },
+              {
+                "name"    => "puppetlabs/foo",
+                "version" => "9.9.9",
+                "version_requirement" => ">= 1.0.0"
+              }
+            ],
+            'puppetlabs/baz' => []
+          }
+        end
       end
 
-      it "should ignore invalid modules" do
-        env.modulepath = %w{/a}
-        Dir.expects(:entries).with("/a").returns %w{foo bar}
-
-        Puppet::Module.expects(:new).with { |name, env| name == "foo" }.returns mock("foomod", :name => "foo")
-        Puppet::Module.expects(:new).with { |name, env| name == "bar" }.raises( Puppet::Module::InvalidName, "name is invalid" )
-
-        env.modules.collect{|mod| mod.name}.sort.should == %w{foo}
+      describe ".module_by_forge_name" do
+        it "should find modules by forge_name" do
+          mod = PuppetSpec::Modules.create(
+            'baz',
+            @first,
+            :metadata => {:author => 'puppetlabs'},
+            :environment => env
+          )
+          env.module_by_forge_name('puppetlabs/baz').should == mod
+        end
+
+        it "should not find modules with same name by the wrong author" do
+          mod = PuppetSpec::Modules.create(
+            'baz',
+            @first,
+            :metadata => {:author => 'sneakylabs'},
+            :environment => env
+          )
+          env.module_by_forge_name('puppetlabs/baz').should == nil
+        end
+
+        it "should return nil when the module can't be found" do
+          env.module_by_forge_name('ima/nothere').should be_nil
+        end
       end
 
-      it "should create modules with the correct environment" do
-        env.modulepath = %w{/a}
-        Dir.expects(:entries).with("/a").returns %w{foo}
+      describe ".modules" do
+        it "should return an empty list if there are no modules" do
+          env.modules.should == []
+        end
+
+        it "should return a module named for every directory in each module path" do
+          %w{foo bar}.each do |mod_name|
+            FileUtils.mkdir_p(File.join(@first, mod_name))
+          end
+          %w{bee baz}.each do |mod_name|
+            FileUtils.mkdir_p(File.join(@second, mod_name))
+          end
+          env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort
+        end
+
+        it "should remove duplicates" do
+          FileUtils.mkdir_p(File.join(@first,  'foo'))
+          FileUtils.mkdir_p(File.join(@second, 'foo'))
+
+          env.modules.collect{|mod| mod.name}.sort.should == %w{foo}
+        end
+
+        it "should ignore modules with invalid names" do
+          FileUtils.mkdir_p(File.join(@first, 'foo'))
+          FileUtils.mkdir_p(File.join(@first, 'foo2'))
+          FileUtils.mkdir_p(File.join(@first, 'foo-bar'))
+          FileUtils.mkdir_p(File.join(@first, 'foo_bar'))
+          FileUtils.mkdir_p(File.join(@first, 'foo*bar'))
+          FileUtils.mkdir_p(File.join(@first, 'foo bar'))
+
+          env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar}
+        end
+
+        it "should create modules with the correct environment" do
+          FileUtils.mkdir_p(File.join(@first, 'foo'))
+
+          env.modules.each {|mod| mod.environment.should == env }
+        end
 
-        env.modules.each {|mod| mod.environment.should == env }
       end
+    end
 
-      it "should cache the module list" do
-        env.modulepath = %w{/a}
-        Dir.expects(:entries).once.with("/a").returns %w{foo}
+    it "should cache the module list" do
+      env.modulepath = %w{/a}
+      Dir.expects(:entries).once.with("/a").returns %w{foo}
 
-        env.modules
-        env.modules
-      end
+      env.modules
+      env.modules
     end
   end
 
diff --git a/spec/unit/semver_spec.rb b/spec/unit/semver_spec.rb
index 0e0457b..87fc968 100644
--- a/spec/unit/semver_spec.rb
+++ b/spec/unit/semver_spec.rb
@@ -10,13 +10,13 @@
     end
 
     it 'should validate special version strings' do
-      %w[ 0.0.0foo 999.999.999bar v0.0.0a v999.999.999beta ].each do |vstring|
+      %w[ 0.0.0-foo 999.999.999-bar v0.0.0-a v999.999.999-beta ].each do |vstring|
         SemVer.valid?(vstring).should be_true
       end
     end
 
     it 'should fail to validate invalid version strings' do
-      %w[ nope 0.0foo 999.999 x0.0.0 z.z.z 1.2.3-beta 1.x.y ].each do |vstring|
+      %w[ nope 0.0foo 999.999 x0.0.0 z.z.z 1.2.3beta 1.x.y ].each do |vstring|
         SemVer.valid?(vstring).should be_false
       end
     end
@@ -27,8 +27,8 @@
       @versions = %w[
         0.0.1
         0.0.2
-        1.0.0rc1
-        1.0.0rc2
+        1.0.0-rc1
+        1.0.0-rc2
         1.0.0
         1.0.1
         1.1.0
@@ -38,7 +38,7 @@
         1.1.4
         1.2.0
         1.2.1
-        2.0.0rc1
+        2.0.0-rc1
       ].map { |v| SemVer.new(v) }
     end
 
@@ -49,7 +49,7 @@
     end
 
     it 'should return nil if no versions match' do
-      %w[ 3.0.0 2.0.0rc2 1.0.0alpha ].each do |v|
+      %w[ 3.0.0 2.0.0-rc2 1.0.0-alpha ].each do |v|
         SemVer.find_matching(v, @versions).should be_nil
       end
     end
@@ -58,7 +58,7 @@
       SemVer.find_matching('1.0', @versions).should == 'v1.0.1'
       SemVer.find_matching('1.1', @versions).should == 'v1.1.4'
       SemVer.find_matching('1', @versions).should   == 'v1.2.1'
-      SemVer.find_matching('2', @versions).should   == 'v2.0.0rc1'
+      SemVer.find_matching('2', @versions).should   == 'v2.0.0-rc1'
       SemVer.find_matching('2.1', @versions).should == nil
     end
 
@@ -68,12 +68,105 @@
       SemVer.find_matching('1.1.x', @versions).should == 'v1.1.4'
       SemVer.find_matching('1.x', @versions).should   == 'v1.2.1'
       SemVer.find_matching('1.x.x', @versions).should == 'v1.2.1'
-      SemVer.find_matching('2.x', @versions).should   == 'v2.0.0rc1'
-      SemVer.find_matching('2.x.x', @versions).should == 'v2.0.0rc1'
+      SemVer.find_matching('2.x', @versions).should   == 'v2.0.0-rc1'
+      SemVer.find_matching('2.x.x', @versions).should == 'v2.0.0-rc1'
       SemVer.find_matching('2.1.x', @versions).should == nil
     end
   end
 
+  describe '::[]' do
+    it "should produce expected ranges" do
+      tests = {
+        '1.2.3'           => SemVer.new('v1.2.3-') ..  SemVer.new('v1.2.3'),
+        '>1.2.3'          => SemVer.new('v1.2.4-') ..  SemVer::MAX,
+        '<1.2.3'          => SemVer::MIN           ... SemVer.new('v1.2.3-'),
+        '>=1.2.3'         => SemVer.new('v1.2.3-') ..  SemVer::MAX,
+        '<=1.2.3'         => SemVer::MIN           ..  SemVer.new('v1.2.3'),
+        '>1.2.3 <1.2.5'   => SemVer.new('v1.2.4-') ... SemVer.new('v1.2.5-'),
+        '>=1.2.3 <=1.2.5' => SemVer.new('v1.2.3-') ..  SemVer.new('v1.2.5'),
+        '1.2.3 - 2.3.4'   => SemVer.new('v1.2.3-') ..  SemVer.new('v2.3.4'),
+        '~1.2.3'          => SemVer.new('v1.2.3-') ... SemVer.new('v1.3.0-'),
+        '~1.2'            => SemVer.new('v1.2.0-') ... SemVer.new('v2.0.0-'),
+        '~1'              => SemVer.new('v1.0.0-') ... SemVer.new('v2.0.0-'),
+        '1.2.x'           => SemVer.new('v1.2.0')  ... SemVer.new('v1.3.0-'),
+        '1.x'             => SemVer.new('v1.0.0')  ... SemVer.new('v2.0.0-'),
+      }
+
+      tests.each do |vstring, expected|
+        SemVer[vstring].should == expected
+      end
+    end
+
+    it "should suit up" do
+      suitability = {
+        [ '1.2.3',         'v1.2.2' ] => false,
+        [ '>=1.2.3',       'v1.2.2' ] => false,
+        [ '<=1.2.3',       'v1.2.2' ] => true,
+        [ '>= 1.2.3',      'v1.2.2' ] => false,
+        [ '<= 1.2.3',      'v1.2.2' ] => true,
+        [ '1.2.3 - 1.2.4', 'v1.2.2' ] => false,
+        [ '~1.2.3',        'v1.2.2' ] => false,
+        [ '~1.2',          'v1.2.2' ] => true,
+        [ '~1',            'v1.2.2' ] => true,
+        [ '1.2.x',         'v1.2.2' ] => true,
+        [ '1.x',           'v1.2.2' ] => true,
+
+        [ '1.2.3',         'v1.2.3-alpha' ] => true,
+        [ '>=1.2.3',       'v1.2.3-alpha' ] => true,
+        [ '<=1.2.3',       'v1.2.3-alpha' ] => true,
+        [ '>= 1.2.3',      'v1.2.3-alpha' ] => true,
+        [ '<= 1.2.3',      'v1.2.3-alpha' ] => true,
+        [ '>1.2.3',        'v1.2.3-alpha' ] => false,
+        [ '<1.2.3',        'v1.2.3-alpha' ] => false,
+        [ '> 1.2.3',       'v1.2.3-alpha' ] => false,
+        [ '< 1.2.3',       'v1.2.3-alpha' ] => false,
+        [ '1.2.3 - 1.2.4', 'v1.2.3-alpha' ] => true,
+        [ '1.2.3 - 1.2.4', 'v1.2.4-alpha' ] => true,
+        [ '1.2.3 - 1.2.4', 'v1.2.5-alpha' ] => false,
+        [ '~1.2.3',        'v1.2.3-alpha' ] => true,
+        [ '~1.2.3',        'v1.3.0-alpha' ] => false,
+        [ '~1.2',          'v1.2.3-alpha' ] => true,
+        [ '~1.2',          'v2.0.0-alpha' ] => false,
+        [ '~1',            'v1.2.3-alpha' ] => true,
+        [ '~1',            'v2.0.0-alpha' ] => false,
+        [ '1.2.x',         'v1.2.3-alpha' ] => true,
+        [ '1.2.x',         'v1.3.0-alpha' ] => false,
+        [ '1.x',           'v1.2.3-alpha' ] => true,
+        [ '1.x',           'v2.0.0-alpha' ] => false,
+
+        [ '1.2.3',         'v1.2.3' ] => true,
+        [ '>=1.2.3',       'v1.2.3' ] => true,
+        [ '<=1.2.3',       'v1.2.3' ] => true,
+        [ '>= 1.2.3',      'v1.2.3' ] => true,
+        [ '<= 1.2.3',      'v1.2.3' ] => true,
+        [ '1.2.3 - 1.2.4', 'v1.2.3' ] => true,
+        [ '~1.2.3',        'v1.2.3' ] => true,
+        [ '~1.2',          'v1.2.3' ] => true,
+        [ '~1',            'v1.2.3' ] => true,
+        [ '1.2.x',         'v1.2.3' ] => true,
+        [ '1.x',           'v1.2.3' ] => true,
+
+        [ '1.2.3',         'v1.2.4' ] => false,
+        [ '>=1.2.3',       'v1.2.4' ] => true,
+        [ '<=1.2.3',       'v1.2.4' ] => false,
+        [ '>= 1.2.3',      'v1.2.4' ] => true,
+        [ '<= 1.2.3',      'v1.2.4' ] => false,
+        [ '1.2.3 - 1.2.4', 'v1.2.4' ] => true,
+        [ '~1.2.3',        'v1.2.4' ] => true,
+        [ '~1.2',          'v1.2.4' ] => true,
+        [ '~1',            'v1.2.4' ] => true,
+        [ '1.2.x',         'v1.2.4' ] => true,
+        [ '1.x',           'v1.2.4' ] => true,
+      }
+
+      suitability.each do |arguments, expected|
+        range, vstring = arguments
+        actual = SemVer[range] === SemVer.new(vstring)
+        actual.should == expected
+      end
+    end
+  end
+
   describe 'instantiation' do
     it 'should raise an exception when passed an invalid version string' do
       expect { SemVer.new('invalidVersion') }.to raise_exception ArgumentError
@@ -88,25 +181,25 @@
     end
 
     it 'should populate the appropriate fields for a special version string' do
-      version = SemVer.new('3.4.5beta6')
+      version = SemVer.new('3.4.5-beta6')
       version.major.should   == 3
       version.minor.should   == 4
       version.tiny.should    == 5
-      version.special.should == 'beta6'
+      version.special.should == '-beta6'
     end
   end
 
   describe '#matched_by?' do
-    subject { SemVer.new('v1.2.3beta') }
+    subject { SemVer.new('v1.2.3-beta') }
 
     describe 'should match against' do
       describe 'literal version strings' do
-        it { should be_matched_by('1.2.3beta') }
+        it { should be_matched_by('1.2.3-beta') }
 
-        it { should_not be_matched_by('1.2.3alpha') }
-        it { should_not be_matched_by('1.2.4beta') }
-        it { should_not be_matched_by('1.3.3beta') }
-        it { should_not be_matched_by('2.2.3beta') }
+        it { should_not be_matched_by('1.2.3-alpha') }
+        it { should_not be_matched_by('1.2.4-beta') }
+        it { should_not be_matched_by('1.3.3-beta') }
+        it { should_not be_matched_by('2.2.3-beta') }
       end
 
       describe 'partial version strings' do
@@ -155,14 +248,14 @@
       it { should < SemVer.new('1.2.4') }
 
       # Against special versions
-      it { should > SemVer.new('1.2.3beta') }
-      it { should < SemVer.new('1.2.4beta') }
+      it { should > SemVer.new('1.2.3-beta') }
+      it { should < SemVer.new('1.2.4-beta') }
     end
 
-    describe 'on a special version (v1.2.3beta)' do
-      subject { SemVer.new('v1.2.3beta') }
+    describe 'on a special version (v1.2.3-beta)' do
+      subject { SemVer.new('v1.2.3-beta') }
 
-      it { should == SemVer.new('1.2.3beta') }
+      it { should == SemVer.new('1.2.3-beta') }
 
       # Same version, final release
       it { should < SemVer.new('1.2.3') }
@@ -180,8 +273,8 @@
       it { should < SemVer.new('1.2.4') }
 
       # Against special versions
-      it { should > SemVer.new('1.2.3alpha') }
-      it { should < SemVer.new('1.2.3beta2') }
+      it { should > SemVer.new('1.2.3-alpha') }
+      it { should < SemVer.new('1.2.3-beta2') }
     end
   end
 end
diff --git a/spec/unit/util/monkey_patches_spec.rb b/spec/unit/util/monkey_patches_spec.rb
index f3a94d1..5e4e073 100755
--- a/spec/unit/util/monkey_patches_spec.rb
+++ b/spec/unit/util/monkey_patches_spec.rb
@@ -95,3 +95,80 @@ class Puppet::TestYamlNonInitializeClass
     end
   end
 end
+
+describe Range do
+  def do_test( range, other, expected )
+    result = range.intersection(other)
+    result.should == expected
+  end
+
+  it "should return expected ranges for iterable things" do
+    iterable_tests = {
+      1  .. 4   => nil,          # before
+      11 .. 15  => nil,          # after
+      1  .. 6   => 5  ..  6,     # overlap_begin
+      9  .. 15  => 9  ..  10,    # overlap_end
+      1  .. 5   => 5  ..  5,     # overlap_begin_edge
+      10 .. 15  => 10 ..  10,    # overlap_end_edge
+      5  .. 10  => 5  ..  10,    # overlap_all
+      6  .. 9   => 6  ..  9,     # overlap_inner
+
+      1 ... 5   => nil,          # before             (exclusive range)
+      1 ... 7   => 5  ... 7,     # overlap_begin      (exclusive range)
+      1 ... 6   => 5  ... 6,     # overlap_begin_edge (exclusive range)
+      5 ... 11  => 5  ..  10,    # overlap_all        (exclusive range)
+      6 ... 10  => 6  ... 10,    # overlap_inner      (exclusive range)
+    }
+
+    iterable_tests.each do |other, expected|
+      do_test( 5..10, other, expected )
+      do_test( other, 5..10, expected )
+    end
+  end
+
+  it "should return expected ranges for noniterable things" do
+    inclusive_base_case = {
+      1.to_f  .. 4.to_f   => nil,                   # before
+      11.to_f .. 15.to_f  => nil,                   # after
+      1.to_f  .. 6.to_f   => 5.to_f  ..  6.to_f,    # overlap_begin
+      9.to_f  .. 15.to_f  => 9.to_f  ..  10.to_f,   # overlap_end
+      1.to_f  .. 5.to_f   => 5.to_f  ..  5.to_f,    # overlap_begin_edge
+      10.to_f .. 15.to_f  => 10.to_f ..  10.to_f,   # overlap_end_edge
+      5.to_f  .. 10.to_f  => 5.to_f  ..  10.to_f,   # overlap_all
+      6.to_f  .. 9.to_f   => 6.to_f  ..  9.to_f,    # overlap_inner
+
+      1.to_f ... 5.to_f   => nil,                   # before             (exclusive range)
+      1.to_f ... 7.to_f   => 5.to_f  ... 7.to_f,    # overlap_begin      (exclusive range)
+      1.to_f ... 6.to_f   => 5.to_f  ... 6.to_f,    # overlap_begin_edge (exclusive range)
+      5.to_f ... 11.to_f  => 5.to_f  ..  10.to_f,   # overlap_all        (exclusive range)
+      6.to_f ... 10.to_f  => 6.to_f  ... 10.to_f,   # overlap_inner      (exclusive range)
+    }
+
+    inclusive_base_case.each do |other, expected|
+      do_test( 5.to_f..10.to_f, other, expected )
+      do_test( other, 5.to_f..10.to_f, expected )
+    end
+
+    exclusive_base_case = {
+      1.to_f  .. 4.to_f   => nil,                   # before
+      11.to_f .. 15.to_f  => nil,                   # after
+      1.to_f  .. 6.to_f   => 5.to_f  ..  6.to_f,    # overlap_begin
+      9.to_f  .. 15.to_f  => 9.to_f  ... 10.to_f,   # overlap_end
+      1.to_f  .. 5.to_f   => 5.to_f  ..  5.to_f,    # overlap_begin_edge
+      10.to_f .. 15.to_f  => nil,                   # overlap_end_edge
+      5.to_f  .. 10.to_f  => 5.to_f  ... 10.to_f,   # overlap_all
+      6.to_f  .. 9.to_f   => 6.to_f  ..  9.to_f,    # overlap_inner
+
+      1.to_f ... 5.to_f   => nil,                   # before             (exclusive range)
+      1.to_f ... 7.to_f   => 5.to_f  ... 7.to_f,    # overlap_begin      (exclusive range)
+      1.to_f ... 6.to_f   => 5.to_f  ... 6.to_f,    # overlap_begin_edge (exclusive range)
+      5.to_f ... 11.to_f  => 5.to_f  ... 10.to_f,   # overlap_all        (exclusive range)
+      6.to_f ... 10.to_f  => 6.to_f  ... 10.to_f,   # overlap_inner      (exclusive range)
+    }
+
+    exclusive_base_case.each do |other, expected|
+      do_test( 5.to_f...10.to_f, other, expected )
+      do_test( other, 5.to_f...10.to_f, expected )
+    end
+  end
+end

    

--
You received this message because you are subscribed to the Google Groups "Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to [email protected].
For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to