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
84 def self.after_all_tests()
85 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
159 def self.after_each_test()
160   # Ensure that a matching tear down only happens once per completed setup
161   # (see #before_each_test).
162   return unless @@reentry_count == 1
163   @@reentry_count = 0
164 
165   Puppet.settings.send(:clear_everything_for_tests)
166 
167   Puppet::Util::Storage.clear
168   Puppet::Util::ExecutionStub.reset
169 
170   Puppet.clear_deprecation_warnings
171 
172   # uncommenting and manipulating this can be useful when tracking down calls to deprecated code
173   #Puppet.log_deprecations_to_file("deprecations.txt", /^Puppet::Util.exec/)
174 
175   # Restore the indirector configuration.  See before hook.
176   indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections)
177   indirections.each do |indirector|
178     $saved_indirection_state.fetch(indirector.name, {}).each do |variable, value|
179       if variable == :@termini
180         indirector.instance_variable_set(variable, value)
181       else
182         indirector.instance_variable_get(variable).value = value
183       end
184     end
185   end
186   $saved_indirection_state = nil
187 
188   # Can't use Puppet.features.microsoft_windows? as it may be mocked out in a test.  This can cause test recurring test failures
189   if (!!File::ALT_SEPARATOR)
190     mode = :windows
191   else
192     mode = :posix
193   end
194   # Restore the global process environment.  Can't just assign because this
195   # is a magic variable, sadly, and doesn't do that™.  It is sufficiently
196   # faster to use the compare-then-set model to avoid excessive work that it
197   # justifies the complexity.  --daniel 2012-03-15
198   unless Puppet::Util.get_environment(mode) == $old_env
199     Puppet::Util.clear_environment(mode)
200     $old_env.each {|k, v| Puppet::Util.set_env(k, v, mode) }
201   end
202 
203   # Clear all environments
204   Puppet.lookup(:environments).clear_all
205 
206   # Restore the load_path late, to avoid messing with stubs from the test.
207   $LOAD_PATH.clear
208   $old_load_path.each {|x| $LOAD_PATH << x }
209 
210   Puppet.rollback_context(ROLLBACK_MARK)
211 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
70 def self.before_all_tests()
71   # The process environment is a shared, persistent resource.
72   # Can't use Puppet.features.microsoft_windows? as it may be mocked out in a test.  This can cause test recurring test failures
73   if (!!File::ALT_SEPARATOR)
74     mode = :windows
75   else
76     mode = :posix
77   end
78   $old_env = Puppet::Util.get_environment(mode)
79 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
 94 def self.before_each_test()
 95   # When using both rspec-puppet and puppet-rspec-helper, there are two packages trying
 96   # to be helpful and orchestrate the callback sequence. We let only the first win, the
 97   # second callback results in a no-op.
 98   # Likewise when entering after_each_test(), a check is made to make tear down happen
 99   # only once.
