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 79 def self.after_all_tests() 80 end
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
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
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
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
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
# 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