class Puppet::Test::TestHelper

This class is intended to provide an API to be used by external projects

when they are running tests that depend on puppet core.  This should
allow us to vary the implementation details of managing puppet's state
for testing, from one version of puppet to the next--without forcing
the external projects to do any of that state management or be aware of
the implementation details.

For now, this consists of a few very simple signatures. The plan is

that it should be the responsibility of the puppetlabs_spec_helper
to broker between external projects and this API; thus, if any
hacks are required (e.g. to determine whether or not a particular)
version of puppet supports this API, those hacks will be consolidated in
one place and won't need to be duplicated in every external project.

This should also alleviate the anti-pattern that we've been following,

wherein each external project starts off with a copy of puppet core's
test_helper.rb and is exposed to risk of that code getting out of
sync with core.

Since this class will be “library code” that ships with puppet, it does

not use API from any existing test framework such as rspec.  This should
theoretically allow it to be used with other unit test frameworks in the
future, if desired.

Note that in the future this API could potentially be expanded to handle

other features such as "around_test", but we didn't see a compelling
reason to deal with that right now.

Constants

ROLLBACK_MARK

The name of the rollback mark used in the Puppet.context. This is what the test infrastructure returns to for each test.

Public Class Methods

after_all_tests() click to toggle source

Call this method once, at the end of a test run, when no more tests

will be run.

@return nil

   # File lib/puppet/test/test_helper.rb
79 def self.after_all_tests()
80 end
after_each_test() click to toggle source

Call this method once per test, after execution of each individual test. @return nil

    # File lib/puppet/test/test_helper.rb
156 def self.after_each_test()
157   # Ensure that a matching tear down only happens once per completed setup
158   # (see #before_each_test).
159   return unless @@reentry_count == 1
160   @@reentry_count = 0
161 
162   Puppet.settings.send(:clear_everything_for_tests)
163 
164   Puppet::Util::Storage.clear
165   Puppet::Util::ExecutionStub.reset
166   Puppet.runtime.clear
167 
168   Puppet.clear_deprecation_warnings
169 
170   # uncommenting and manipulating this can be useful when tracking down calls to deprecated code
171   #Puppet.log_deprecations_to_file("deprecations.txt", /^Puppet::Util.exec/)
172 
173   # Restore the indirector configuration.  See before hook.
174   indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections)
175   indirections.each do |indirector|
176     $saved_indirection_state.fetch(indirector.name, {}).each do |variable, value|
177       if variable == :@termini
178         indirector.instance_variable_set(variable, value)
179       else
180         indirector.instance_variable_get(variable).value = value
181       end
182     end
183   end
184   $saved_indirection_state = nil
185 
186   # Restore the global process environment.
187   ENV.replace($old_env)
188 
189   # Clear all environments
190   Puppet.lookup(:environments).clear_all
191 
192   # Restore the load_path late, to avoid messing with stubs from the test.
193   $LOAD_PATH.clear
194   $old_load_path.each {|x| $LOAD_PATH << x }
195 
196   Puppet.rollback_context(ROLLBACK_MARK)
197 end
before_all_tests() click to toggle source

Call this method once, when beginning a test run–prior to running

any individual tests.

@return nil

   # File lib/puppet/test/test_helper.rb
71 def self.before_all_tests()
72   # The process environment is a shared, persistent resource.
73   $old_env = ENV.to_hash
74 end
before_each_test() click to toggle source

Call this method once per test, prior to execution of each individual test. @return nil

    # File lib/puppet/test/test_helper.rb
 89 def self.before_each_test()
 90   # When using both rspec-puppet and puppet-rspec-helper, there are two packages trying
 91   # to be helpful and orchestrate the callback sequence. We let only the first win, the
 92   # second callback results in a no-op.
 93   # Likewise when entering after_each_test(), a check is made to make tear down happen
 94   # only once.
 95   #
 96   return unless @@reentry_count == 0
 97   @@reentry_count = 1
 98 
 99   Puppet.mark_context(ROLLBACK_MARK)
