test-kitchen 1.4.2 → 1.5.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +27 -2
  4. data/CHANGELOG.md +40 -1
  5. data/Gemfile.proxy_tests +6 -0
  6. data/features/kitchen_driver_discover_command.feature +6 -0
  7. data/features/kitchen_init_command.feature +5 -3
  8. data/features/step_definitions/gem_steps.rb +12 -4
  9. data/features/step_definitions/git_steps.rb +1 -1
  10. data/features/step_definitions/output_steps.rb +1 -1
  11. data/features/support/env.rb +13 -9
  12. data/lib/kitchen/cli.rb +3 -0
  13. data/lib/kitchen/command/driver_discover.rb +8 -0
  14. data/lib/kitchen/configurable.rb +32 -0
  15. data/lib/kitchen/driver/ssh_base.rb +18 -4
  16. data/lib/kitchen/generator/driver_create.rb +2 -2
  17. data/lib/kitchen/lazy_hash.rb +20 -0
  18. data/lib/kitchen/provisioner/base.rb +16 -1
  19. data/lib/kitchen/provisioner/chef_base.rb +42 -98
  20. data/lib/kitchen/provisioner/chef_solo.rb +6 -4
  21. data/lib/kitchen/provisioner/chef_zero.rb +7 -5
  22. data/lib/kitchen/transport/winrm.rb +36 -2
  23. data/lib/kitchen/verifier/base.rb +18 -1
  24. data/lib/kitchen/verifier/busser.rb +12 -5
  25. data/lib/kitchen/verifier/shell.rb +101 -0
  26. data/lib/kitchen/version.rb +1 -1
  27. data/spec/kitchen/configurable_spec.rb +211 -2
  28. data/spec/kitchen/driver/ssh_base_spec.rb +131 -8
  29. data/spec/kitchen/lazy_hash_spec.rb +27 -0
  30. data/spec/kitchen/provisioner/base_spec.rb +25 -0
  31. data/spec/kitchen/provisioner/chef_base_spec.rb +133 -252
  32. data/spec/kitchen/provisioner/chef_solo_spec.rb +17 -0
  33. data/spec/kitchen/provisioner/chef_zero_spec.rb +14 -2
  34. data/spec/kitchen/provisioner/shell_spec.rb +60 -12
  35. data/spec/kitchen/transport/winrm_spec.rb +50 -0
  36. data/spec/kitchen/verifier/base_spec.rb +26 -0
  37. data/spec/kitchen/verifier/busser_spec.rb +69 -3
  38. data/spec/kitchen/verifier/shell_spec.rb +157 -0
  39. data/support/busser_install_command.sh +1 -2
  40. data/test-kitchen.gemspec +10 -2
  41. metadata +86 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed6240c00a8d6ca35986ab16735134c1fe4368b1
4
- data.tar.gz: 3b4324d554794093cc15ebd2f82e9bca3c426074
3
+ metadata.gz: adede1e7727afe32740d4f4b60452b704f4326ee
4
+ data.tar.gz: cfd3c1e7ea3517a9d9bbdc8b3b1e135dd546f26b
5
5
  SHA512:
6
- metadata.gz: d1949f7688de24d288aa21d15f4d1bc80dc049937b60ab56e3ac445211e7c68c57fcb7c2b1f3fc93fa25e341f2dd9907494ecdcfcda4518dd2939cbed22fec3f
7
- data.tar.gz: a8713a783d3ff9b2dff382e790a1496ec75b1f305bab75b02d0a5a36f505995c4b998f2a0a057d6d7443371757b50d69aa732b1d16a467211378500b021836d3
6
+ metadata.gz: 3d3dfad8e69eb236fd38ca05e233245342140823b94cf9d7cea0b56f5ec6bbae1aba59494c728fe768dc29e51fd3ee817e764836751e1187f996f6566385b758
7
+ data.tar.gz: 4cb4852bb1977639bbb5068420ba26c170f5594d1e21b12be1dec4c6cd6a464ab7426640324066e7545b768c8865d401d1a5d6969f97fc2cdb200fd064ad6020
data/.gitignore CHANGED
@@ -3,7 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
6
+ Gemfile*.lock
7
7
  InstalledFiles
