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
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
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
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
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
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
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
# 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