diff --git a/README.markdown b/README.markdown index 29ff41430..28289ee10 100644 --- a/README.markdown +++ b/README.markdown @@ -1,115 +1,115 @@ -Puppet Interfaces +Puppet Strings ================= A set of executables that provide complete CLI access to Puppet's -core data types. They also provide Interface classes for +core data types. They also provide String classes for each of the core data types, which are extensible via plugins. For instance, you can create a new action for catalogs at -lib/puppet/interface/catalog/$action.rb. +lib/puppet/string/catalog/$action.rb. This is a Puppet module and should work fine if you install it in Puppet's module path. **Note that this only works with Puppet 2.6.next (and thus will work with 2.6.5), because there is otherwise a bug in finding Puppet applications. You also have to either install the lib files into your Puppet libdir, or you need to add this lib directory to your RUBYLIB.** This is meant to be tested and iterated upon, with the plan that it will be merged into Puppet core once we're satisfied with it. Usage ----- The general usage is: - $ puppet + $ puppet So, e.g.: $ puppet facts find myhost.domain.com $ puppet node destroy myhost You can use it to list all known data types and the available terminus classes: - $ puppet interface list + $ puppet string list catalog : active_record, compiler, queue, rest, yaml certificate : ca, file, rest certificate_request : ca, file, rest certificate_revocation_list : ca, file, rest file_bucket_file : file, rest inventory : yaml key : ca, file node : active_record, exec, ldap, memory, plain, rest, yaml report : processor, rest, yaml resource : ral, rest resource_type : parser, rest status : local, rest But most interestingly, you can use it for two main purposes: * As a client for any Puppet REST server, such as catalogs, facts, reports, etc. * As a local CLI for any local Puppet data A simple case is looking at the local facts: $ puppet facts find localhost If you're on the server, you can look in that server's fact collection: $ puppet facts --mode master --vardir /tmp/foo --terminus yaml find localhost Note that we're setting both the vardir and the 'mode', which switches from the default 'agent' mode to server mode (requires a patch in my branch). If you'd prefer the data be outputted in json instead of yaml, well, you can do that, too: $ puppet find --mode master facts --vardir /tmp/foo --terminus yaml --format pson localhost To test using it as an endpoint for compiling and retrieving catalogs from a remote server, (from my commit), try this: # Terminal 1 $ sbin/puppetmasterd --trace --confdir /tmp/foo --vardir /tmp/foo --debug --manifest ~/bin/test.pp --certname localhost --no-daemonize # Terminal 2 $ sbin/puppetd --trace --debug --confdir /tmp/foo --vardir /tmp/foo --certname localhost --server localhost --test --report # Terminal 3, actual testing $ puppet catalog find localhost --certname localhost --server localhost --mode master --confdir /tmp/foo --vardir /tmp/foo --trace --terminus rest This compiles a test catalog (assuming that ~/bin/test.pp exists) and returns it. With the right auth setup, you can also get facts: $ puppet facts find localhost --certname localhost --server localhost --mode master --confdir /tmp/foo --vardir /tmp/foo --trace --terminus rest Or use IRB to do the same thing: $ irb - >> require 'puppet/interface' + >> require 'puppet/string' => true - >> interface = Puppet::Interface[:facts, '1.0.0'] - => # - >> facts = interface.find("myhost") + >> string = Puppet::String[:facts, '1.0.0'] + => # + >> facts = string.find("myhost") Like I said, a prototype, but I'd love it if people would play it with some and make some recommendations. Extending --------- -Like most parts of Puppet, these are easy to extend. Just drop a new action into a given interface's directory. E.g.: +Like most parts of Puppet, these are easy to extend. Just drop a new action into a given string's directory. E.g.: - $ cat lib/puppet/interface/catalog/select.rb + $ cat lib/puppet/string/catalog/select.rb # Select and show a list of resources of a given type. - Puppet::Interface.define(:catalog, '1.0.0') do + Puppet::String.define(:catalog, '1.0.0') do action :select do invoke do |host,type| catalog = Puppet::Resource::Catalog.indirection.find(host) catalog.resources.reject { |res| res.type != type }.each { |res| puts res } end end end $ puppet catalog select localhost Class Class[main] Class[Settings] $ Notice that this gets loaded automatically when you try to use it. So, if you have a simple command you've written, such as for cleaning up nodes or diffing catalogs, you an port it to this framework and it should fit cleanly. -Also note that interfaces are versioned. These version numbers are interpreted according to Semantic Versioning (http://semver.org). +Also note that strings are versioned. These version numbers are interpreted according to Semantic Versioning (http://semver.org). diff --git a/lib/puppet/application/string_base.rb b/lib/puppet/application/string_base.rb index 5b701597d..bc627adde 100644 --- a/lib/puppet/application/string_base.rb +++ b/lib/puppet/application/string_base.rb @@ -1,97 +1,97 @@ require 'puppet/application' require 'puppet/string' class Puppet::Application::StringBase < Puppet::Application should_parse_config run_mode :agent def preinit super trap(:INT) do $stderr.puts "Cancelling String" exit(0) end end option("--debug", "-d") do |arg| Puppet::Util::Log.level = :debug end option("--verbose", "-v") do Puppet::Util::Log.level = :info end option("--format FORMAT") do |arg| @format = arg.to_sym end option("--mode RUNMODE", "-r") do |arg| raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg) self.class.run_mode(arg.to_sym) set_run_mode self.class.run_mode end attr_accessor :string, :type, :verb, :arguments, :format attr_writer :exit_code # This allows you to set the exit code if you don't want to just exit # immediately but you need to indicate a failure. def exit_code @exit_code || 0 end def main # Call the method associated with the provided action (e.g., 'find'). if result = string.send(verb, *arguments) puts render(result) end exit(exit_code) end # Override this if you need custom rendering. def render(result) render_method = Puppet::Network::FormatHandler.format(format).render_method if render_method == "to_pson" jj result exit(0) else result.send(render_method) end end def setup Puppet::Util::Log.newdestination :console @verb = command_line.args.shift @arguments = command_line.args @arguments ||= [] @arguments = Array(@arguments) @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym # TODO: These should be configurable versions. - unless Puppet::String.string?(@type, :latest) + unless Puppet::String.string?(@type, :current) raise "Could not find any version of string '#{@type}'" end - @string = Puppet::String[@type, :latest] + @string = Puppet::String[@type, :current] @format ||= @string.default_format # We copy all of the app options to the string. # This allows each action to read in the options. @string.options = options validate end def validate unless verb raise "You must specify #{string.actions.join(", ")} as a verb; 'save' probably does not work right now" end unless string.action?(verb) raise "Command '#{verb}' not found for #{type}" end end end diff --git a/lib/puppet/string.rb b/lib/puppet/string.rb index b5f7b9048..9a223a40c 100644 --- a/lib/puppet/string.rb +++ b/lib/puppet/string.rb @@ -1,98 +1,100 @@ require 'puppet' require 'puppet/util/autoload' class Puppet::String require 'puppet/string/action_manager' require 'puppet/string/string_collection' include Puppet::String::ActionManager extend Puppet::String::ActionManager include Puppet::Util class << self # This is just so we can search for actions. We only use its # list of directories to search. # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb def autoloader @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/string") end def strings Puppet::String::StringCollection.strings end def string?(name, version) Puppet::String::StringCollection.string?(name, version) end def register(instance) Puppet::String::StringCollection.register(instance) end def define(name, version, &block) if string?(name, version) string = Puppet::String::StringCollection[name, version] else string = self.new(name, version) Puppet::String::StringCollection.register(string) string.load_actions end string.instance_eval(&block) if block_given? return string end alias :[] :define end attr_accessor :default_format def set_default_format(format) self.default_format = format.to_sym end attr_accessor :type, :verb, :version, :arguments, :options attr_reader :name def initialize(name, version, &block) unless Puppet::String::StringCollection.validate_version(version) raise ArgumentError, "Cannot create string with invalid version number '#{version}'!" end @name = Puppet::String::StringCollection.underscorize(name) @version = version @default_format = :pson instance_eval(&block) if block_given? end # Try to find actions defined in other files. def load_actions - path = "puppet/string/v#{version}/#{name}" + path = "puppet/string/#{name}" loaded = [] - Puppet::String.autoloader.search_directories.each do |dir| - fdir = ::File.join(dir, path) - next unless FileTest.directory?(fdir) - - Dir.chdir(fdir) do - Dir.glob("*.rb").each do |file| - aname = file.sub(/\.rb/, '') - if loaded.include?(aname) - Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - next + [path, "#{name}@#{version}/#{path}"].each do |path| + Puppet::String.autoloader.search_directories.each do |dir| + fdir = ::File.join(dir, path) + next unless FileTest.directory?(fdir) + + Dir.chdir(fdir) do + Dir.glob("*.rb").each do |file| + aname = file.sub(/\.rb/, '') + if loaded.include?(aname) + Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + next + end + loaded << aname + Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + require "#{path}/#{aname}" end - loaded << aname - Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - require "#{path}/#{aname}" end end end end def to_s "Puppet::String[#{name.inspect}, #{version.inspect}]" end end diff --git a/lib/puppet/string/v0.0.1/catalog.rb b/lib/puppet/string/catalog.rb similarity index 100% rename from lib/puppet/string/v0.0.1/catalog.rb rename to lib/puppet/string/catalog.rb diff --git a/lib/puppet/string/v0.0.1/catalog/select.rb b/lib/puppet/string/catalog/select.rb similarity index 100% rename from lib/puppet/string/v0.0.1/catalog/select.rb rename to lib/puppet/string/catalog/select.rb diff --git a/lib/puppet/string/v0.0.1/certificate.rb b/lib/puppet/string/certificate.rb similarity index 100% rename from lib/puppet/string/v0.0.1/certificate.rb rename to lib/puppet/string/certificate.rb diff --git a/lib/puppet/string/v0.0.1/certificate_request.rb b/lib/puppet/string/certificate_request.rb similarity index 100% rename from lib/puppet/string/v0.0.1/certificate_request.rb rename to lib/puppet/string/certificate_request.rb diff --git a/lib/puppet/string/v0.0.1/certificate_revocation_list.rb b/lib/puppet/string/certificate_revocation_list.rb similarity index 100% rename from lib/puppet/string/v0.0.1/certificate_revocation_list.rb rename to lib/puppet/string/certificate_revocation_list.rb diff --git a/lib/puppet/string/v0.0.1/config.rb b/lib/puppet/string/config.rb similarity index 100% rename from lib/puppet/string/v0.0.1/config.rb rename to lib/puppet/string/config.rb diff --git a/lib/puppet/string/v0.0.1/configurer.rb b/lib/puppet/string/configurer.rb similarity index 100% rename from lib/puppet/string/v0.0.1/configurer.rb rename to lib/puppet/string/configurer.rb diff --git a/lib/puppet/string/v0.0.1/facts.rb b/lib/puppet/string/facts.rb similarity index 100% rename from lib/puppet/string/v0.0.1/facts.rb rename to lib/puppet/string/facts.rb diff --git a/lib/puppet/string/v0.0.1/file.rb b/lib/puppet/string/file.rb similarity index 100% rename from lib/puppet/string/v0.0.1/file.rb rename to lib/puppet/string/file.rb diff --git a/lib/puppet/string/v0.0.1/key.rb b/lib/puppet/string/key.rb similarity index 100% rename from lib/puppet/string/v0.0.1/key.rb rename to lib/puppet/string/key.rb diff --git a/lib/puppet/string/v0.0.1/node.rb b/lib/puppet/string/node.rb similarity index 100% rename from lib/puppet/string/v0.0.1/node.rb rename to lib/puppet/string/node.rb diff --git a/lib/puppet/string/v0.0.1/report.rb b/lib/puppet/string/report.rb similarity index 100% rename from lib/puppet/string/v0.0.1/report.rb rename to lib/puppet/string/report.rb diff --git a/lib/puppet/string/v0.0.1/resource.rb b/lib/puppet/string/resource.rb similarity index 100% rename from lib/puppet/string/v0.0.1/resource.rb rename to lib/puppet/string/resource.rb diff --git a/lib/puppet/string/v0.0.1/resource_type.rb b/lib/puppet/string/resource_type.rb similarity index 100% rename from lib/puppet/string/v0.0.1/resource_type.rb rename to lib/puppet/string/resource_type.rb diff --git a/lib/puppet/string/v0.0.1/status.rb b/lib/puppet/string/status.rb similarity index 100% rename from lib/puppet/string/v0.0.1/status.rb rename to lib/puppet/string/status.rb diff --git a/lib/puppet/string/string_collection.rb b/lib/puppet/string/string_collection.rb index e9cba7f55..45a192703 100644 --- a/lib/puppet/string/string_collection.rb +++ b/lib/puppet/string/string_collection.rb @@ -1,98 +1,76 @@ require 'puppet/string' module Puppet::String::StringCollection SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ @strings = Hash.new { |hash, key| hash[key] = {} } def self.strings unless @loaded @loaded = true $LOAD_PATH.each do |dir| next unless FileTest.directory?(dir) Dir.chdir(dir) do Dir.glob("puppet/string/v*/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| iname = file.sub(/\.rb/, '') begin require iname rescue Exception => detail puts detail.backtrace if Puppet[:trace] raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" end end end end end return @strings.keys end - def self.versions(name) - versions = [] - $LOAD_PATH.each do |dir| - next unless FileTest.directory?(dir) - v_dir = File.join dir, %w[puppet string v*] - Dir.glob(File.join v_dir, "#{name}{.rb,/*.rb}").each do |f| - v = f.sub(%r[.*/v([^/]+?)/#{name}(?:(?:/[^/]+)?.rb)$], '\1') - if validate_version(v) - versions << v - else - warn "'#{v}' (#{f}) is not a valid version string; skipping" - end - end - end - return versions.uniq.sort { |a, b| compare_versions(a, b) } - end - def self.validate_version(version) !!(SEMVER_VERSION =~ version.to_s) end - def self.compare_versions(a, b) - a, b = [a, b].map do |x| - parts = SEMVER_VERSION.match(x).to_a[1..4] - parts[0..2] = parts[0..2].map { |e| e.to_i } - parts - end - - cmp = a[0..2] <=> b[0..2] - if cmp == 0 - cmp = a[3] <=> b[3] - cmp = +1 if a[3].empty? && !b[3].empty? - cmp = -1 if b[3].empty? && !a[3].empty? - end - cmp - end - def self.[](name, version) - version = versions(name).last if version == :latest - unless version.nil? - @strings[underscorize(name)][version] if string?(name, version) - end + @strings[underscorize(name)][version] if string?(name, version) end def self.string?(name, version) - version = versions(name).last if version == :latest - return false if version.nil? - name = underscorize(name) + cache = @strings[name] + return true if cache.has_key?(version) - unless @strings.has_key?(name) && @strings[name].has_key?(version) - require "puppet/string/v#{version}/#{name}" + loaded = cache.keys + + files = ["puppet/string/#{name}"] + unless version == :current + files << "#{name}@#{version}/puppet/string/#{name}" end - return @strings.has_key?(name) && @strings[name].has_key?(version) - rescue LoadError + + files.each do |file| + begin + require file + if version == :current || !file.include?('@') + loaded = (cache.keys - loaded).first + cache[:current] = cache[loaded] unless loaded.nil? + end + return true if cache.has_key?(version) + rescue LoadError + # pass + end + end + return false end def self.register(string) @strings[underscorize(string.name)][string.version] = string end def self.underscorize(name) unless name.to_s =~ /^[-_a-z]+$/i then raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid string name" end name.to_s.downcase.split(/[-_]/).join('_').to_sym end end diff --git a/spec/unit/application/string_base_spec.rb b/spec/unit/application/string_base_spec.rb index bc563e11d..86f9c09aa 100755 --- a/spec/unit/application/string_base_spec.rb +++ b/spec/unit/application/string_base_spec.rb @@ -1,73 +1,74 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') require 'puppet/application/string_base' +require 'tmpdir' + +class Puppet::Application::StringBase::Basetest < Puppet::Application::StringBase +end describe Puppet::Application::StringBase do before :all do @dir = Dir.mktmpdir $LOAD_PATH.push(@dir) - FileUtils.mkdir_p(File.join @dir, 'puppet', 'string', 'v0.0.1') - FileUtils.touch(File.join @dir, 'puppet', 'string', 'v0.0.1', 'basetest.rb') + FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') + File.open(File.join(@dir, 'puppet', 'string', 'basetest.rb'), 'w') do |f| + f.puts "Puppet::String.define(:basetest, '0.0.1')" + end end after :all do FileUtils.remove_entry_secure @dir $LOAD_PATH.pop end - base_string = Puppet::String.define(:basetest, '0.0.1') - class Puppet::Application::StringBase::Basetest < Puppet::Application::StringBase - end - before do @app = Puppet::Application::StringBase::Basetest.new - @app.stubs(:string).returns base_string @app.stubs(:exit) @app.stubs(:puts) Puppet::Util::Log.stubs(:newdestination) end describe "when calling main" do before do @app.verb = :find @app.arguments = ["myname", "myarg"] @app.string.stubs(:find) end it "should send the specified verb and name to the string" do @app.string.expects(:find).with("myname", "myarg") @app.main end it "should use its render method to render any result" it "should exit with the current exit code" end describe "during setup" do before do @app.command_line.stubs(:args).returns(["find", "myname", "myarg"]) @app.stubs(:validate) end it "should set the verb from the command line arguments" do @app.setup @app.verb.should == "find" end it "should make sure arguments are an array" do @app.command_line.stubs(:args).returns(["find", "myname", "myarg"]) @app.setup @app.arguments.should == ["myname", "myarg"] end it "should set the options on the string" do @app.options[:foo] = "bar" @app.setup @app.string.options.should == @app.options end end end diff --git a/spec/unit/string/string_collection_spec.rb b/spec/unit/string/string_collection_spec.rb index 46c431f75..63ddf7c5e 100755 --- a/spec/unit/string/string_collection_spec.rb +++ b/spec/unit/string/string_collection_spec.rb @@ -1,249 +1,184 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') require 'tmpdir' describe Puppet::String::StringCollection do before :all do - @strings = subject.instance_variable_get("@strings").dup + @strings = subject.instance_variable_get("@strings") + @strings_backup = @strings.dup end - before :each do - subject.instance_variable_get("@strings").clear - end + before { @strings.clear } after :all do - subject.instance_variable_set("@strings", @strings) + subject.instance_variable_set("@strings", @strings_backup) end describe "::strings" do end - describe "::versions" do - before :each do - @dir = Dir.mktmpdir - @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') - $LOAD_PATH.push(@dir) - end - - after :each do - FileUtils.remove_entry_secure @dir - $LOAD_PATH.pop - end - - it "should return an empty array when no versions are loadable" do - subject.versions(:fozzie).should == [] - end - - it "should return versions loadable as puppet/string/v{version}/{name}" do - FileUtils.mkdir_p(File.join @lib, 'v1.0.0') - FileUtils.touch(File.join @lib, 'v1.0.0', 'fozzie.rb') - subject.versions(:fozzie).should == ['1.0.0'] - end - - it "should an ordered list of all versions loadable as puppet/string/v{version}/{name}" do - %w[ 1.2.1rc2 1.2.1beta1 1.2.1rc1 1.2.1 1.2.2 ].each do |version| - FileUtils.mkdir_p(File.join @lib, "v#{version}") - FileUtils.touch(File.join @lib, "v#{version}", 'fozzie.rb') - end - subject.versions(:fozzie).should == %w[ 1.2.1beta1 1.2.1rc1 1.2.1rc2 1.2.1 1.2.2 ] - end - - it "should not return a version for an empty puppet/string/v{version}/{name}" do - FileUtils.mkdir_p(File.join @lib, 'v1.0.0', 'fozzie') - subject.versions(:fozzie).should == [] - end - - it "should an ordered list of all versions loadable as puppet/string/v{version}/{name}/*.rb" do - %w[ 1.2.1rc2 1.2.1beta1 1.2.1rc1 1.2.1 1.2.2 ].each do |version| - FileUtils.mkdir_p(File.join @lib, "v#{version}", "fozzie") - FileUtils.touch(File.join @lib, "v#{version}", 'fozzie', 'action.rb') - end - subject.versions(:fozzie).should == %w[ 1.2.1beta1 1.2.1rc1 1.2.1rc2 1.2.1 1.2.2 ] - end - end - describe "::validate_version" do it 'should permit three number versions' do subject.validate_version('10.10.10').should == true end it 'should permit versions with appended descriptions' do subject.validate_version('10.10.10beta').should == true end it 'should not permit versions with more than three numbers' do subject.validate_version('1.2.3.4').should == false end it 'should not permit versions with only two numbers' do subject.validate_version('10.10').should == false end it 'should not permit versions with only one number' do subject.validate_version('123').should == false end it 'should not permit versions with text in any position but at the end' do subject.validate_version('v1.1.1').should == false end end - describe "::compare_versions" do - # (a <=> b) should be: - # -1 if a < b - # 0 if a == b - # 1 if a > b - it 'should sort major version numbers numerically' do - subject.compare_versions('1.0.0', '2.0.0').should == -1 - subject.compare_versions('2.0.0', '1.1.1').should == 1 - subject.compare_versions('2.0.0', '10.0.0').should == -1 - end - - it 'should sort minor version numbers numerically' do - subject.compare_versions('0.1.0', '0.2.0').should == -1 - subject.compare_versions('0.2.0', '0.1.1').should == 1 - subject.compare_versions('0.2.0', '0.10.0').should == -1 - end - - it 'should sort tiny version numbers numerically' do - subject.compare_versions('0.0.1', '0.0.2').should == -1 - subject.compare_versions('0.0.2', '0.0.1').should == 1 - subject.compare_versions('0.0.2', '0.0.10').should == -1 - end - - it 'should sort major version before minor version' do - subject.compare_versions('1.1.0', '1.2.0').should == -1 - subject.compare_versions('1.2.0', '1.1.1').should == 1 - subject.compare_versions('1.2.0', '1.10.0').should == -1 - - subject.compare_versions('1.1.0', '2.2.0').should == -1 - subject.compare_versions('2.2.0', '1.1.1').should == 1 - subject.compare_versions('2.2.0', '1.10.0').should == 1 - end - - it 'should sort minor version before tiny version' do - subject.compare_versions('0.1.1', '0.1.2').should == -1 - subject.compare_versions('0.1.2', '0.1.1').should == 1 - subject.compare_versions('0.1.2', '0.1.10').should == -1 - - subject.compare_versions('0.1.1', '0.2.2').should == -1 - subject.compare_versions('0.2.2', '0.1.1').should == 1 - subject.compare_versions('0.2.2', '0.1.10').should == 1 - end - - it 'should sort appended strings asciibetically' do - subject.compare_versions('0.0.0a', '0.0.0b').should == -1 - subject.compare_versions('0.0.0beta1', '0.0.0beta2').should == -1 - subject.compare_versions('0.0.0beta1', '0.0.0rc1').should == -1 - subject.compare_versions('0.0.0beta1', '0.0.0alpha1').should == 1 - subject.compare_versions('0.0.0beta1', '0.0.0beta1').should == 0 - end - - it "should sort appended strings before 'whole' versions" do - subject.compare_versions('0.0.1a', '0.0.1').should == -1 - subject.compare_versions('0.0.1', '0.0.1beta').should == 1 - end - end - describe "::[]" do before :each do subject.instance_variable_get("@strings")[:foo]['0.0.1'] = 10 end before :each do @dir = Dir.mktmpdir @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') $LOAD_PATH.push(@dir) end after :each do FileUtils.remove_entry_secure @dir $LOAD_PATH.pop end it "should return the string with the given name" do subject["foo", '0.0.1'].should == 10 end it "should attempt to load the string if it isn't found" do - subject.expects(:require).with('puppet/string/v0.0.1/bar') + subject.expects(:require).with('puppet/string/bar') + subject.expects(:require).with('bar@0.0.1/puppet/string/bar') subject["bar", '0.0.1'] end - it "should attempt to load the string with the greatest version for specified version :latest" do - %w[ 1.2.1 1.2.2 ].each do |version| - FileUtils.mkdir_p(File.join @lib, "v#{version}") - FileUtils.touch(File.join @lib, "v#{version}", 'fozzie.rb') - end - subject.expects(:require).with('puppet/string/v1.2.2/fozzie') - subject['fozzie', :latest] + it "should attempt to load the default string for the specified version :current" do + subject.expects(:require).never # except... + subject.expects(:require).with('puppet/string/fozzie') + subject['fozzie', :current] end end describe "::string?" do before :each do subject.instance_variable_get("@strings")[:foo]['0.0.1'] = 10 end it "should return true if the string specified is registered" do subject.string?("foo", '0.0.1').should == true end it "should attempt to require the string if it is not registered" do - subject.expects(:require).with('puppet/string/v0.0.1/bar') - subject.string?("bar", '0.0.1') + subject.expects(:require).with do |file| + @strings[:bar]['0.0.1'] = true + file == 'puppet/string/bar' + end + subject.string?("bar", '0.0.1').should == true end it "should return true if requiring the string registered it" do subject.stubs(:require).with do subject.instance_variable_get("@strings")[:bar]['0.0.1'] = 20 end + end + + it "should require the string by version if the 'current' version isn't it" do + subject.expects(:require).with('puppet/string/bar').raises(LoadError) + subject.expects(:require).with do |file| + @strings[:bar]['0.0.1'] = true + file == 'bar@0.0.1/puppet/string/bar' + end subject.string?("bar", '0.0.1').should == true end it "should return false if the string is not registered" do subject.stubs(:require).returns(true) subject.string?("bar", '0.0.1').should == false end it "should return false if there is a LoadError requiring the string" do subject.stubs(:require).raises(LoadError) subject.string?("bar", '0.0.1').should == false end + + it "should register the version loaded by `:current` as `:current`" do + subject.expects(:require).with do |file| + @strings[:huzzah]['2.0.1'] = :huzzah_string + file == 'puppet/string/huzzah' + end + subject.string?("huzzah", :current) + @strings[:huzzah][:current].should == :huzzah_string + end + + it "should register the version loaded from `puppet/string/{name}` as `:current`" do + subject.expects(:require).with do |file| + @strings[:huzzah]['2.0.1'] = :huzzah_string + file == 'puppet/string/huzzah' + end + subject.string?("huzzah", '2.0.1') + @strings[:huzzah][:current].should == :huzzah_string + end + + it "should not register the version loaded from `{name}@{version}` as `:current`" do + subject.expects(:require).with('puppet/string/huzzah').raises(LoadError) + subject.expects(:require).with do |file| + @strings[:huzzah]['0.0.1'] = true + file == 'huzzah@0.0.1/puppet/string/huzzah' + end + subject.string?("huzzah", '0.0.1') + @strings[:huzzah].should_not have_key(:current) + end end describe "::register" do it "should store the string by name" do string = Puppet::String.new(:my_string, '0.0.1') subject.register(string) subject.instance_variable_get("@strings").should == {:my_string => {'0.0.1' => string}} end end describe "::underscorize" do faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] valid = { "Foo" => :foo, :Foo => :foo, "foo_bar" => :foo_bar, :foo_bar => :foo_bar, "foo-bar" => :foo_bar, :"foo-bar" => :foo_bar, } valid.each do |input, expect| it "should map #{input.inspect} to #{expect.inspect}" do result = subject.underscorize(input) result.should == expect end end faulty.each do |input| it "should fail when presented with #{input.inspect} (#{input.class})" do expect { subject.underscorize(input) }. should raise_error ArgumentError, /not a valid string name/ end end end end diff --git a/spec/unit/string_spec.rb b/spec/unit/string_spec.rb index 73d1f2177..64d4f12f8 100755 --- a/spec/unit/string_spec.rb +++ b/spec/unit/string_spec.rb @@ -1,83 +1,84 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb') describe Puppet::String do before :all do @strings = Puppet::String::StringCollection.instance_variable_get("@strings").dup end before :each do Puppet::String::StringCollection.instance_variable_get("@strings").clear end after :all do Puppet::String::StringCollection.instance_variable_set("@strings", @strings) end describe "#define" do it "should register the string" do string = Puppet::String.define(:string_test_register, '0.0.1') string.should == Puppet::String[:string_test_register, '0.0.1'] end it "should load actions" do Puppet::String.any_instance.expects(:load_actions) Puppet::String.define(:string_test_load_actions, '0.0.1') end it "should require a version number" do proc { Puppet::String.define(:no_version) }.should raise_error(ArgumentError) end end describe "#initialize" do it "should require a version number" do proc { Puppet::String.new(:no_version) }.should raise_error(ArgumentError) end it "should require a valid version number" do proc { Puppet::String.new(:bad_version, 'Rasins') }.should raise_error(ArgumentError) end it "should instance-eval any provided block" do face = Puppet::String.new(:string_test_block,'0.0.1') do action(:something) do invoke { "foo" } end end face.something.should == "foo" end end it "should have a name" do Puppet::String.new(:me,'0.0.1').name.should == :me end it "should stringify with its own name" do Puppet::String.new(:me,'0.0.1').to_s.should =~ /\bme\b/ end it "should allow overriding of the default format" do face = Puppet::String.new(:me,'0.0.1') face.set_default_format :foo face.default_format.should == :foo end it "should default to :pson for its format" do Puppet::String.new(:me, '0.0.1').default_format.should == :pson end # Why? it "should create a class-level autoloader" do Puppet::String.autoloader.should be_instance_of(Puppet::Util::Autoload) end it "should try to require strings that are not known" do - Puppet::String::StringCollection.expects(:require).with "puppet/string/v0.0.1/foo" + Puppet::String::StringCollection.expects(:require).with "puppet/string/foo" + Puppet::String::StringCollection.expects(:require).with "foo@0.0.1/puppet/string/foo" Puppet::String[:foo, '0.0.1'] end it "should be able to load all actions in all search paths" end