8
8
  _yardoc
9
9
  coverage
data/.travis.yml CHANGED
@@ -1,10 +1,9 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 2.2
4
+ - 2.2.3
5
5
  - 2.1
6
6
  - 2.0.0
7
- - 1.9.3
8
7
  - ruby-head
9
8
 
10
9
  env:
@@ -24,6 +23,32 @@ bundler_args: --without guard
24
23
  sudo: false
25
24
 
26
25
  matrix:
26
+ include:
27
+ - rvm: 2.2
28
+ sudo: required
29
+ dist: trusty
30
+ # To run the proxy tests we need additional gems than what Test Kitchen normally uses
31
+ # for testing
32
+ gemfile: Gemfile.proxy_tests
33
+ before_install:
34
+ - sudo apt-get update
35
+ - sudo apt-get -y install squid3 git curl
36
+ env:
37
+ # ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']
38
+ - secure: "E7h427Y8z6Nx5sdrA4Ye9gO9pDGpe4jxfhJdM4HPJIM/FSkEJM0ZDPdvhQ6QAQ1S1AHja/JhQUFaNP5NzKhRkVqGCzunBBC6RVb7pp1Qo16EMhJkg7LEsExiSsjQykAzt2w6K/71V+mIlMKxCPYdpu6PT/8w/jZFgBqLEHXuogU="
39
+ - global:
40
+ - PROXY_TESTS_DIR=proxy_tests/files/default/scripts
41
+ - PROXY_TESTS_REPO=$PROXY_TESTS_DIR/repo
42
+
43
+ script:
44
+ - bundle exec kitchen --version
45
+ # TODO switch to master when https://github.com/chef/proxy_tests/pull/12 is merged
46
+ - git clone https://github.com/chef/proxy_tests.git
47
+ - rvmsudo -E bundle exec bash $PROXY_TESTS_DIR/run_tests.sh kitchen \* \* /tmp/out.txt
48
+ after_script:
49
+ - cat /tmp/out.txt
50
+ - sudo cat /var/log/squid3/cache.log
51
+ - sudo cat /var/log/squid3/access.log
27
52
  allow_failures:
28
53
  - rvm: ruby-head
29
54
  exclude:
