Page MenuHomePhorge

No OneTemporary

diff --git a/acceptance/tests/environment/use_enc_environment_for_files.rb b/acceptance/tests/environment/use_enc_environment_for_files.rb
index 5f3dc6dd0..357d1a8ff 100644
--- a/acceptance/tests/environment/use_enc_environment_for_files.rb
+++ b/acceptance/tests/environment/use_enc_environment_for_files.rb
@@ -1,60 +1,63 @@
test_name "Agent should use environment given by ENC for fetching remote files"
testdir = master.tmpdir('respect_enc_test')
create_remote_file master, "#{testdir}/enc.rb", <<END
#!#{master['puppetbindir']}/ruby
puts <<YAML
parameters:
environment: special
YAML
END
on master, "chmod 755 #{testdir}/enc.rb"
on master, "mkdir -p #{testdir}/modules"
# Create a plugin file on the master
on master, "mkdir -p #{testdir}/special/amod/files"
create_remote_file(master, "#{testdir}/special/amod/files/testy", "special_environment")
on master, "chown -R #{master['user']}:#{master['group']} #{testdir}"
on master, "chmod -R g+rwX #{testdir}"
master_opts = {
'master' => {
'node_terminus' => 'exec',
'external_nodes' => "#{testdir}/enc.rb",
'filetimeout' => 1
},
'special' => {
'modulepath' => "#{testdir}/special",
'manifest' => "#{testdir}/different.pp"
}
}
+if master.is_pe?
+ master_opts['special']['modulepath'] << ":#{master['sitemoduledir']}"
+end
with_puppet_running_on master, master_opts, testdir do
agents.each do |agent|
atmp = agent.tmpdir('respect_enc_test')
logger.debug "agent: #{agent} \tagent.tmpdir => #{atmp}"
create_remote_file master, "#{testdir}/different.pp", <<END
file { "#{atmp}/special_testy":
source => "puppet:///modules/amod/testy",
}
notify { "mytemp is ${::mytemp}": }
END
on master, "chmod 644 #{testdir}/different.pp"
sleep 2 # Make sure the master has time to reload the file
run_agent_on(agent, "--no-daemonize --onetime --server #{master} --verbose --trace")
on agent, "cat #{atmp}/special_testy" do |result|
assert_match(/special_environment/,
result.stdout,
"The file from environment 'special' was not found")
end
on agent, "rm -rf #{atmp}"
end
end
diff --git a/acceptance/tests/environment/use_enc_environment_for_pluginsync.rb b/acceptance/tests/environment/use_enc_environment_for_pluginsync.rb
index e1e54f8c9..05902a37e 100644
--- a/acceptance/tests/environment/use_enc_environment_for_pluginsync.rb
+++ b/acceptance/tests/environment/use_enc_environment_for_pluginsync.rb
@@ -1,40 +1,43 @@
test_name "Agent should use environment given by ENC for pluginsync"
testdir = master.tmpdir('respect_enc_test')
create_remote_file master, "#{testdir}/enc.rb", <<END
#!#{master['puppetbindir']}/ruby
puts <<YAML
parameters:
environment: special
YAML
END
on master, "chmod 755 #{testdir}/enc.rb"
master_opts = {
'master' => {
'node_terminus' => 'exec',
'external_nodes' => "#{testdir}/enc.rb"
},
'special' => {
'modulepath' => "#{testdir}/special"
}
}
+if master.is_pe?
+ master_opts['special']['modulepath'] << ":#{master['sitemoduledir']}"
+end
on master, "mkdir -p #{testdir}/modules"
# Create a plugin file on the master
on master, "mkdir -p #{testdir}/special/amod/lib/puppet"
create_remote_file(master, "#{testdir}/special/amod/lib/puppet/foo.rb", "#special_version")
on master, "chown -R #{master['user']}:#{master['group']} #{testdir}"
on master, "chmod -R g+rwX #{testdir}"
with_puppet_running_on master, master_opts, testdir do
agents.each do |agent|
run_agent_on(agent, "--no-daemonize --onetime --server #{master}")
on agent, "cat #{agent['puppetvardir']}/lib/puppet/foo.rb"
assert_match(/#special_version/, stdout, "The plugin from environment 'special' was not synced")
on agent, "rm -rf #{agent['puppetvardir']}/lib"
end
end
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index b7e324687..ed2c66bba 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -1,130 +1,129 @@
# the parent class for all of our syntactical objects
require 'puppet'
require 'puppet/util/autoload'
# The base class for all of the objects that make up the parse trees.
# Handles things like file name, line #, and also does the initialization
# for all of the parameters of all of the child objects.
class Puppet::Parser::AST
# Do this so I don't have to type the full path in all of the subclasses
AST = Puppet::Parser::AST
include Puppet::Util::Errors
include Puppet::Util::MethodHelper
include Puppet::Util::Docs
attr_accessor :parent, :scope, :file, :line, :pos
def inspect
"( #{self.class} #{self.to_s} #{@children.inspect} )"
end
# don't fetch lexer comment by default
def use_docs
self.class.use_docs
end
# allow our subclass to specify they want documentation
class << self
attr_accessor :use_docs
def associates_doc
self.use_docs = true
end
end
# Evaluate the current object. Just a stub method, since the subclass
# should override this method.
def evaluate(*options)
end
# Throw a parse error.
def parsefail(message)
self.fail(Puppet::ParseError, message)
end
# Wrap a statemp in a reusable way so we always throw a parse error.
def parsewrap
exceptwrap :type => Puppet::ParseError do
yield
end
end
# The version of the evaluate method that should be called, because it
# correctly handles errors. It is critical to use this method because
# it can enable you to catch the error where it happens, rather than
# much higher up the stack.
def safeevaluate(*options)
# We duplicate code here, rather than using exceptwrap, because this
# is called so many times during parsing.
begin
return self.evaluate(*options)
rescue Puppet::Error => detail
raise adderrorcontext(detail)
rescue => detail
error = Puppet::ParseError.new(detail.to_s, nil, nil, detail)
# We can't use self.fail here because it always expects strings,
# not exceptions.
raise adderrorcontext(error, detail)
end
end
# Initialize the object. Requires a hash as the argument, and
# takes each of the parameters of the hash and calls the settor
# method for them. This is probably pretty inefficient and should
# likely be changed at some point.
def initialize(args)
set_options(args)
end
# evaluate ourselves, and match
def evaluate_match(value, scope)
obj = self.safeevaluate(scope)
obj = obj.downcase if obj.respond_to?(:downcase)
value = value.downcase if value.respond_to?(:downcase)
obj = Puppet::Parser::Scope.number?(obj) || obj
value = Puppet::Parser::Scope.number?(value) || value
# "" == undef for case/selector/if
obj == value or (obj == "" and value == :undef) or (obj == :undef and value == "")
end
end
# And include all of the AST subclasses.
require 'puppet/parser/ast/arithmetic_operator'
require 'puppet/parser/ast/astarray'
require 'puppet/parser/ast/asthash'
require 'puppet/parser/ast/boolean_operator'
require 'puppet/parser/ast/branch'
require 'puppet/parser/ast/caseopt'
require 'puppet/parser/ast/casestatement'
require 'puppet/parser/ast/collection'
require 'puppet/parser/ast/collexpr'
require 'puppet/parser/ast/comparison_operator'
require 'puppet/parser/ast/definition'
require 'puppet/parser/ast/else'
require 'puppet/parser/ast/function'
require 'puppet/parser/ast/hostclass'
require 'puppet/parser/ast/ifstatement'
require 'puppet/parser/ast/in_operator'
require 'puppet/parser/ast/lambda'
require 'puppet/parser/ast/leaf'
require 'puppet/parser/ast/match_operator'
require 'puppet/parser/ast/method_call'
require 'puppet/parser/ast/minus'
require 'puppet/parser/ast/node'
require 'puppet/parser/ast/nop'
require 'puppet/parser/ast/not'
require 'puppet/parser/ast/relationship'
require 'puppet/parser/ast/resource'
require 'puppet/parser/ast/resource_defaults'
require 'puppet/parser/ast/resource_instance'
require 'puppet/parser/ast/resource_override'
require 'puppet/parser/ast/resource_reference'
require 'puppet/parser/ast/resourceparam'
require 'puppet/parser/ast/selector'
-require 'puppet/parser/ast/tag'
require 'puppet/parser/ast/vardef'
require 'puppet/parser/code_merger'
diff --git a/lib/puppet/parser/ast/collexpr.rb b/lib/puppet/parser/ast/collexpr.rb
index 9f266ed47..481c14b20 100644
--- a/lib/puppet/parser/ast/collexpr.rb
+++ b/lib/puppet/parser/ast/collexpr.rb
@@ -1,109 +1,109 @@
require 'puppet'
require 'puppet/parser/ast/branch'
require 'puppet/parser/collector'
# An object that collects stored objects from the central cache and returns
# them to the current host, yo.
class Puppet::Parser::AST
class CollExpr < AST::Branch
attr_accessor :test1, :test2, :oper, :form, :type, :parens
def evaluate(scope)
if Puppet[:parser] == 'future'
evaluate4x(scope)
else
evaluate3x(scope)
end
end
# We return an object that does a late-binding evaluation.
def evaluate3x(scope)
# Make sure our contained expressions have all the info they need.
[@test1, @test2].each do |t|
if t.is_a?(self.class)
t.form ||= self.form
t.type ||= self.type
end
end
# The code is only used for virtual lookups
match1, code1 = @test1.safeevaluate scope
match2, code2 = @test2.safeevaluate scope
# First build up the virtual code.
# If we're a conjunction operator, then we're calling code. I did
# some speed comparisons, and it's at least twice as fast doing these
# case statements as doing an eval here.
code = proc do |resource|
case @oper
when "and"; code1.call(resource) and code2.call(resource)
when "or"; code1.call(resource) or code2.call(resource)
when "=="
if match1 == "tag"
resource.tagged?(match2)
else
if resource[match1].is_a?(Array)
resource[match1].include?(match2)
else
resource[match1] == match2
end
end
when "!="; resource[match1] != match2
end
end
match = [match1, @oper, match2]
return match, code
end
- # Late binding evaluation of a collect expression (as done in 3x), but with proper Puppet Langauge
+ # Late binding evaluation of a collect expression (as done in 3x), but with proper Puppet Language
# semantics for equals and include
#
def evaluate4x(scope)
# Make sure our contained expressions have all the info they need.
[@test1, @test2].each do |t|
if t.is_a?(self.class)
t.form ||= self.form
t.type ||= self.type
end
end
# The code is only used for virtual lookups
match1, code1 = @test1.safeevaluate scope
match2, code2 = @test2.safeevaluate scope
# First build up the virtual code.
# If we're a conjunction operator, then we're calling code. I did
# some speed comparisons, and it's at least twice as fast doing these
# case statements as doing an eval here.
code = proc do |resource|
case @oper
when "and"; code1.call(resource) and code2.call(resource)
when "or"; code1.call(resource) or code2.call(resource)
when "=="
if match1 == "tag"
resource.tagged?(match2)
else
if resource[match1].is_a?(Array)
@@compare_operator.include?(resource[match1], match2)
else
@@compare_operator.equals(resource[match1], match2)
end
end
when "!="; ! @@compare_operator.equals(resource[match1], match2)
end
end
match = [match1, @oper, match2]
return match, code
end
def initialize(hash = {})
super
if Puppet[:parser] == "future"
@@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new
end
raise ArgumentError, "Invalid operator #{@oper}" unless %w{== != and or}.include?(@oper)
end
end
end
diff --git a/lib/puppet/parser/ast/tag.rb b/lib/puppet/parser/ast/tag.rb
deleted file mode 100644
index 6f906a1c6..000000000
--- a/lib/puppet/parser/ast/tag.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'puppet/parser/ast/branch'
-
-class Puppet::Parser::AST
- # The code associated with a class. This is different from components
- # in that each class is a singleton -- only one will exist for a given
- # node.
- class Tag < AST::Branch
- @name = :class
- attr_accessor :type
-
- def evaluate(scope)
- types = @type.safeevaluate(scope)
-
- types = [types] unless types.is_a? Array
-
- types.each do |type|
- # Now set our class. We don't have to worry about checking
- # whether we've been evaluated because we're not evaluating
- # any code.
- scope.setclass(self.object_id, type)
- end
- end
- end
-end
diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index e17479ba6..506f8a27e 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -1,267 +1,278 @@
require 'puppet/resource'
# The primary difference between this class and its
# parent is that this class has rules on who can set
# parameters
class Puppet::Parser::Resource < Puppet::Resource
require 'puppet/parser/resource/param'
require 'puppet/util/tagging'
require 'puppet/parser/yaml_trimmer'
require 'puppet/resource/type_collection_helper'
include Puppet::Resource::TypeCollectionHelper
include Puppet::Util
include Puppet::Util::MethodHelper
include Puppet::Util::Errors
include Puppet::Util::Logging
include Puppet::Parser::YamlTrimmer
attr_accessor :source, :scope, :collector_id
attr_accessor :virtual, :override, :translated, :catalog, :evaluated
attr_accessor :file, :line
attr_reader :exported, :parameters
# Determine whether the provided parameter name is a relationship parameter.
def self.relationship_parameter?(name)
@relationship_names ||= Puppet::Type.relationship_params.collect { |p| p.name }
@relationship_names.include?(name)
end
# Set up some boolean test methods
def translated?; !!@translated; end
def override?; !!@override; end
def evaluated?; !!@evaluated; end
def [](param)
param = param.intern
if param == :title
return self.title
end
if @parameters.has_key?(param)
@parameters[param].value
else
nil
end
end
def eachparam
@parameters.each do |name, param|
yield param
end
end
def environment
scope.environment
end
# Process the stage metaparameter for a class. A containment edge
# is drawn from the class to the stage. The stage for containment
# defaults to main, if none is specified.
def add_edge_to_stage
return unless self.class?
unless stage = catalog.resource(:stage, self[:stage] || (scope && scope.resource && scope.resource[:stage]) || :main)
raise ArgumentError, "Could not find stage #{self[:stage] || :main} specified by #{self}"
end
self[:stage] ||= stage.title unless stage.title == :main
catalog.add_edge(stage, self)
end
# Retrieve the associated definition and evaluate it.
def evaluate
return if evaluated?
@evaluated = true
if klass = resource_type and ! builtin_type?
finish
evaluated_code = klass.evaluate_code(self)
return evaluated_code
elsif builtin?
devfail "Cannot evaluate a builtin type (#{type})"
else
self.fail "Cannot find definition #{type}"
end
end
# Mark this resource as both exported and virtual,
# or remove the exported mark.
def exported=(value)
if value
@virtual = true
@exported = value
else
@exported = value
end
end
# Do any finishing work on this object, called before evaluation or
# before storage/translation.
def finish
return if finished?
@finished = true
add_defaults
add_scope_tags
validate
end
# Has this resource already been finished?
def finished?
@finished
end
def initialize(*args)
raise ArgumentError, "Resources require a hash as last argument" unless args.last.is_a? Hash
raise ArgumentError, "Resources require a scope" unless args.last[:scope]
super
@source ||= scope.source
end
# Is this resource modeling an isomorphic resource type?
def isomorphic?
if builtin_type?
return resource_type.isomorphic?
else
return true
end
end
# Merge an override resource in. This will throw exceptions if
# any overrides aren't allowed.
def merge(resource)
# Test the resource scope, to make sure the resource is even allowed
# to override.
unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source)
raise Puppet::ParseError.new("Only subclasses can override parameters", resource.line, resource.file)
end
# Some of these might fail, but they'll fail in the way we want.
resource.parameters.each do |name, param|
override_parameter(param)
end
end
# This only mattered for clients < 0.25, which we don't support any longer.
# ...but, since this hasn't been deprecated, and at least some functions
# used it, deprecate now rather than just eliminate. --daniel 2012-07-15
def metaparam_compatibility_mode?
Puppet.deprecation_warning "metaparam_compatibility_mode? is obsolete since < 0.25 clients are really, really not supported any more"
false
end
def name
self[:name] || self.title
end
# A temporary occasion, until I get paths in the scopes figured out.
alias path to_s
# Define a parameter in our resource.
# if we ever receive a parameter named 'tag', set
# the resource tags with its value.
def set_parameter(param, value = nil)
if ! value.nil?
param = Puppet::Parser::Resource::Param.new(
:name => param, :value => value, :source => self.source
)
elsif ! param.is_a?(Puppet::Parser::Resource::Param)
raise ArgumentError, "Received incomplete information - no value provided for parameter #{param}"
end
tag(*param.value) if param.name == :tag
# And store it in our parameter hash.
@parameters[param.name] = param
end
alias []= set_parameter
def to_hash
@parameters.inject({}) do |hash, ary|
param = ary[1]
# Skip "undef" values.
hash[param.name] = param.value if param.value != :undef
hash
end
end
# Convert this resource to a RAL resource.
def to_ral
copy_as_resource.to_ral
end
+ # Is the receiver tagged with the given tags?
+ # This match takes into account the tags that a resource will inherit from its container
+ # but have not been set yet.
+ # It does *not* take tags set via resource defaults as these will *never* be set on
+ # the resource itself since all resources always have tags that are automatically
+ # assigned.
+ #
+ def tagged?(*tags)
+ super || ((scope_resource = scope.resource) && scope_resource != self && scope_resource.tagged?(tags))
+ end
+
private
# Add default values from our definition.
def add_defaults
scope.lookupdefaults(self.type).each do |name, param|
unless @parameters.include?(name)
self.debug "Adding default for #{name}"
@parameters[name] = param.dup
end
end
end
def add_scope_tags
if scope_resource = scope.resource
tag(*scope_resource.tags)
end
end
# Accept a parameter from an override.
def override_parameter(param)
# This can happen if the override is defining a new parameter, rather
# than replacing an existing one.
(set_parameter(param) and return) unless current = @parameters[param.name]
# The parameter is already set. Fail if they're not allowed to override it.
unless param.source.child_of?(current.source)
msg = "Parameter '#{param.name}' is already set on #{self}"
msg += " by #{current.source}" if current.source.to_s != ""
if current.file or current.line
fields = []
fields << current.file if current.file
fields << current.line.to_s if current.line
msg += " at #{fields.join(":")}"
end
msg += "; cannot redefine"
Puppet.log_exception(ArgumentError.new(), msg)
raise Puppet::ParseError.new(msg, param.line, param.file)
end
# If we've gotten this far, we're allowed to override.
# Merge with previous value, if the parameter was generated with the +>
# syntax. It's important that we use a copy of the new param instance
# here, not the old one, and not the original new one, so that the source
# is registered correctly for later overrides but the values aren't
# implcitly shared when multiple resources are overrriden at once (see
# ticket #3556).
if param.add
param = param.dup
param.value = [current.value, param.value].flatten
end
set_parameter(param)
end
# Make sure the resource's parameters are all valid for the type.
def validate
@parameters.each do |name, param|
validate_parameter(name)
end
rescue => detail
self.fail Puppet::ParseError, detail.to_s + " on #{self}", detail
end
def extract_parameters(params)
params.each do |param|
# Don't set the same parameter twice
self.fail Puppet::ParseError, "Duplicate parameter '#{param.name}' for on #{self}" if @parameters[param.name]
set_parameter(param)
end
end
end
diff --git a/lib/puppet/util/tagging.rb b/lib/puppet/util/tagging.rb
index 031b27f93..266029a1f 100644
--- a/lib/puppet/util/tagging.rb
+++ b/lib/puppet/util/tagging.rb
@@ -1,55 +1,56 @@
require 'puppet/util/tag_set'
module Puppet::Util::Tagging
ValidTagRegex = /^\w[-\w:.]*$/
- # Add a tag to our current list. These tags will be added to all
- # of the objects contained in this scope.
+ # Add a tag to the current tag set.
+ # When a tag set is used for a scope, these tags will be added to all of
+ # the objects contained in this scope when the objects are finished.
+ #
def tag(*ary)
@tags ||= new_tags
- ary.each do |tag|
+ ary.flatten.each do |tag|
name = tag.to_s.downcase
if name =~ ValidTagRegex
@tags << name
name.split("::").each do |section|
@tags << section
end
else
- fail(Puppet::ParseError, "Invalid tag #{name}")
+ fail(Puppet::ParseError, "Invalid tag '#{name}'")
end
end
end
- # Are we tagged with the provided tag?
+ # Is the receiver tagged with the given tags?
def tagged?(*tags)
not ( self.tags & tags.flatten.collect { |t| t.to_s } ).empty?
end
# Return a copy of the tag list, so someone can't ask for our tags
# and then modify them.
def tags
@tags ||= new_tags
@tags.dup
end
def tags=(tags)
@tags = new_tags
return if tags.nil? or tags == ""
tags = tags.strip.split(/\s*,\s*/) if tags.is_a?(String)
-
tags.each {|t| tag(t) }
end
private
def valid_tag?(tag)
tag.is_a?(String) and tag =~ ValidTagRegex
end
def new_tags
Puppet::Util::TagSet.new
end
end
diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb
index f10ce1adc..6ce0b7d67 100755
--- a/spec/integration/parser/compiler_spec.rb
+++ b/spec/integration/parser/compiler_spec.rb
@@ -1,513 +1,541 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/parser/parser_factory'
require 'puppet_spec/compiler'
require 'matchers/resource'
describe "Puppet::Parser::Compiler" do
include PuppetSpec::Compiler
include Matchers::Resource
before :each do
@node = Puppet::Node.new "testnode"
@scope_resource = stub 'scope_resource', :builtin? => true, :finish => nil, :ref => 'Class[main]'
@scope = stub 'scope', :resource => @scope_resource, :source => mock("source")
end
after do
Puppet.settings.clear
end
# shared because tests are invoked both for classic and future parser
#
shared_examples_for "the compiler" do
it "should be able to determine the configuration version from a local version control repository" do
pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do
# This should always work, because we should always be
# in the puppet repo when we run this.
version = %x{git rev-parse HEAD}.chomp
Puppet.settings[:config_version] = 'git rev-parse HEAD'
@parser = Puppet::Parser::ParserFactory.parser "development"
@compiler = Puppet::Parser::Compiler.new(@node)
@compiler.catalog.version.should == version
end
end
it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do
Puppet[:code] = <<-PP
class foo
{
notify { foo_notify: }
include bar
}
class bar
{
notify { bar_notify: }
}
PP
@node.stubs(:classes).returns(['foo', 'bar'])
catalog = Puppet::Parser::Compiler.compile(@node)
catalog.resource("Notify[foo_notify]").should_not be_nil
catalog.resource("Notify[bar_notify]").should_not be_nil
end
describe "when resolving class references" do
it "should favor local scope, even if there's an included class in topscope" do
Puppet[:code] = <<-PP
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
}
class baz {
}
include baz
include experiment
include experiment::baz
PP
catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
notify_resource = catalog.resource( "Notify[x]" )
notify_resource[:require].title.should == "Experiment::Baz"
end
it "should favor local scope, even if there's an unincluded class in topscope" do
Puppet[:code] = <<-PP
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
}
class baz {
}
include experiment
include experiment::baz
PP
catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
notify_resource = catalog.resource( "Notify[x]" )
notify_resource[:require].title.should == "Experiment::Baz"
end
end
describe "(ticket #13349) when explicitly specifying top scope" do
["class {'::bar::baz':}", "include ::bar::baz"].each do |include|
describe "with #{include}" do
it "should find the top level class" do
Puppet[:code] = <<-MANIFEST
class { 'foo::test': }
class foo::test {
#{include}
}
class bar::baz {
notify { 'good!': }
}
class foo::bar::baz {
notify { 'bad!': }
}
MANIFEST
catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
catalog.resource("Class[Bar::Baz]").should_not be_nil
catalog.resource("Notify[good!]").should_not be_nil
catalog.resource("Class[Foo::Bar::Baz]").should be_nil
catalog.resource("Notify[bad!]").should be_nil
end
end
end
end
it "should recompute the version after input files are re-parsed" do
Puppet[:code] = 'class foo { }'
Time.stubs(:now).returns(1)
node = Puppet::Node.new('mynode')
Puppet::Parser::Compiler.compile(node).version.should == 1
Time.stubs(:now).returns(2)
Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
Puppet::Parser::Compiler.compile(node).version.should == 2
end
['class', 'define', 'node'].each do |thing|
it "should not allow '#{thing}' inside evaluated conditional constructs" do
Puppet[:code] = <<-PP
if true {
#{thing} foo {
}
notify { decoy: }
}
PP
begin
Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
raise "compilation should have raised Puppet::Error"
rescue Puppet::Error => e
e.message.should =~ /at line 2/
end
end
end
it "should not allow classes inside unevaluated conditional constructs" do
Puppet[:code] = <<-PP
if false {
class foo {
}
}
PP
lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error)
end
describe "when defining relationships" do
def extract_name(ref)
ref.sub(/File\[(\w+)\]/, '\1')
end
let(:node) { Puppet::Node.new('mynode') }
let(:code) do
<<-MANIFEST
file { [a,b,c]:
mode => 0644,
}
file { [d,e]:
mode => 0755,
}
MANIFEST
end
let(:expected_relationships) { [] }
let(:expected_subscriptions) { [] }
before :each do
Puppet[:code] = code
end
after :each do
catalog = Puppet::Parser::Compiler.compile(node)
resources = catalog.resources.select { |res| res.type == 'File' }
actual_relationships, actual_subscriptions = [:before, :notify].map do |relation|
resources.map do |res|
dependents = Array(res[relation])
dependents.map { |ref| [res.title, extract_name(ref)] }
end.inject(&:concat)
end
actual_relationships.should =~ expected_relationships
actual_subscriptions.should =~ expected_subscriptions
end
it "should create a relationship" do
code << "File[a] -> File[b]"
expected_relationships << ['a','b']
end
it "should create a subscription" do
code << "File[a] ~> File[b]"
expected_subscriptions << ['a', 'b']
end
it "should create relationships using title arrays" do
code << "File[a,b] -> File[c,d]"
expected_relationships.concat [
['a', 'c'],
['b', 'c'],
['a', 'd'],
['b', 'd'],
]
end
it "should create relationships using collection expressions" do
code << "File <| mode == 0644 |> -> File <| mode == 0755 |>"
expected_relationships.concat [
['a', 'd'],
['b', 'd'],
['c', 'd'],
['a', 'e'],
['b', 'e'],
['c', 'e'],
]
end
it "should create relationships using resource names" do
code << "'File[a]' -> 'File[b]'"
expected_relationships << ['a', 'b']
end
it "should create relationships using variables" do
code << <<-MANIFEST
$var = File[a]
$var -> File[b]
MANIFEST
expected_relationships << ['a', 'b']
end
it "should create relationships using case statements" do
code << <<-MANIFEST
$var = 10
case $var {
10: {
file { s1: }
}
12: {
file { s2: }
}
}
->
case $var + 2 {
10: {
file { t1: }
}
12: {
file { t2: }
}
}
MANIFEST
expected_relationships << ['s1', 't2']
end
it "should create relationships using array members" do
code << <<-MANIFEST
$var = [ [ [ File[a], File[b] ] ] ]
$var[0][0][0] -> $var[0][0][1]
MANIFEST
expected_relationships << ['a', 'b']
end
it "should create relationships using hash members" do
code << <<-MANIFEST
$var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}}
$var[foo][bar][source] -> $var[foo][bar][target]
MANIFEST
expected_relationships << ['a', 'b']
end
it "should create relationships using resource declarations" do
code << "file { l: } -> file { r: }"
expected_relationships << ['l', 'r']
end
it "should chain relationships" do
code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]"
expected_relationships << ['a', 'b'] << ['d', 'c']
expected_subscriptions << ['b', 'c'] << ['e', 'd']
end
end
context 'when working with immutable node data' do
context 'and have opted in to immutable_node_data' do
before :each do
Puppet[:immutable_node_data] = true
end
def node_with_facts(facts)
Puppet[:facts_terminus] = :memory
Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new("testing", facts))
node = Puppet::Node.new("testing")
node.fact_merge
node
end
matcher :fail_compile_with do |node, message_regex|
match do |manifest|
@error = nil
begin
compile_to_catalog(manifest, node)
false
rescue Puppet::Error => e
@error = e
message_regex.match(e.message)
end
end
failure_message_for_should do
if @error
"failed with #{@error}\n#{@error.backtrace}"
else
"did not fail"
end
end
end
it 'should make $facts available' do
node = node_with_facts('the_facts' => 'straight')
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $facts[the_facts] }
MANIFEST
catalog.resource("Notify[test]")[:message].should == "straight"
end
it 'should make $facts reserved' do
node = node_with_facts('the_facts' => 'straight')
expect('$facts = {}').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/)
expect('class a { $facts = {} } include a').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/)
end
it 'should make $facts immutable' do
node = node_with_facts('string' => 'value', 'array' => ['string'], 'hash' => { 'a' => 'string' }, 'number' => 1, 'boolean' => true)
expect('$i=inline_template("<% @facts[%q{new}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i)
expect('$i=inline_template("<% @facts[%q{string}].chop! %>")').to fail_compile_with(node, /frozen String/i)
expect('$i=inline_template("<% @facts[%q{array}][0].chop! %>")').to fail_compile_with(node, /frozen String/i)
expect('$i=inline_template("<% @facts[%q{array}][1] = 2 %>")').to fail_compile_with(node, /frozen Array/i)
expect('$i=inline_template("<% @facts[%q{hash}][%q{a}].chop! %>")').to fail_compile_with(node, /frozen String/i)
expect('$i=inline_template("<% @facts[%q{hash}][%q{b}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i)
end
it 'should make $facts available even if there are no facts' do
Puppet[:facts_terminus] = :memory
node = Puppet::Node.new("testing2")
node.fact_merge
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $facts }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, {})
end
end
context 'and have not opted in to immutable_node_data' do
before :each do
Puppet[:immutable_node_data] = false
end
it 'should not make $facts available' do
Puppet[:facts_terminus] = :memory
facts = Puppet::Node::Facts.new("testing", 'the_facts' => 'straight')
Puppet::Node::Facts.indirection.save(facts)
node = Puppet::Node.new("testing")
node.fact_merge
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => "An $facts space" }
MANIFEST
catalog.resource("Notify[test]")[:message].should == "An space"
end
end
end
context 'when working with the trusted data hash' do
context 'and have opted in to trusted_node_data' do
before :each do
Puppet[:trusted_node_data] = true
end
it 'should make $trusted available' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $trusted[data] }
MANIFEST
catalog.resource("Notify[test]")[:message].should == "value"
end
it 'should not allow assignment to $trusted' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
expect do
catalog = compile_to_catalog(<<-MANIFEST, node)
$trusted = 'changed'
notify { 'test': message => $trusted == 'changed' }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/)
end
it 'should not allow addition to $trusted hash' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
expect do
catalog = compile_to_catalog(<<-MANIFEST, node)
$trusted['extra'] = 'added'
notify { 'test': message => $trusted['extra'] == 'added' }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
# different errors depending on regular or future parser
end.to raise_error(Puppet::Error, /(can't modify frozen [hH]ash)|(Illegal attempt to assign)/)
end
it 'should not allow addition to $trusted hash via Ruby inline template' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
expect do
catalog = compile_to_catalog(<<-MANIFEST, node)
$dummy = inline_template("<% @trusted['extra'] = 'added' %> lol")
notify { 'test': message => $trusted['extra'] == 'added' }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
end.to raise_error(Puppet::Error, /can't modify frozen [hH]ash/)
end
end
context 'and have not opted in to trusted_node_data' do
before :each do
Puppet[:trusted_node_data] = false
end
it 'should not make $trusted available' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $trusted == undef }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
end
it 'should allow assignment to $trusted' do
node = Puppet::Node.new("testing")
catalog = compile_to_catalog(<<-MANIFEST, node)
$trusted = 'changed'
notify { 'test': message => $trusted == 'changed' }
MANIFEST
catalog.resource("Notify[test]")[:message].should == true
end
end
end
+
+ context 'when evaluating collection' do
+ it 'matches on container inherited tags' do
+ Puppet[:code] = <<-MANIFEST
+ class xport_test {
+ tag 'foo_bar'
+ @notify { 'nbr1':
+ message => 'explicitly tagged',
+ tag => 'foo_bar'
+ }
+
+ @notify { 'nbr2':
+ message => 'implicitly tagged'
+ }
+
+ Notify <| tag == 'foo_bar' |> {
+ message => 'overridden'
+ }
+ }
+ include xport_test
+ MANIFEST
+
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+
+ expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden')
+ expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden')
+ end
+ end
end
describe 'using classic parser' do
before :each do
Puppet[:parser] = 'current'
end
it_behaves_like 'the compiler' do
end
end
end
diff --git a/spec/integration/parser/future_compiler_spec.rb b/spec/integration/parser/future_compiler_spec.rb
index bcf2273a0..eac0fa921 100644
--- a/spec/integration/parser/future_compiler_spec.rb
+++ b/spec/integration/parser/future_compiler_spec.rb
@@ -1,402 +1,431 @@
require 'spec_helper'
require 'puppet/pops'
require 'puppet/parser/parser_factory'
require 'puppet_spec/compiler'
require 'puppet_spec/pops'
require 'puppet_spec/scope'
require 'matchers/resource'
require 'rgen/metamodel_builder'
# Test compilation using the future evaluator
describe "Puppet::Parser::Compiler" do
include PuppetSpec::Compiler
include Matchers::Resource
before :each do
Puppet[:parser] = 'future'
end
describe "the compiler when using future parser and evaluator" do
it "should be able to determine the configuration version from a local version control repository" do
pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do
# This should always work, because we should always be
# in the puppet repo when we run this.
version = %x{git rev-parse HEAD}.chomp
Puppet.settings[:config_version] = 'git rev-parse HEAD'
compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("testnode"))
compiler.catalog.version.should == version
end
end
it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do
node = Puppet::Node.new("testnodex")
node.classes = ['foo', 'bar']
catalog = compile_to_catalog(<<-PP, node)
class foo
{
notify { foo_notify: }
include bar
}
class bar
{
notify { bar_notify: }
}
PP
catalog = Puppet::Parser::Compiler.compile(node)
expect(catalog).to have_resource("Notify[foo_notify]")
expect(catalog).to have_resource("Notify[bar_notify]")
end
it 'applies defaults for defines with qualified names (PUP-2302)' do
catalog = compile_to_catalog(<<-CODE)
define my::thing($msg = 'foo') { notify {'check_me': message => $msg } }
My::Thing { msg => 'evoe' }
my::thing { 'name': }
CODE
expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "evoe")
end
it 'does not apply defaults from dynamic scopes (PUP-867)' do
catalog = compile_to_catalog(<<-CODE)
class a {
Notify { message => "defaulted" }
include b
notify { bye: }
}
class b { notify { hi: } }
include a
CODE
expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, nil)
expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted")
end
it 'gets default from inherited class (PUP-867)' do
catalog = compile_to_catalog(<<-CODE)
class a {
Notify { message => "defaulted" }
include c
notify { bye: }
}
class b { Notify { message => "inherited" } }
class c inherits b { notify { hi: } }
include a
CODE
expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "inherited")
expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted")
end
describe "when resolving class references" do
it "should favor local scope, even if there's an included class in topscope" do
catalog = compile_to_catalog(<<-PP)
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
}
class baz {
}
include baz
include experiment
include experiment::baz
PP
expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]"))
end
it "should favor local scope, even if there's an unincluded class in topscope" do
catalog = compile_to_catalog(<<-PP)
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
}
class baz {
}
include experiment
include experiment::baz
PP
expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]"))
end
end
describe "(ticket #13349) when explicitly specifying top scope" do
["class {'::bar::baz':}", "include ::bar::baz"].each do |include|
describe "with #{include}" do
it "should find the top level class" do
catalog = compile_to_catalog(<<-MANIFEST)
class { 'foo::test': }
class foo::test {
#{include}
}
class bar::baz {
notify { 'good!': }
}
class foo::bar::baz {
notify { 'bad!': }
}
MANIFEST
expect(catalog).to have_resource("Class[Bar::Baz]")
expect(catalog).to have_resource("Notify[good!]")
expect(catalog).to_not have_resource("Class[Foo::Bar::Baz]")
expect(catalog).to_not have_resource("Notify[bad!]")
end
end
end
end
it "should recompute the version after input files are re-parsed" do
Puppet[:code] = 'class foo { }'
Time.stubs(:now).returns(1)
node = Puppet::Node.new('mynode')
Puppet::Parser::Compiler.compile(node).version.should == 1
Time.stubs(:now).returns(2)
Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
Puppet::Parser::Compiler.compile(node).version.should == 2
end
['define', 'class', 'node'].each do |thing|
it "'#{thing}' is not allowed inside evaluated conditional constructs" do
expect do
compile_to_catalog(<<-PP)
if true {
#{thing} foo {
}
notify { decoy: }
}
PP
end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/)
end
it "'#{thing}' is not allowed inside un-evaluated conditional constructs" do
expect do
compile_to_catalog(<<-PP)
if false {
#{thing} foo {
}
notify { decoy: }
}
PP
end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/)
end
end
describe "relationships can be formed" do
def extract_name(ref)
ref.sub(/File\[(\w+)\]/, '\1')
end
def assert_creates_relationships(relationship_code, expectations)
base_manifest = <<-MANIFEST
file { [a,b,c]:
mode => 0644,
}
file { [d,e]:
mode => 0755,
}
MANIFEST
catalog = compile_to_catalog(base_manifest + relationship_code)
resources = catalog.resources.select { |res| res.type == 'File' }
actual_relationships, actual_subscriptions = [:before, :notify].map do |relation|
resources.map do |res|
dependents = Array(res[relation])
dependents.map { |ref| [res.title, extract_name(ref)] }
end.inject(&:concat)
end
actual_relationships.should =~ (expectations[:relationships] || [])
actual_subscriptions.should =~ (expectations[:subscriptions] || [])
end
it "of regular type" do
assert_creates_relationships("File[a] -> File[b]",
:relationships => [['a','b']])
end
it "of subscription type" do
assert_creates_relationships("File[a] ~> File[b]",
:subscriptions => [['a', 'b']])
end
it "between multiple resources expressed as resource with multiple titles" do
assert_creates_relationships("File[a,b] -> File[c,d]",
:relationships => [['a', 'c'],
['b', 'c'],
['a', 'd'],
['b', 'd']])
end
it "between collection expressions" do
assert_creates_relationships("File <| mode == 0644 |> -> File <| mode == 0755 |>",
:relationships => [['a', 'd'],
['b', 'd'],
['c', 'd'],
['a', 'e'],
['b', 'e'],
['c', 'e']])
end
it "between resources expressed as Strings" do
assert_creates_relationships("'File[a]' -> 'File[b]'",
:relationships => [['a', 'b']])
end
it "between resources expressed as variables" do
assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']])
$var = File[a]
$var -> File[b]
MANIFEST
end
it "between resources expressed as case statements" do
assert_creates_relationships(<<-MANIFEST, :relationships => [['s1', 't2']])
$var = 10
case $var {
10: {
file { s1: }
}
12: {
file { s2: }
}
}
->
case $var + 2 {
10: {
file { t1: }
}
12: {
file { t2: }
}
}
MANIFEST
end
it "using deep access in array" do
assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']])
$var = [ [ [ File[a], File[b] ] ] ]
$var[0][0][0] -> $var[0][0][1]
MANIFEST
end
it "using deep access in hash" do
assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']])
$var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}}
$var[foo][bar][source] -> $var[foo][bar][target]
MANIFEST
end
it "using resource declarations" do
assert_creates_relationships("file { l: } -> file { r: }", :relationships => [['l', 'r']])
end
it "between entries in a chain of relationships" do
assert_creates_relationships("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]",
:relationships => [['a', 'b'], ['d', 'c']],
:subscriptions => [['b', 'c'], ['e', 'd']])
end
end
context "when dealing with variable references" do
it 'an initial underscore in a variable name is ok' do
catalog = compile_to_catalog(<<-MANIFEST)
class a { $_a = 10}
include a
notify { 'test': message => $a::_a }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 10)
end
it 'an initial underscore in not ok if elsewhere than last segment' do
expect do
catalog = compile_to_catalog(<<-MANIFEST)
class a { $_a = 10}
include a
notify { 'test': message => $_a::_a }
MANIFEST
end.to raise_error(/Illegal variable name/)
end
it 'a missing variable as default value becomes undef' do
catalog = compile_to_catalog(<<-MANIFEST)
class a ($b=$x) { notify {$b: message=>'meh'} }
include a
MANIFEST
expect(catalog).to have_resource("Notify[undef]").with_parameter(:message, "meh")
end
end
context 'when working with the trusted data hash' do
context 'and have opted in to hashed_node_data' do
before :each do
Puppet[:trusted_node_data] = true
end
it 'should make $trusted available' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => $trusted[data] }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "value")
end
it 'should not allow assignment to $trusted' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
expect do
compile_to_catalog(<<-MANIFEST, node)
$trusted = 'changed'
notify { 'test': message => $trusted == 'changed' }
MANIFEST
end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/)
end
end
context 'and have not opted in to hashed_node_data' do
before :each do
Puppet[:trusted_node_data] = false
end
it 'should not make $trusted available' do
node = Puppet::Node.new("testing")
node.trusted_data = { "data" => "value" }
catalog = compile_to_catalog(<<-MANIFEST, node)
notify { 'test': message => ($trusted == undef) }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true)
end
it 'should allow assignment to $trusted' do
catalog = compile_to_catalog(<<-MANIFEST)
$trusted = 'changed'
notify { 'test': message => $trusted == 'changed' }
MANIFEST
expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true)
end
end
end
end
+
+ context 'when evaluating collection' do
+ it 'matches on container inherited tags' do
+ Puppet[:code] = <<-MANIFEST
+ class xport_test {
+ tag('foo_bar')
+ @notify { 'nbr1':
+ message => 'explicitly tagged',
+ tag => 'foo_bar'
+ }
+
+ @notify { 'nbr2':
+ message => 'implicitly tagged'
+ }
+
+ Notify <| tag == 'foo_bar' |> {
+ message => 'overridden'
+ }
+ }
+ include xport_test
+ MANIFEST
+
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+
+ expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden')
+ expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden')
+ end
+ end
+
end
diff --git a/spec/unit/util/tagging_spec.rb b/spec/unit/util/tagging_spec.rb
index 248e915e9..c2ebeaab3 100755
--- a/spec/unit/util/tagging_spec.rb
+++ b/spec/unit/util/tagging_spec.rb
@@ -1,131 +1,162 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/util/tagging'
describe Puppet::Util::Tagging do
let(:tagger) { Object.new.extend(Puppet::Util::Tagging) }
it "should add tags to the returned tag list" do
tagger.tag("one")
expect(tagger.tags).to include("one")
end
it "should return a duplicate of the tag list, rather than the original" do
tagger.tag("one")
tags = tagger.tags
tags << "two"
expect(tagger.tags).to_not include("two")
end
it "should add all provided tags to the tag list" do
tagger.tag("one", "two")
expect(tagger.tags).to include("one")
expect(tagger.tags).to include("two")
end
it "should fail on tags containing '*' characters" do
expect { tagger.tag("bad*tag") }.to raise_error(Puppet::ParseError)
end
it "should fail on tags starting with '-' characters" do
expect { tagger.tag("-badtag") }.to raise_error(Puppet::ParseError)
end
it "should fail on tags containing ' ' characters" do
expect { tagger.tag("bad tag") }.to raise_error(Puppet::ParseError)
end
it "should allow alpha tags" do
expect { tagger.tag("good_tag") }.not_to raise_error
end
it "should allow tags containing '.' characters" do
expect { tagger.tag("good.tag") }.to_not raise_error(Puppet::ParseError)
end
it "should add qualified classes as tags" do
tagger.tag("one::two")
expect(tagger.tags).to include("one::two")
end
it "should add each part of qualified classes as tags" do
tagger.tag("one::two::three")
expect(tagger.tags).to include('one')
expect(tagger.tags).to include("two")
expect(tagger.tags).to include("three")
end
it "should indicate when the object is tagged with a provided tag" do
tagger.tag("one")
expect(tagger).to be_tagged("one")
end
it "should indicate when the object is not tagged with a provided tag" do
expect(tagger).to_not be_tagged("one")
end
it "should indicate when the object is tagged with any tag in an array" do
tagger.tag("one")
expect(tagger).to be_tagged("one","two","three")
end
it "should indicate when the object is not tagged with any tag in an array" do
tagger.tag("one")
expect(tagger).to_not be_tagged("two","three")
end
context "when tagging" do
it "converts symbols to strings" do
tagger.tag(:hello)
expect(tagger.tags).to include('hello')
end
it "downcases tags" do
tagger.tag(:HEllO)
tagger.tag("GooDByE")
expect(tagger).to be_tagged("hello")
expect(tagger).to be_tagged("goodbye")
end
it "accepts hyphenated tags" do
tagger.tag("my-tag")
expect(tagger).to be_tagged("my-tag")
end
end
context "when querying if tagged" do
it "responds true if queried on the entire set" do
tagger.tag("one", "two")
expect(tagger).to be_tagged("one", "two")
end
it "responds true if queried on a subset" do
tagger.tag("one", "two", "three")
expect(tagger).to be_tagged("two", "one")
end
it "responds true if queried on an overlapping but not fully contained set" do
tagger.tag("one", "two")
expect(tagger).to be_tagged("zero", "one")
end
it "responds false if queried on a disjoint set" do
tagger.tag("one", "two", "three")
expect(tagger).to_not be_tagged("five")
end
it "responds false if queried on the empty set" do
expect(tagger).to_not be_tagged
end
end
context "when assigning tags" do
it "splits a string on ','" do
tagger.tags = "one, two, three"
expect(tagger).to be_tagged("one")
expect(tagger).to be_tagged("two")
expect(tagger).to be_tagged("three")
end
+
+ it "protects against empty tags" do
+ expect { tagger.tags = "one,,two"}.to raise_error(/Invalid tag ''/)
+ end
+
+ it "takes an array of tags" do
+ tagger.tags = ["one", "two"]
+
+ expect(tagger).to be_tagged("one")
+ expect(tagger).to be_tagged("two")
+ end
+
+ it "removes any existing tags when reassigning" do
+ tagger.tags = "one, two"
+
+ tagger.tags = "three, four"
+
+ expect(tagger).to_not be_tagged("one")
+ expect(tagger).to_not be_tagged("two")
+ expect(tagger).to be_tagged("three")
+ expect(tagger).to be_tagged("four")
+ end
+
+ it "allows empty tags that are generated from :: separated tags" do
+ tagger.tags = "one::::two::three"
+
+ expect(tagger).to be_tagged("one")
+ expect(tagger).to be_tagged("")
+ expect(tagger).to be_tagged("two")
+ expect(tagger).to be_tagged("three")
+ end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 8:59 AM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10075398
Default Alt Text
(60 KB)

Event Timeline