100 
101   # We need to preserve the current state of all our indirection cache and
102   # terminus classes.  This is pretty important, because changes to these
103   # are global and lead to order dependencies in our testing.
104   #
105   # We go direct to the implementation because there is no safe, sane public
106   # API to manage restoration of these to their default values.  This
107   # should, once the value is proved, be moved to a standard API on the
108   # indirector.
109   #
110   # To make things worse, a number of the tests stub parts of the
111   # indirector.  These stubs have very specific expectations that what
112   # little of the public API we could use is, well, likely to explode
113   # randomly in some tests.  So, direct access.  --daniel 2011-08-30
114   $saved_indirection_state = {}
115   indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections)
116   indirections.each do |indirector|
117     $saved_indirection_state[indirector.name] = {
118       :@terminus_class => indirector.instance_variable_get(:@terminus_class).value,
119       :@cache_class    => indirector.instance_variable_get(:@cache_class).value,
120       # dup the termini hash so termini created and registered during
121       # the test aren't stored in our saved_indirection_state
122       :@termini        => indirector.instance_variable_get(:@termini).dup
123     }
124   end
125 
126   # So is the load_path
127   $old_load_path = $LOAD_PATH.dup
128 
129   initialize_settings_before_each()
130 
131   Puppet.push_context(
132     {
133       trusted_information:
134         Puppet::Context::TrustedInformation.new('local', 'testing', {}, { "trusted_testhelper" => true }),
135       ssl_context: Puppet::SSL::SSLContext.new(cacerts: []).freeze,
136       http_session: proc { Puppet.runtime[:http].create_session }
137     },
138     "Context for specs")
139 
140   # trigger `require 'facter'`
141   Puppet.runtime[:facter]
142 
143   Puppet::Parser::Functions.reset
144   Puppet::Application.clear!
145   Puppet::Util::Profiler.clear
146 
147   Puppet::Node::Facts.indirection.terminus_class = :memory
148   facts = Puppet::Node::Facts.new(Puppet[:node_name_value])
149   Puppet::Node::Facts.indirection.save(facts)
150 
151   Puppet.clear_deprecation_warnings
152 end
initialize() click to toggle source

Call this method once, as early as possible, such as before loading tests that call Puppet. @return nil

   # File lib/puppet/test/test_helper.rb
37 def self.initialize()
38   # This meta class instance variable is used as a guard to ensure that
39   # before_each, and after_each are only called once. This problem occurs
40   # when there are more than one puppet test infrastructure orchestrator in use.
41   # The use of both puppetabs-spec_helper, and rodjek-rspec_puppet will cause
42   # two resets of the puppet environment, and will cause problem rolling back to
43   # a known point as there is no way to differentiate where the calls are coming
44   # from. See more information in #before_each_test, and #after_each_test
45   # Note that the variable is only initialized to 0 if nil. This is important
46   # as more than one orchestrator will call initialize. A second call can not
47   # simply set it to 0 since that would potentially destroy an active guard.
48   #
49   @@reentry_count ||= 0
50 
51   @environmentpath = Dir.mktmpdir('environments')
52   Dir.mkdir("#{@environmentpath}/production")
53   owner = Process.pid
54   Puppet.push_context(Puppet.base_context({
55     :environmentpath => @environmentpath,
56     :basemodulepath => "",
57   }), "Initial for specs")
58   Puppet::Parser::Functions.reset
59 
60   ObjectSpace.define_finalizer(Puppet.lookup(:environments), proc {
61     if Process.pid == owner
62       FileUtils.rm_rf(@environmentpath)
63     end
64   })
65   Puppet::SSL::Oids.register_puppet_oids
66 end

Private Class Methods

app_defaults_for_tests() click to toggle source

PRIVATE METHODS (not part of the public TestHelper API–do not call these from outside

of this class!)
    # File lib/puppet/test/test_helper.rb
205 def self.app_defaults_for_tests()
206   {
207       :logdir     => "/dev/null",
208       :confdir    => "/dev/null",
209       :publicdir  => "/dev/null",
210       :codedir    => "/dev/null",
211       :vardir     => "/dev/null",
212       :rundir     => "/dev/null",
213       :hiera_config => "/dev/null",
214   }
215 end
initialize_settings_before_each() click to toggle source
    # File lib/puppet/test/test_helper.rb
218 def self.initialize_settings_before_each()
219   Puppet.settings.preferred_run_mode = "user"
220   # Initialize "app defaults" settings to a good set of test values
221   Puppet.settings.initialize_app_defaults(app_defaults_for_tests)
222 
223   # We don't want to depend upon the reported domain name of the
224   # machine running the tests, nor upon the DNS setup of that
225   # domain.
226   Puppet.settings[:use_srv_records] = false
227 
228   # Longer keys are secure, but they sure make for some slow testing - both
229   # in terms of generating keys, and in terms of anything the next step down
230   # the line doing validation or whatever.  Most tests don't care how long
231   # or secure it is, just that it exists, so these are better and faster
232   # defaults, in testing only.
233   #
234   # I would make these even shorter, but OpenSSL doesn't support anything
235   # below 512 bits.  Sad, really, because a 0 bit key would be just fine.
236   Puppet[:keylength] = 512
237 
238   # Although we setup a testing context during initialization, some tests
239   # will end up creating their own context using the real context objects
240   # and use the setting for the environments. In order to avoid those tests
241   # having to deal with a missing environmentpath we can just set it right
242   # here.
243   Puppet[:environmentpath] = @environmentpath
244   Puppet[:environment_timeout] = 0
245 end