golden_child 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3b5bd24b65d42730252fa72b8c65690b45376497
4
+ data.tar.gz: 764d9fc4dbffc4d99be6deaad9c92b90d5b63802
5
+ SHA512:
6
+ metadata.gz: 13feb14a993bdb4611918dc97f21e39eff26fab99ed5924de3dad980becdd51c5f5bbd649cf75c941e9504238b4878fab28a1aad5fcf8ba879b835df5a9406cc
7
+ data.tar.gz: d3c5b0fdd7884fdf710b73d0cf4211520ef96e0f469bb187789bb8c040fa81bfc90eda2878ad55b603b7faecf86e3d8cdb22cb79acc45a3539ae19a0fc3a3cd7
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in golden_child.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Avdi Grimm
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # GoldenChild
2
+
3
+ Some helpers for doing golden master testing in Ruby.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'golden_child'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install golden_child
20
+
21
+ ## Usage
22
+
23
+ TBD.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'golden_child/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "golden_child"
8
+ spec.version = GoldenChild::VERSION
9
+ spec.authors = ["Avdi Grimm"]
10
+ spec.email = ["avdi@avdi.org"]
11
+ spec.summary = %q{Some helpers for golden master testing in Ruby.}
12
+ spec.description = %q{}
13
+ spec.homepage = "https://github.com/avdi/golden_master"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
@@ -0,0 +1,80 @@
1
+ require "golden_child/version"
2
+ require "golden_child/helpers"
3
+ require "golden_child/scenario"
4
+ require "golden_child/configuration"
5
+ require "fileutils"
6
+ require "pathname"
7
+ require "yaml/store"
8
+ require "forwardable"
9
+
10
+ module GoldenChild
11
+ class Error < StandardError;
12
+ end
13
+ class UserError < Error;
14
+ end
15
+
16
+ extend FileUtils
17
+ extend SingleForwardable
18
+
19
+ def_delegators :configuration, :golden_path, :project_root, :master_root,
20
+ :actual_root
21
+ def_delegators :configuration, :get_path_for_shortcode
22
+
23
+ # @return [GoldenChild::Configuration]
24
+ def self.configuration
25
+ @configuration ||= Configuration.new
26
+ end
27
+
28
+ # @yield [GoldenChild::Configuration] the global configuration
29
+ def self.configure
30
+ yield configuration
31
+ end
32
+
33
+ # @param [Array<String, Pathname>] paths or shortcodes for files to accept
34
+ def self.accept(*filenames)
35
+ filenames.each do |fn|
36
+ accept_file(fn)
37
+ end
38
+ end
39
+
40
+ def self.remove(*filenames)
41
+ filenames.each do |fn|
42
+ remove_master_file(fn)
43
+ end
44
+ end
45
+
46
+ def self.accept_file(path_or_shortcode)
47
+ path = resolve_path(path_or_shortcode)
48
+ master_path = find_master_for(path)
49
+ mkpath master_path.dirname
50
+ cp path, master_path
51
+ end
52
+
53
+
54
+ def self.remove_master_file(path_or_shortcode)
55
+ path = resolve_path(path_or_shortcode)
56
+ master_path = find_master_for(path)
57
+ rm master_path
58
+ end
59
+
60
+ # @return [Pathname]
61
+ def self.resolve_path(path_or_shortcode)
62
+ path = case path_or_shortcode
63
+ when /^@\d+$/
64
+ get_path_for_shortcode(path_or_shortcode)
65
+ else
66
+ path_or_shortcode
67
+ end
68
+ Pathname(path)
69
+ end
70
+
71
+ def self.find_master_for(path)
72
+ raise UserError, "No such file #{path}" unless path.exist?
73
+ raise UserError, "Not a file: #{path}" unless path.file?
74
+ rel_path = path.relative_path_from(actual_root)
75
+ unless rel_path
76
+ raise UserError, "File #{path} is not in #{actual_root}"
77
+ end
78
+ master_root + rel_path
79
+ end
80
+ end
@@ -0,0 +1,19 @@
1
+ module GoldenChild
2
+ BlockContentFilter = Struct.new(:patterns, :block) do
3
+ # @return [true, false] whether any of the patterns match
4
+ def ===(filename)
5
+ patterns.any?{|pattern|
6
+ case pattern
7
+ when String
8
+ File.fnmatch(pattern, filename.to_s)
9
+ else
10
+ pattern === filename.to_s
11
+ end
12
+ }
13
+ end
14
+
15
+ def call(file_content)
16
+ block.call(file_content)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,84 @@
1
+ require "golden_child/block_content_filter"
2
+
3
+ module GoldenChild
4
+ class Configuration
5
+ # @return [Pathname] the directory in which "actual" results will be generated
6
+ def actual_root
7
+ golden_path + "actual"
8
+ end
9
+
10
+ # @return [Pathname] the base directory for gold master dirs
11
+ def master_root
12
+ golden_path + "master"
13
+ end
14
+
15
+ # @return [Pathname] the root directory for everything GoldenChild does
16
+ def golden_path
17
+ Pathname("spec/golden")
18
+ end
19
+
20
+ # @return [Pathname] the root directory of the current project
21
+ def project_root
22
+ @project_root ||= Pathname.pwd
23
+ end
24
+
25
+ # @param [String, Pathname] new_root set the project root directory
26
+ def project_root=(new_root)
27
+ @project_root = Pathname(new_root).expand_path
28
+ end
29
+
30
+ # @return [Hash] The global, editable set of default env vars
31
+ def env
32
+ @env ||= {}
33
+ end
34
+
35
+ # @return [Enumerable<BlockContentFilter>] the configured
36
+ # content filters
37
+ def content_filters
38
+ @content_filters ||= []
39
+ end
40
+
41
+ # Add a filter for a given file pattern. Filters are useful for removing
42
+ # volatile information (like timestamps) from the files to be compared.
43
+ # **NOTE:** filters are currently **not** suitable for removing sensitive
44
+ # information, since they are only applied when diffing files. The files
45
+ # on disk are not filtered.
46
+ #
47
+ # @param [Array<String, [#===]>] patterns Filename patterns that
48
+ # determine which files this filter should be applied to. Strings are
49
+ # treated as file glob patterns.
50
+ # @yieldparam [String] file_content the contents of the file
51
+ # @yieldreturn [String] the filtered file contents
52
+ def add_content_filter(*patterns, &filter)
53
+ content_filters << BlockContentFilter.new(patterns, filter)
54
+ end
55
+
56
+ # @param [String] code
57
+ # @return [String] path to the corresponding file
58
+ # @raise [UserError] if the shortcode is not found
59
+ #
60
+ # TODO: Move this out of Configuration
61
+ def get_path_for_shortcode(code)
62
+ value = code[/\d+/].to_i
63
+ state_transaction(read_only: true) do |store|
64
+ store[:shortcode_map].invert.fetch(value) do
65
+ fail UserError, "Shortcode not found: #{code}"
66
+ end
67
+ end
68
+ end
69
+
70
+ # @yield [YAML::Store]
71
+ #
72
+ # TODO: Move this out of Configuration
73
+ def state_transaction(read_only: false)
74
+ config_dir = project_root + ".golden_child"
75
+ mkpath config_dir unless config_dir.exist?
76
+ state_db = config_dir + "state.yaml"
77
+ store = YAML::Store.new(state_db)
78
+ store.transaction(read_only) do
79
+ yield store
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,30 @@
1
+ require "fileutils"
2
+
3
+ module GoldenChild
4
+ module Helpers
5
+ include FileUtils
6
+
7
+ attr_writer :scenario
8
+
9
+ # @return [Scenario] the currently active scenario
10
+ def scenario
11
+ @scenario or fail "You must set the scenario first"
12
+ end
13
+
14
+ # (see Scenario#populate_from)
15
+ def populate_from(source_dir)
16
+ scenario.populate_from(source_dir, caller)
17
+ end
18
+
19
+ # (see Scenario#run)
20
+ def run(*args, ** options, &block)
21
+ scenario.run(*args, caller: caller, ** options)
22
+ end
23
+
24
+ # (see Scenario#within_zip)
25
+ def within_zip(*args, &block)
26
+ scenario.within_zip(*args, &block)
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ require "golden_child"
2
+
3
+ module GoldenChild::RspecMatchers
4
+ extend RSpec::Matchers::DSL
5
+
6
+ matcher :match_master do |**options|
7
+ match do |actual|
8
+ @result = scenario.validate(*actual, **options)
9
+ @result.passed?
10
+ end
11
+
12
+ failure_message do |actual|
13
+ @result.message
14
+ end
15
+ end
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.include GoldenChild::Helpers, golden: true
20
+ config.include GoldenChild::RspecMatchers, golden: true
21
+
22
+ config.before(:example, golden: true) do |example|
23
+ # If it looks like an RSpec-Given example, use the example group name.
24
+ #
25
+ # In RSpec-Given the group names the scenario,and the example names are
26
+ # messy source code.
27
+ #
28
+ # TODO: Determine if this is a problem for nested RSpec-Given groups
29
+ scenario_name = if example.description =~ /^\s*Then\b/
30
+ example.example_group.description
31
+ else
32
+ example.full_description
33
+ end
34
+ self.scenario = GoldenChild::Scenario.new(name: scenario_name)
35
+ example.metadata[:golden_child_scenario] = scenario
36
+ scenario.setup
37
+ end
38
+
39
+ config.after(:example, golden: true) do |example|
40
+ scenario.teardown
41
+ end
42
+ end
43
+
@@ -0,0 +1,273 @@
1
+ require "fileutils"
2
+ require "yaml"
3
+ require "open3"
4
+ require "rake/file_list"
5
+ require "forwardable"
6
+ require "rspec/support"
7
+
8
+ module GoldenChild
9
+ class Scenario
10
+ include FileUtils
11
+ extend Forwardable
12
+
13
+ attr_reader :name, :command_history, :configuration
14
+
15
+ # @!method project_root
16
+ # (see Configuration#project_root)
17
+ def_delegators :configuration, :golden_path, :actual_root, :project_root,
18
+ :content_filters
19
+
20
+ # @abstract
21
+ Validation = Struct.new(:message)
22
+
23
+ class FailedValidation < Validation
24
+ def passed?
25
+ false
26
+ end
27
+ end
28
+ class PassedValidation < Validation
29
+ def passed?
30
+ true
31
+ end
32
+ end
33
+
34
+ # @param [String] name The name of the scenario (e.g. RSpec example group)
35
+ def initialize(name:, configuration: ::GoldenChild.configuration)
36
+ @name = name
37
+ @command_history = []
38
+ @configuration = configuration
39
+ end
40
+
41
+ # Recursively populate the current scenario with copies of files from
42
+ # `source_dir`
43
+ #
44
+ # @param [String, Pathname] source_dir
45
+ # @param [Array] caller
46
+ def populate_from(source_dir, caller=caller)
47
+ Dir.chdir(project_root) do
48
+ raise "Scenario has not been set up" unless actual_path.exist?
49
+ source_dir = Pathname(source_dir)
50
+ unless source_dir.directory?
51
+ fail RuntimeError, "No such directory #{source_dir}", caller
52
+ end
53
+
54
+ Dir.foreach(source_dir) do |entry|
55
+ next if %w[. ..].include?(entry)
56
+ copy_entry source_dir + entry, actual_path + entry
57
+ end
58
+ end
59
+ end
60
+
61
+ # Run a command in the context of the current scenario.
62
+ #
63
+ # @param [Array] args The command. See {Process.spawn} for the various
64
+ # forms supported. Note that any environment should be passed via the
65
+ # `env` option below.
66
+ # @param [true, false] allow_fail (false) Whether to raise an exception
67
+ # if the command fails.
68
+ # @param [Hash] env Environment variables for the command
69
+ # @param [Array] caller
70
+ # @param [Hash] options
71
+ def run(*args, allow_fail: false, env: self.env, caller: caller, ** options)
72
+ options[:chdir] ||= actual_path.to_s
73
+ env = env.map { |k, v| [k.to_s, v.to_s] }.to_h
74
+ stdout, stderr, status = Open3.capture3(env, *args, ** options)
75
+ command_history.push(
76
+ command: args, status: status, stdout: stdout, stderr: stderr)
77
+ command_log = ""
78
+ command_log << "\nCommand: #{args}"
79
+ command_log << "\nEnvironment:"
80
+ env.each_pair do |key, value|
81
+ command_log << "\n #{key}=#{value}"
82
+ end
83
+ command_log << "\nExited with status #{status.exitstatus}"
84
+ command_log << "\n========== Command STDOUT ==========\n"
85
+ command_log << stdout
86
+ command_log << "\n========== End STDOUT ==========\n"
87
+ command_log << "\n========== Command STDERR ==========\n"
88
+ command_log << stderr
89
+ command_log << "\n========== End STDERR ==========\n"
90
+ (control_dir + "commands.log").open("a") do |f|
91
+ f.write(command_log)
92
+ end
93
+ unless status.success? || allow_fail
94
+ fail RuntimeError, command_log, caller
95
+ end
96
+ end
97
+
98
+ # Unzip a zip file, and execute commands in the context of the unzipped
99
+ # directory.
100
+ #
101
+ # You must have the `unzip(1)` program installed for this to work.helper
102
+ #
103
+ # @param [String, Pathname] relative_filename The path of the zip file
104
+ def within_zip(relative_filename)
105
+ relative_filename = Pathname(relative_filename)
106
+ filename = actual_path + relative_filename
107
+ raise "Zip file not found: #{filename}" unless filename.exist?
108
+ unzip_dir = unzip_dir_for(relative_filename)
109
+ mkpath unzip_dir
110
+ unzip_succeeded =
111
+ system(*%W[unzip -qq #{filename} -d #{unzip_dir}])
112
+ raise "Could not unzip #{filename}" unless unzip_succeeded
113
+ push_working_dir(unzip_dir.relative_path_from(current_actual_path)) do
114
+ yield(unzip_dir)
115
+ end
116
+ end
117
+
118
+ # @return [Pathname]
119
+ def unzip_dir_for(relative_filename)
120
+ current_actual_path + (relative_filename.to_s + ".golden_child_unzip")
121
+ end
122
+
123
+ # Verify that `files` are identical to their corresponding gold master
124
+ # files.
125
+ def validate(*files, ** options)
126
+ paths = Rake::FileList[*files.map(&:to_s)]
127
+ pass = true
128
+ message = "No files to validate"
129
+ Dir.chdir(project_root) do
130
+ paths.each do |path|
131
+ master_file = current_master_path + path
132
+ actual_file = current_actual_path + path
133
+ shortcode = get_shortcode_for(actual_file)
134
+ approval_cmd = "golden accept #{shortcode}"
135
+ message = ""
136
+ file_pass = false
137
+ if !actual_file.exist?
138
+ message << "Expected file: #{actual_file}"
139
+ message << "\nto be created, but it was not."
140
+ elsif !actual_file.file?
141
+ message << "Expected: #{actual_file}"
142
+ message << "\n to be a file, but it is a #{actual_file.ftype}."
143
+ elsif !master_file.exist?
144
+ message << "Master: #{master_file}"
145
+ message << "\ndoes not yet exist."
146
+ message << "\nActual file: #{actual_file}"
147
+ message << "\nhas the following content:\n\n"
148
+ message << filter_file(actual_file)
149
+ message << "\n\nIf this looks correct, run `#{approval_cmd}`"
150
+ elsif !master_file.file?
151
+ message << "Master: #{master_file}"
152
+ message << "must be a file, but it is a #{master_file.ftype}."
153
+ elsif filtered_files_differ?(master_file, actual_file)
154
+ message << "Actual: #{actual_file}"
155
+ message << "\ndiffers from master: #{master_file}"
156
+ message << "\n"
157
+ message << diff(master_file, actual_file)
158
+ message << "\n\nIf the changes look correct, run `#{approval_cmd}`"
159
+ else
160
+ message << "Actual file #{actual_file} matches master #{master_file}"
161
+ file_pass = true
162
+ end
163
+ unless file_pass
164
+ pass = false
165
+ break
166
+ end
167
+ end
168
+ end
169
+ if pass
170
+ PassedValidation.new(message)
171
+ else
172
+ FailedValidation.new(message)
173
+ end
174
+ end
175
+
176
+ def filtered_files_differ?(master_file, actual_file)
177
+ filter_file(master_file) != filter_file(actual_file)
178
+ end
179
+
180
+ def diff(master_file, actual_file)
181
+ differ = RSpec::Support::Differ.new
182
+ differences = differ.diff(filter_file(actual_file), filter_file(master_file))
183
+ differences.empty? ? nil : differences
184
+ end
185
+
186
+ def filter_file(filename)
187
+ @filtered_files ||= {} # memoization
188
+ @filtered_files.fetch(filename) {
189
+ @filtered_files[filename] = content_filters.reduce(filename.read) {
190
+ |content, filter|
191
+ filter.call(content)
192
+ }
193
+ }
194
+ end
195
+
196
+ def get_shortcode_for(actual_file)
197
+ code = state_transaction do |store|
198
+ shortcode_map = (store[:shortcode_map] ||= {})
199
+ shortcode_map.fetch(actual_file.to_s) {
200
+ new_code = shortcode_map.values.max.to_i + 1
201
+ shortcode_map[actual_file.to_s] = new_code
202
+ }
203
+ end
204
+ "@#{code}"
205
+ end
206
+
207
+ def setup
208
+ mkpath master_path.parent
209
+ rmtree actual_path
210
+ mkpath actual_path
211
+ mkpath control_dir
212
+ end
213
+
214
+ def teardown
215
+ end
216
+
217
+ def actual_path
218
+ actual_root + relative_path
219
+ end
220
+
221
+ alias_method :root, :actual_path
222
+
223
+ def master_path
224
+ golden_path + "master" + relative_path
225
+ end
226
+
227
+ def current_actual_path
228
+ actual_path + current_working_dir
229
+ end
230
+
231
+ def current_master_path
232
+ master_path + current_working_dir
233
+ end
234
+
235
+ def control_dir
236
+ actual_path + ".golden_child"
237
+ end
238
+
239
+ def relative_path
240
+ slug
241
+ end
242
+
243
+ def slug
244
+ name.downcase.tr_s("^a-z0-9", "-")[0..255]
245
+ end
246
+
247
+ # @return [Hash] editable env var hash, defaults to {#configuration}
248
+ def env
249
+ @env ||= configuration.env.dup
250
+ end
251
+
252
+ private
253
+
254
+ def_delegators :configuration, :state_transaction, :get_path_for_shortcode
255
+
256
+ def push_working_dir(new_dir)
257
+ dir_stack = working_dir_stack
258
+ dir_stack.push(new_dir)
259
+ yield
260
+ ensure
261
+ dir_stack.pop
262
+ end
263
+
264
+ def working_dir_stack
265
+ Thread.current[:golden_child_working_dir] ||= []
266
+ end
267
+
268
+ def current_working_dir
269
+ last_dir = working_dir_stack.last
270
+ last_dir || "."
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,3 @@
1
+ module GoldenChild
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: golden_child
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Avdi Grimm
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: ''
42
+ email:
43
+ - avdi@avdi.org
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - golden_child.gemspec
54
+ - lib/golden_child.rb
55
+ - lib/golden_child/block_content_filter.rb
56
+ - lib/golden_child/configuration.rb
57
+ - lib/golden_child/helpers.rb
58
+ - lib/golden_child/rspec.rb
59
+ - lib/golden_child/scenario.rb
60
+ - lib/golden_child/version.rb
61
+ homepage: https://github.com/avdi/golden_master
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.2.2
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Some helpers for golden master testing in Ruby.
85
+ test_files: []