100   #
101   return unless @@reentry_count == 0
102   @@reentry_count = 1
103 
104   Puppet.mark_context(ROLLBACK_MARK)
105 
106   # We need to preserve the current state of all our indirection cache and
107   # terminus classes.  This is pretty important, because changes to these
108   # are global and lead to order dependencies in our testing.
109   #
110   # We go direct to the implementation because there is no safe, sane public
111   # API to manage restoration of these to their default values.  This
112   # should, once the value is proved, be moved to a standard API on the
113   # indirector.
114   #
115   # To make things worse, a number of the tests stub parts of the
116   # indirector.  These stubs have very specific expectations that what
117   # little of the public API we could use is, well, likely to explode
118   # randomly in some tests.  So, direct access.  --daniel 2011-08-30
119   $saved_indirection_state = {}
120   indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections)
121   indirections.each do |indirector|
122     $saved_indirection_state[indirector.name] = {
123       :@terminus_class => indirector.instance_variable_get(:@terminus_class).value,
124       :@cache_class    => indirector.instance_variable_get(:@cache_class).value,
125       # dup the termini hash so termini created and registered during
126       # the test aren't stored in our saved_indirection_state
127       :@termini        => indirector.instance_variable_get(:@termini).dup
128     }
129   end
130 
131   # So is the load_path
132   $old_load_path = $LOAD_PATH.dup
133 
134   initialize_settings_before_each()
135 
136   Puppet.push_context(
137     {
138       trusted_information:
139         Puppet::Context::TrustedInformation.new('local', 'testing', {}, { "trusted_testhelper" => true }),
140       ssl_context: Puppet::SSL::SSLContext.new(cacerts: []).freeze,
141       http_session: proc { Puppet.runtime[:http].create_session }
142     },
143     "Context for specs")
144 
145   Puppet.runtime.clear
146   Puppet::Parser::Functions.reset
147   Puppet::Application.clear!
148   Puppet::Util::Profiler.clear
149 
150   Puppet::Node::Facts.indirection.terminus_class = :memory
151   facts = Puppet::Node::Facts.new(Puppet[:node_name_value])
152   Puppet::Node::Facts.indirection.save(facts)
153 
154   Puppet.clear_deprecation_warnings
155 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
36 def self.initialize()
37   # This meta class instance variable is used as a guard to ensure that
38   # before_each, and after_each are only called once. This problem occurs
39   # when there are more than one puppet test infrastructure orchestrator in use.
40   # The use of both puppetabs-spec_helper, and rodjek-rspec_puppet will cause
41   # two resets of the puppet environment, and will cause problem rolling back to
42   # a known point as there is no way to differentiate where the calls are coming
43   # from. See more information in #before_each_test, and #after_each_test
44   # Note that the variable is only initialized to 0 if nil. This is important
45   # as more than one orchestrator will call initialize. A second call can not
46   # simply set it to 0 since that would potentially destroy an active guard.
47   #
48   @@reentry_count ||= 0
49 
50   @environmentpath = Dir.mktmpdir('environments')
51   Dir.mkdir("#{@environmentpath}/production")
52   owner = Process.pid
53   Puppet.push_context(Puppet.base_context({
54     :environmentpath => @environmentpath,
55     :basemodulepath => "",
56   }), "Initial for specs")
57   Puppet::Parser::Functions.reset
58 
59   ObjectSpace.define_finalizer(Puppet.lookup(:environments), proc {
60     if Process.pid == owner
61       FileUtils.rm_rf(@environmentpath)
62     end
63   })
64   Puppet::SSL::Oids.register_puppet_oids
65 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
219 def self.app_defaults_for_tests()
220   {
221       :logdir     => "/dev/null",
222       :confdir    => "/dev/null",
223       :publicdir  => "/dev/null",
224       :codedir    => "/dev/null",
225       :vardir     => "/dev/null",
226       :rundir     => "/dev/null",
227       :hiera_config => "/dev/null",
228   }
229 end
initialize_settings_before_each() click to toggle source
    # File lib/puppet/test/test_helper.rb
232 def self.initialize_settings_before_each()
233   Puppet.settings.preferred_run_mode = "user"
234   # Initialize "app defaults" settings to a good set of test values
235   Puppet.settings.initialize_app_defaults(app_defaults_for_tests)
236 
237   # We don't want to depend upon the reported domain name of the
238   # machine running the tests, nor upon the DNS setup of that
239   # domain.
240   Puppet.settings[:use_srv_records] = false
241 
242   # Longer keys are secure, but they sure make for some slow testing - both
243   # in terms of generating keys, and in terms of anything the next step down
244   # the line doing validation or whatever.  Most tests don't care how long
245   # or secure it is, just that it exists, so these are better and faster
246   # defaults, in testing only.
247   #
248   # I would make these even shorter, but OpenSSL doesn't support anything
249   # below 512 bits.  Sad, really, because a 0 bit key would be just fine.
250   Puppet[:keylength] = 512
251 
252   # Although we setup a testing context during initialization, some tests
253   # will end up creating their own context using the real context objects
254   # and use the setting for the environments. In order to avoid those tests
255   # having to deal with a missing environmentpath we can just set it right
256   # here.
257   Puppet[:environmentpath] = @environmentpath
258   Puppet[:environment_timeout] = 0
259 end