data/CHANGELOG.md CHANGED
@@ -1,4 +1,29 @@
1
- ## 1.4.2
1
+ ## 1.5.0
2
+
3
+ ### Potentially breaking changes
4
+
5
+ ### Bug fixes
6
+
7
+ * PR [#816][]: Fix SuSe OS Busser install ([@Peuserik][])
8
+ * PR [#833][]: Updates the gem path to install everything in /tmp/verifier ([@scotthain][])
9
+ * PR [#878][]: write install_command to file and invoke on the instance to avoid command too long on windows ([@mwrock][])
10
+
11
+ ### New features
12
+
13
+ * PR [#741][]: Add shell verifier ([@sawanoboly][])
14
+ * PR [#752][]: Make lazyhash enumerable ([@caboteria][])
15
+ * PR [#892][]: Adding proxy tests to the Travis.yml ([@tyler-ball][])
16
+ * PR [#895][]: Adding in ChefConfig support to enable loading proxy config from chef config files ([@tyler-ball][])
17
+
18
+ ### Improvements
19
+
20
+ * PR [#782][]: Use [`mixlib-install`](https://github.com/chef/mixlib-install) to generate chef install command. ([@thommay][])
21
+ * PR [#804][]: Drop Ruby 1.9 from TravisCI build matrix ([@thommay][])
22
+ * PR [#813][]: Honor proxy ENV variables ([@mcquin][])
23
+ * PR [#885][]: Running the chef_base provisioner install_command via sudo, and command_prefix support ([@adamleff][])
24
+ * PR [#896][]: Fixing garbled output for chef_zero provisioner ([@someara][])
25
+
26
+ ## 1.4.2 / 2015-08-03
2
27
 
3
28
  ### Potentially breaking changes
4
29
 
@@ -769,12 +794,26 @@ The initial release.
769
794
  [#734]: https://github.com/test-kitchen/test-kitchen/issues/734
770
795
  [#736]: https://github.com/test-kitchen/test-kitchen/issues/736
771
796
  [#737]: https://github.com/test-kitchen/test-kitchen/issues/737
797
+ [#741]: https://github.com/test-kitchen/test-kitchen/issues/741
798
+ [#752]: https://github.com/test-kitchen/test-kitchen/issues/752
799
+ [#782]: https://github.com/test-kitchen/test-kitchen/issues/782
772
800
  [#801]: https://github.com/test-kitchen/test-kitchen/issues/801
773
801
  [#802]: https://github.com/test-kitchen/test-kitchen/issues/802
802
+ [#804]: https://github.com/test-kitchen/test-kitchen/issues/804
803
+ [#813]: https://github.com/test-kitchen/test-kitchen/issues/813
804
+ [#816]: https://github.com/test-kitchen/test-kitchen/issues/816
805
+ [#833]: https://github.com/test-kitchen/test-kitchen/issues/833
806
+ [#878]: https://github.com/test-kitchen/test-kitchen/issues/878
807
+ [#885]: https://github.com/test-kitchen/test-kitchen/issues/885
808
+ [#892]: https://github.com/test-kitchen/test-kitchen/issues/892
809
+ [#895]: https://github.com/test-kitchen/test-kitchen/issues/895
810
+ [#896]: https://github.com/test-kitchen/test-kitchen/issues/896
774
811
  [@Annih]: https://github.com/Annih
775
812
  [@ChrisLundquist]: https://github.com/ChrisLundquist
776
813
  [@MarkGibbons]: https://github.com/MarkGibbons
814
+ [@Peuserik]: https://github.com/Peuserik
777
815
  [@adamhjk]: https://github.com/adamhjk
816
+ [@adamleff]: https://github.com/adamleff
778
817
  [@afiune]: https://github.com/afiune
779
818
  [@arangamani]: https://github.com/arangamani
780
819
  [@arunthampi]: https://github.com/arunthampi
@@ -0,0 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
2
+ eval_gemfile File.join(File.dirname(__FILE__), "Gemfile")
3
+
4
+ gem "kitchen-ec2"
5
+ # TODO when this is released in Chef 12.6.0 we need to depend on that
6
+ gem "chef-config", github: "chef/chef", submodules: true
@@ -17,3 +17,9 @@ Feature: Search RubyGems to discover new Test Kitchen Driver gems
17
17
  When I run `kitchen driver discover`
18
18
  Then the exit status should be 0
19
19
  And the output should contain "kitchen-bluebox"
20
+
21
+ Scenario: Running driver discover with the --chef-config-path parameter loads the chef config
22
+ Given an empty file named "kitchen_client.rb"
23
+ When I run `kitchen driver discover --chef-config-path=kitchen_client.rb`
24
+ Then the exit status should be 0
25
+ And the output should contain "kitchen-bluebox"
@@ -3,6 +3,9 @@ Feature: Add Test Kitchen support to an existing project
3
3
  As an operator
4
4
  I want to run a command to initialize my project
5
5
 
6
+ Background:
7
+ Given a sandboxed GEM_HOME directory named "kitchen-init"
8
+
6
9
  @spawn
7
10
  Scenario: Displaying help
8
11
  When I run `kitchen help init`
@@ -15,8 +18,7 @@ Feature: Add Test Kitchen support to an existing project
15
18
 
16
19
  @spawn
17
20
  Scenario: Running init with default values
18
- Given a sandboxed GEM_HOME directory named "kitchen-init"
19
- And I have a git repository
21
+ Given I have a git repository
20
22
  When I run `kitchen init`
21
23
  Then the exit status should be 0
22
24
  And a directory named "test/integration/default" should exist
@@ -173,7 +175,7 @@ Feature: Add Test Kitchen support to an existing project
173
175
  """
174
176
 
175
177
  Scenario: Running without git doesn't make a .gitignore
176
- When I successfully run `kitchen init`
178
+ When I successfully run `kitchen init --no-driver`
177
179
  Then the exit status should be 0
178
180
  And a file named ".gitignore" should not exist
179
181
 
@@ -10,19 +10,27 @@ Given(/^a sandboxed GEM_HOME directory named "(.*?)"$/) do |name|
10
10
  @aruba_timeout_seconds = 30
11
11
 
12
12
  gem_home = Pathname.new(Dir.mktmpdir(name))
13
- ENV["GEM_HOME"] = gem_home.to_s
14
- ENV["GEM_PATH"] = [gem_home.to_s, ENV["GEM_PATH"]].join(":")
13
+ aruba.environment["GEM_HOME"] = gem_home.to_s
14
+ aruba.environment["GEM_PATH"] = [gem_home.to_s, ENV["GEM_PATH"]].join(":")
15
15
  @cleanup_dirs << gem_home
16
16
  end
17
17
 
18
18
  Then(/^a gem named "(.*?)" is installed with version "(.*?)"$/) do |name, version|
19
19
  unbundlerize do
20
- run_simple(unescape("gem list #{name} --version #{version} -i"), true, nil)
20
+ run_simple(
21
+ sanitize_text("gem list #{name} --version #{version} -i"),
22
+ :fail_on_error => true,
23
+ :exit_timeout => nil
24
+ )
21
25
  end
22
26
  end
23
27
 
24
28
  Then(/^a gem named "(.*?)" is installed$/) do |name|
25
29
  unbundlerize do
26
- run_simple(unescape("gem list #{name} -i"), true, nil)
30
+ run_simple(
31
+ sanitize_text("gem list #{name} -i"),
32
+ :fail_on_error => true,
33
+ :exit_timeout => nil
34
+ )
27
35
  end
28
36
  end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  Given(/I have a git repository/) do
4
- create_dir(".git")
4
+ create_directory(".git")
5
5
  end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  Then(/^the stdout should match \/([^\/]*)\/$/) do |expected|
4
- assert_matching_output(expected, all_stdout)
4
+ expect(last_command_started).to have_output_on_stdout(Regexp.new(expected))
5
5
  end
@@ -31,12 +31,12 @@ Before do
31
31
  @aruba_timeout_seconds = 15
32
32
  @cleanup_dirs = []
33
33
 
34
- Aruba::Processes::InProcess.main_class = ArubaHelper
35
- Aruba.process = Aruba::Processes::InProcess
34
+ aruba.config.command_launcher = :in_process
35
+ aruba.config.main_class = ArubaHelper
36
36
  end
37
37
 
38
38
  Before("@spawn") do
39
- Aruba.process = Aruba::Processes::SpawnProcess
39
+ aruba.config.command_launcher = :spawn
40
40
  end
41
41
 
42
42
  After do |s|
@@ -47,25 +47,29 @@ After do |s|
47
47
 
48
48
  # Restore environment variables to their original settings, if they have
49
49
  # been saved off
50
- ENV.keys.select { |key| key =~ /^_CUKE_/ }.each do |backup_key|
51
- ENV[backup_key.sub(/^_CUKE_/, "")] = ENV.delete(backup_key)
52
- end
50
+ env = aruba.environment
51
+ env.to_h.keys.select { |key| key =~ /^_CUKE_/ }.
52
+ each do |backup_key|
53
+ env[backup_key.sub(/^_CUKE_/, "")] = env[backup_key]
54
+ env.delete(backup_key)
55
+ end
53
56
 
54
57
  @cleanup_dirs.each { |dir| FileUtils.rm_rf(dir) }
55
58
  end
56
59
 
57
60
  def backup_envvar(key)
58
- ENV["_CUKE_#{key}"] = ENV[key]
61
+ aruba.environment["_CUKE_#{key}"] = aruba.environment[key]
59
62
  end
60
63
 
61
64
  def restore_envvar(key)
62
- ENV[key] = ENV.delete("_CUKE_#{key}")
65
+ aruba.environment[key] = aruba.environment["_CUKE_#{key}"]
66
+ aruba.environment.delete("_CUKE_#{key}")
63
67
  end
64
68
 
65
69
  def unbundlerize
66
70
  keys = %w[BUNDLER_EDITOR BUNDLE_BIN_PATH BUNDLE_GEMFILE RUBYOPT]
67
71
 
68
- keys.each { |key| backup_envvar(key); ENV.delete(key) }
72
+ keys.each { |key| backup_envvar(key); aruba.environment.delete(key) }
69
73
  yield
70
74
  keys.each { |key| restore_envvar(key) }
71
75
  end
data/lib/kitchen/cli.rb CHANGED
@@ -287,6 +287,9 @@ module Kitchen
287
287
  guarenteed that every result is a driver, but chances are good most
288
288
  relevant drivers will be returned.
289
289
  D
290
+ method_option :chef_config_path,
291
+ :default => nil,
292
+ :desc => "Path to chef config file containing proxy configuration to use"
290
293
  def discover
291
294
  perform("discover", "driver_discover", args)
292
295
  end
@@ -19,6 +19,8 @@
19
19
  require "kitchen/command"
20
20
 
21
21
  require "rubygems/spec_fetcher"
22
+ require "chef-config/config"
23
+ require "chef-config/workstation_config_loader"
22
24
 
23
25
  module Kitchen
24
26
 
@@ -31,6 +33,12 @@ module Kitchen
31
33
 
32
34
  # Invoke the command.
33
35
  def call
36
+ # We are introducing the idea of using the Chef configuration as a
37
+ # unified config for all the ChefDK tools. The first practical
38
+ # implementation of this is 1 location to setup proxy configurations.
39
+ ChefConfig::WorkstationConfigLoader.new(options[:chef_config_path]).load
40
+ ChefConfig::Config.export_proxies
41
+
34
42
  specs = fetch_gem_specs.sort { |x, y| x[0] <=> y[0] }
35
43
  specs = specs[0, 49].push(["...", "..."]) if specs.size > 49
36
44
  specs = specs.unshift(["Gem Name", "Latest Stable Release"])
@@ -295,15 +295,36 @@ module Kitchen
295
295
  # @param code [String] the shell code to be wrapped
296
296
  # @return [String] wrapped shell code
297
297
  # @api private
298
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
299
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
298
300
  def wrap_shell_code(code)
299
301
  env = []
300
302
  if config[:http_proxy]
301
303
  env << shell_env_var("http_proxy", config[:http_proxy])
302
304
  env << shell_env_var("HTTP_PROXY", config[:http_proxy])
305
+ else
306
+ export_proxy(env, "http")
303
307
  end
304
308
  if config[:https_proxy]
305
309
  env << shell_env_var("https_proxy", config[:https_proxy])
306
310
  env << shell_env_var("HTTPS_PROXY", config[:https_proxy])
311
+ else
312
+ export_proxy(env, "https")
313
+ end
314
+ if config[:ftp_proxy]
315
+ env << shell_env_var("ftp_proxy", config[:ftp_proxy])
316
+ env << shell_env_var("FTP_PROXY", config[:ftp_proxy])
317
+ else
318
+ export_proxy(env, "ftp")
319
+ end
320
+ # if http_proxy was set from environment variable or https_proxy was set
321
+ # from environment variable, or ftp_proxy was set from environment
322
+ # variable, include no_proxy environment variable, if set.
323
+ if (!config[:http_proxy] && (ENV["http_proxy"] || ENV["HTTP_PROXY"])) ||
324
+ (!config[:https_proxy] && (ENV["https_proxy"] || ENV["HTTPS_PROXY"])) ||
325
+ (!config[:ftp_proxy] && (ENV["ftp_proxy"] || ENV["FTP_PROXY"]))
326
+ env << shell_env_var("no_proxy", ENV["no_proxy"]) if ENV["no_proxy"]
327
+ env << shell_env_var("NO_PROXY", ENV["NO_PROXY"]) if ENV["NO_PROXY"]
307
328
  end
308
329
  if powershell_shell?
309
330
  env.join("\n").concat("\n").concat(code)
@@ -312,6 +333,17 @@ module Kitchen
312
333
  end
313
334
  end
314
335
 
336
+ # Helper method to export
337
+ #
338
+ # @param env [Array] the environment to modify
339
+ # @param code [String] the type of proxy to export, one of 'http', 'https' or 'ftp'
340
+ # @api private
341
+ def export_proxy(env, type)
342
+ env << shell_env_var("#{type}_proxy", ENV["#{type}_proxy"]) if ENV["#{type}_proxy"]
343
+ env << shell_env_var("#{type.upcase}_PROXY", ENV["#{type.upcase}_PROXY"]) if
344
+ ENV["#{type.upcase}_PROXY"]
345
+ end
346
+
315
347
  # Class methods which will be mixed in on inclusion of Configurable module.
316
348
  module ClassMethods
317
349
 
@@ -233,17 +233,31 @@ module Kitchen
233
233
  [combined[:hostname], combined[:username], opts]
234
234
  end
235
235
 
236
- # Adds http and https proxy environment variables to a command, if set
237
- # in configuration data.
236
+ # Adds http, https and ftp proxy environment variables to a command, if
237
+ # set in configuration data or on local workstation.
238
238
  #
239
239
  # @param cmd [String] command string
240
240
  # @return [String] command string
241
241
  # @api private
242
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
242
243
  def env_cmd(cmd)
243
244
  return if cmd.nil?
244
245
  env = "env"
245
- env << " http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
246
- env << " https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
246
+ http_proxy = config[:http_proxy] || ENV["http_proxy"] ||
247
+ ENV["HTTP_PROXY"]
248
+ https_proxy = config[:https_proxy] || ENV["https_proxy"] ||
249
+ ENV["HTTPS_PROXY"]
250
+ ftp_proxy = config[:ftp_proxy] || ENV["ftp_proxy"] ||
251
+ ENV["FTP_PROXY"]
252
+ no_proxy = if (!config[:http_proxy] && http_proxy) ||
253
+ (!config[:https_proxy] && https_proxy) ||
254
+ (!config[:ftp_proxy] && ftp_proxy)
255
+ ENV["no_proxy"] || ENV["NO_PROXY"]
256
+ end
257
+ env << " http_proxy=#{http_proxy}" if http_proxy
258
+ env << " https_proxy=#{https_proxy}" if https_proxy
259
+ env << " ftp_proxy=#{ftp_proxy}" if ftp_proxy
260
+ env << " no_proxy=#{no_proxy}" if no_proxy
247
261
 
248
262
  env == "env" ? cmd : "#{env} #{cmd}"
249
263
  end
@@ -87,8 +87,8 @@ module Kitchen
87
87
  # @api private
88
88
  def initialize_git
89
89
  inside(target_dir) do
90
- run("git init")
91
- run("git add .")
90
+ run("git init", :capture => true)
91
+ run("git add .", :capture => true)
92
92
  end
93
93
  end
94
94
 
@@ -53,6 +53,7 @@ module Kitchen
53
53
  #
54
54
  # @author Fletcher Nichol <fnichol@nichol.ca>
55
55
  class LazyHash < SimpleDelegator
56
+ include Enumerable
56
57
 
57
58
  # Creates a new LazyHash using a Hash-like object to populate itself and
58
59
  # an object that can be used as context in value-callable blocks. The
@@ -105,6 +106,25 @@ module Kitchen
105
106
  hash
106
107
  end
107
108
 
109
+ # Yields each key/value pair to the provided block. Returns a new
110
+ # Hash with only the keys and rendered values for which the block
111
+ # returns true.
112
+ #
113
+ # @return [Hash] a new hash
114
+ def select(&block)
115
+ to_hash.select(&block)
116
+ end
117
+
118
+ # If no block provided, returns an enumerator over the keys and
119
+ # rendered values in the underlying object. If a block is
120
+ # provided, calls the block once for each [key, rendered_value]
121
+ # pair in the underlying object.
122
+ #
123
+ # @return [Enumerator, Array]
124
+ def each(&block)
125
+ to_hash.each(&block)
126
+ end
127
+
108
128
  private
109
129
 
110
130
  # Returns an object or invokes call with context if object is callable.