rspec-core 3.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.document +5 -0
  5. data/.yardopts +8 -0
  6. data/Changelog.md +2243 -0
  7. data/LICENSE.md +26 -0
  8. data/README.md +384 -0
  9. data/exe/rspec +4 -0
  10. data/lib/rspec/autorun.rb +3 -0
  11. data/lib/rspec/core.rb +185 -0
  12. data/lib/rspec/core/backtrace_formatter.rb +65 -0
  13. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  14. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  15. data/lib/rspec/core/bisect/fork_runner.rb +134 -0
  16. data/lib/rspec/core/bisect/server.rb +61 -0
  17. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  18. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  19. data/lib/rspec/core/bisect/utilities.rb +58 -0
  20. data/lib/rspec/core/configuration.rb +2308 -0
  21. data/lib/rspec/core/configuration_options.rb +233 -0
  22. data/lib/rspec/core/drb.rb +113 -0
  23. data/lib/rspec/core/dsl.rb +98 -0
  24. data/lib/rspec/core/example.rb +656 -0
  25. data/lib/rspec/core/example_group.rb +889 -0
  26. data/lib/rspec/core/example_status_persister.rb +235 -0
  27. data/lib/rspec/core/filter_manager.rb +231 -0
  28. data/lib/rspec/core/flat_map.rb +20 -0
  29. data/lib/rspec/core/formatters.rb +269 -0
  30. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  31. data/lib/rspec/core/formatters/base_formatter.rb +70 -0
  32. data/lib/rspec/core/formatters/base_text_formatter.rb +75 -0
  33. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  34. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  35. data/lib/rspec/core/formatters/console_codes.rb +68 -0
  36. data/lib/rspec/core/formatters/deprecation_formatter.rb +223 -0
  37. data/lib/rspec/core/formatters/documentation_formatter.rb +70 -0
  38. data/lib/rspec/core/formatters/exception_presenter.rb +508 -0
  39. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  40. data/lib/rspec/core/formatters/helpers.rb +110 -0
  41. data/lib/rspec/core/formatters/html_formatter.rb +153 -0
  42. data/lib/rspec/core/formatters/html_printer.rb +414 -0
  43. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  44. data/lib/rspec/core/formatters/json_formatter.rb +102 -0
  45. data/lib/rspec/core/formatters/profile_formatter.rb +68 -0
  46. data/lib/rspec/core/formatters/progress_formatter.rb +29 -0
  47. data/lib/rspec/core/formatters/protocol.rb +182 -0
  48. data/lib/rspec/core/formatters/snippet_extractor.rb +134 -0
  49. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  50. data/lib/rspec/core/hooks.rb +624 -0
  51. data/lib/rspec/core/invocations.rb +87 -0
  52. data/lib/rspec/core/memoized_helpers.rb +554 -0
  53. data/lib/rspec/core/metadata.rb +498 -0
  54. data/lib/rspec/core/metadata_filter.rb +255 -0
  55. data/lib/rspec/core/minitest_assertions_adapter.rb +31 -0
  56. data/lib/rspec/core/mocking_adapters/flexmock.rb +31 -0
  57. data/lib/rspec/core/mocking_adapters/mocha.rb +57 -0
  58. data/lib/rspec/core/mocking_adapters/null.rb +14 -0
  59. data/lib/rspec/core/mocking_adapters/rr.rb +31 -0
  60. data/lib/rspec/core/mocking_adapters/rspec.rb +32 -0
  61. data/lib/rspec/core/notifications.rb +521 -0
  62. data/lib/rspec/core/option_parser.rb +309 -0
  63. data/lib/rspec/core/ordering.rb +158 -0
  64. data/lib/rspec/core/output_wrapper.rb +29 -0
  65. data/lib/rspec/core/pending.rb +165 -0
  66. data/lib/rspec/core/profiler.rb +34 -0
  67. data/lib/rspec/core/project_initializer.rb +48 -0
  68. data/lib/rspec/core/project_initializer/.rspec +1 -0
  69. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +100 -0
  70. data/lib/rspec/core/rake_task.rb +168 -0
  71. data/lib/rspec/core/reporter.rb +257 -0
  72. data/lib/rspec/core/ruby_project.rb +53 -0
  73. data/lib/rspec/core/runner.rb +199 -0
  74. data/lib/rspec/core/sandbox.rb +37 -0
  75. data/lib/rspec/core/set.rb +54 -0
  76. data/lib/rspec/core/shared_context.rb +55 -0
  77. data/lib/rspec/core/shared_example_group.rb +269 -0
  78. data/lib/rspec/core/shell_escape.rb +49 -0
  79. data/lib/rspec/core/test_unit_assertions_adapter.rb +30 -0
  80. data/lib/rspec/core/version.rb +9 -0
  81. data/lib/rspec/core/warnings.rb +40 -0
  82. data/lib/rspec/core/world.rb +275 -0
  83. metadata +292 -0
  84. metadata.gz.sig +0 -0
@@ -0,0 +1,235 @@
1
+ RSpec::Support.require_rspec_support "directory_maker"
2
+
3
+ module RSpec
4
+ module Core
5
+ # Persists example ids and their statuses so that we can filter
6
+ # to just the ones that failed the last time they ran.
7
+ # @private
8
+ class ExampleStatusPersister
9
+ def self.load_from(file_name)
10
+ return [] unless File.exist?(file_name)
11
+ ExampleStatusParser.parse(File.read(file_name))
12
+ end
13
+
14
+ def self.persist(examples, file_name)
15
+ new(examples, file_name).persist
16
+ end
17
+
18
+ def initialize(examples, file_name)
19
+ @examples = examples
20
+ @file_name = file_name
21
+ end
22
+
23
+ def persist
24
+ RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(@file_name))
25
+ File.open(@file_name, File::RDWR | File::CREAT) do |f|
26
+ # lock the file while reading / persisting to avoid a race
27
+ # condition where parallel or unrelated spec runs race to
28
+ # update the same file
29
+ f.flock(File::LOCK_EX)
30
+ unparsed_previous_runs = f.read
31
+ f.rewind
32
+ f.write(dump_statuses(unparsed_previous_runs))
33
+ f.flush
34
+ f.truncate(f.pos)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def dump_statuses(unparsed_previous_runs)
41
+ statuses_from_previous_runs = ExampleStatusParser.parse(unparsed_previous_runs)
42
+ merged_statuses = ExampleStatusMerger.merge(statuses_from_this_run, statuses_from_previous_runs)
43
+ ExampleStatusDumper.dump(merged_statuses)
44
+ end
45
+
46
+ def statuses_from_this_run
47
+ @examples.map do |ex|
48
+ result = ex.execution_result
49
+
50
+ {
51
+ :example_id => ex.id,
52
+ :status => result.status ? result.status.to_s : Configuration::UNKNOWN_STATUS,
53
+ :run_time => result.run_time ? Formatters::Helpers.format_duration(result.run_time) : ""
54
+ }
55
+ end
56
+ end
57
+ end
58
+
59
+ # Merges together a list of example statuses from this run
60
+ # and a list from previous runs (presumably loaded from disk).
61
+ # Each example status object is expected to be a hash with
62
+ # at least an `:example_id` and a `:status` key. Examples that
63
+ # were loaded but not executed (due to filtering, `--fail-fast`
64
+ # or whatever) should have a `:status` of `UNKNOWN_STATUS`.
65
+ #
66
+ # This willl produce a new list that:
67
+ # - Will be missing examples from previous runs that we know for sure
68
+ # no longer exist.
69
+ # - Will have the latest known status for any examples that either
70
+ # definitively do exist or may still exist.
71
+ # - Is sorted by file name and example definition order, so that
72
+ # the saved file is easily scannable if users want to inspect it.
73
+ # @private
74
+ class ExampleStatusMerger
75
+ def self.merge(this_run, from_previous_runs)
76
+ new(this_run, from_previous_runs).merge
77
+ end
78
+
79
+ def initialize(this_run, from_previous_runs)
80
+ @this_run = hash_from(this_run)
81
+ @from_previous_runs = hash_from(from_previous_runs)
82
+ @file_exists_cache = Hash.new { |hash, file| hash[file] = File.exist?(file) }
83
+ end
84
+
85
+ def merge
86
+ delete_previous_examples_that_no_longer_exist
87
+
88
+ @this_run.merge(@from_previous_runs) do |_ex_id, new, old|
89
+ new.fetch(:status) == Configuration::UNKNOWN_STATUS ? old : new
90
+ end.values.sort_by(&method(:sort_value_from))
91
+ end
92
+
93
+ private
94
+
95
+ def hash_from(example_list)
96
+ example_list.inject({}) do |hash, example|
97
+ hash[example.fetch(:example_id)] = example
98
+ hash
99
+ end
100
+ end
101
+
102
+ def delete_previous_examples_that_no_longer_exist
103
+ @from_previous_runs.delete_if do |ex_id, _|
104
+ example_must_no_longer_exist?(ex_id)
105
+ end
106
+ end
107
+
108
+ def example_must_no_longer_exist?(ex_id)
109
+ # Obviously, it exists if it was loaded for this spec run...
110
+ return false if @this_run.key?(ex_id)
111
+
112
+ spec_file = spec_file_from(ex_id)
113
+
114
+ # `this_run` includes examples that were loaded but not executed.
115
+ # Given that, if the spec file for this example was loaded,
116
+ # but the id does not still exist, it's safe to assume that
117
+ # the example must no longer exist.
118
+ return true if loaded_spec_files.include?(spec_file)
119
+
120
+ # The example may still exist as long as the file exists...
121
+ !@file_exists_cache[spec_file]
122
+ end
123
+
124
+ def loaded_spec_files
125
+ @loaded_spec_files ||= Set.new(@this_run.keys.map(&method(:spec_file_from)))
126
+ end
127
+
128
+ def spec_file_from(ex_id)
129
+ ex_id.split("[").first
130
+ end
131
+
132
+ def sort_value_from(example)
133
+ file, scoped_id = Example.parse_id(example.fetch(:example_id))
134
+ [file, *scoped_id.split(":").map(&method(:Integer))]
135
+ end
136
+ end
137
+
138
+ # Dumps a list of hashes in a pretty, human readable format
139
+ # for later parsing. The hashes are expected to have symbol
140
+ # keys and string values, and each hash should have the same
141
+ # set of keys.
142
+ # @private
143
+ class ExampleStatusDumper
144
+ def self.dump(examples)
145
+ new(examples).dump
146
+ end
147
+
148
+ def initialize(examples)
149
+ @examples = examples
150
+ end
151
+
152
+ def dump
153
+ return nil if @examples.empty?
154
+ (formatted_header_rows + formatted_value_rows).join("\n") << "\n"
155
+ end
156
+
157
+ private
158
+
159
+ def formatted_header_rows
160
+ @formatted_header_rows ||= begin
161
+ dividers = column_widths.map { |w| "-" * w }
162
+ [formatted_row_from(headers.map(&:to_s)), formatted_row_from(dividers)]
163
+ end
164
+ end
165
+
166
+ def formatted_value_rows
167
+ @foramtted_value_rows ||= rows.map do |row|
168
+ formatted_row_from(row)
169
+ end
170
+ end
171
+
172
+ def rows
173
+ @rows ||= @examples.map { |ex| ex.values_at(*headers) }
174
+ end
175
+
176
+ def formatted_row_from(row_values)
177
+ padded_values = row_values.each_with_index.map do |value, index|
178
+ value.ljust(column_widths[index])
179
+ end
180
+
181
+ padded_values.join(" | ") << " |"
182
+ end
183
+
184
+ def headers
185
+ @headers ||= @examples.first.keys
186
+ end
187
+
188
+ def column_widths
189
+ @column_widths ||= begin
190
+ value_sets = rows.transpose
191
+
192
+ headers.each_with_index.map do |header, index|
193
+ values = value_sets[index] << header.to_s
194
+ values.map(&:length).max
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ # Parses a string that has been previously dumped by ExampleStatusDumper.
201
+ # Note that this parser is a bit naive in that it does a simple split on
202
+ # "\n" and " | ", with no concern for handling escaping. For now, that's
203
+ # OK because the values we plan to persist (example id, status, and perhaps
204
+ # example duration) are highly unlikely to contain "\n" or " | " -- after
205
+ # all, who puts those in file names?
206
+ # @private
207
+ class ExampleStatusParser
208
+ def self.parse(string)
209
+ new(string).parse
210
+ end
211
+
212
+ def initialize(string)
213
+ @header_line, _, *@row_lines = string.lines.to_a
214
+ end
215
+
216
+ def parse
217
+ @row_lines.map { |line| parse_row(line) }
218
+ end
219
+
220
+ private
221
+
222
+ def parse_row(line)
223
+ Hash[headers.zip(split_line(line))]
224
+ end
225
+
226
+ def headers
227
+ @headers ||= split_line(@header_line).grep(/\S/).map(&:to_sym)
228
+ end
229
+
230
+ def split_line(line)
231
+ line.split(/\s+\|\s+?/, -1)
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,231 @@
1
+ module RSpec
2
+ module Core
3
+ # @private
4
+ class FilterManager
5
+ attr_reader :exclusions, :inclusions
6
+
7
+ def initialize
8
+ @exclusions, @inclusions = FilterRules.build
9
+ end
10
+
11
+ # @api private
12
+ #
13
+ # @param file_path [String]
14
+ # @param line_numbers [Array]
15
+ def add_location(file_path, line_numbers)
16
+ # locations is a hash of expanded paths to arrays of line
17
+ # numbers to match against. e.g.
18
+ # { "path/to/file.rb" => [37, 42] }
19
+ add_path_to_arrays_filter(:locations, File.expand_path(file_path), line_numbers)
20
+ end
21
+
22
+ def add_ids(rerun_path, scoped_ids)
23
+ # ids is a hash of relative paths to arrays of ids
24
+ # to match against. e.g.
25
+ # { "./path/to/file.rb" => ["1:1", "2:4"] }
26
+ rerun_path = Metadata.relative_path(File.expand_path rerun_path)
27
+ add_path_to_arrays_filter(:ids, rerun_path, scoped_ids)
28
+ end
29
+
30
+ def empty?
31
+ inclusions.empty? && exclusions.empty?
32
+ end
33
+
34
+ def prune(examples)
35
+ # Semantically, this is unnecessary (the filtering below will return the empty
36
+ # array unmodified), but for perf reasons it's worth exiting early here. Users
37
+ # commonly have top-level examples groups that do not have any direct examples
38
+ # and instead have nested groups with examples. In that kind of situation,
39
+ # `examples` will be empty.
40
+ return examples if examples.empty?
41
+
42
+ examples = prune_conditionally_filtered_examples(examples)
43
+
44
+ if inclusions.standalone?
45
+ examples.select { |e| inclusions.include_example?(e) }
46
+ else
47
+ locations, ids, non_scoped_inclusions = inclusions.split_file_scoped_rules
48
+
49
+ examples.select do |ex|
50
+ file_scoped_include?(ex.metadata, ids, locations) do
51
+ !exclusions.include_example?(ex) && non_scoped_inclusions.include_example?(ex)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def exclude(*args)
58
+ exclusions.add(args.last)
59
+ end
60
+
61
+ def exclude_only(*args)
62
+ exclusions.use_only(args.last)
63
+ end
64
+
65
+ def exclude_with_low_priority(*args)
66
+ exclusions.add_with_low_priority(args.last)
67
+ end
68
+
69
+ def include(*args)
70
+ inclusions.add(args.last)
71
+ end
72
+
73
+ def include_only(*args)
74
+ inclusions.use_only(args.last)
75
+ end
76
+
77
+ def include_with_low_priority(*args)
78
+ inclusions.add_with_low_priority(args.last)
79
+ end
80
+
81
+ private
82
+
83
+ def add_path_to_arrays_filter(filter_key, path, values)
84
+ filter = inclusions.delete(filter_key) || Hash.new { |h, k| h[k] = [] }
85
+ filter[path].concat(values)
86
+ inclusions.add(filter_key => filter)
87
+ end
88
+
89
+ def prune_conditionally_filtered_examples(examples)
90
+ examples.reject do |ex|
91
+ meta = ex.metadata
92
+ !meta.fetch(:if, true) || meta[:unless]
93
+ end
94
+ end
95
+
96
+ # When a user specifies a particular spec location, that takes priority
97
+ # over any exclusion filters (such as if the spec is tagged with `:slow`
98
+ # and there is a `:slow => true` exclusion filter), but only for specs
99
+ # defined in the same file as the location filters. Excluded specs in
100
+ # other files should still be excluded.
101
+ def file_scoped_include?(ex_metadata, ids, locations)
102
+ no_id_filters = ids[ex_metadata[:rerun_file_path]].empty?
103
+ no_location_filters = locations[
104
+ File.expand_path(ex_metadata[:rerun_file_path])
105
+ ].empty?
106
+
107
+ return yield if no_location_filters && no_id_filters
108
+
109
+ MetadataFilter.filter_applies?(:ids, ids, ex_metadata) ||
110
+ MetadataFilter.filter_applies?(:locations, locations, ex_metadata)
111
+ end
112
+ end
113
+
114
+ # @private
115
+ class FilterRules
116
+ PROC_HEX_NUMBER = /0x[0-9a-f]+@/
117
+ PROJECT_DIR = File.expand_path('.')
118
+
119
+ attr_accessor :opposite
120
+ attr_reader :rules
121
+
122
+ def self.build
123
+ exclusions = ExclusionRules.new
124
+ inclusions = InclusionRules.new
125
+ exclusions.opposite = inclusions
126
+ inclusions.opposite = exclusions
127
+ [exclusions, inclusions]
128
+ end
129
+
130
+ def initialize(rules={})
131
+ @rules = rules
132
+ end
133
+
134
+ def add(updated)
135
+ @rules.merge!(updated).each_key { |k| opposite.delete(k) }
136
+ end
137
+
138
+ def add_with_low_priority(updated)
139
+ updated = updated.merge(@rules)
140
+ opposite.each_pair { |k, v| updated.delete(k) if updated[k] == v }
141
+ @rules.replace(updated)
142
+ end
143
+
144
+ def use_only(updated)
145
+ updated.each_key { |k| opposite.delete(k) }
146
+ @rules.replace(updated)
147
+ end
148
+
149
+ def clear
150
+ @rules.clear
151
+ end
152
+
153
+ def delete(key)
154
+ @rules.delete(key)
155
+ end
156
+
157
+ def fetch(*args, &block)
158
+ @rules.fetch(*args, &block)
159
+ end
160
+
161
+ def [](key)
162
+ @rules[key]
163
+ end
164
+
165
+ def empty?
166
+ rules.empty?
167
+ end
168
+
169
+ def each_pair(&block)
170
+ @rules.each_pair(&block)
171
+ end
172
+
173
+ def description
174
+ rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)', '')
175
+ end
176
+
177
+ def include_example?(example)
178
+ MetadataFilter.apply?(:any?, @rules, example.metadata)
179
+ end
180
+ end
181
+
182
+ # @private
183
+ ExclusionRules = FilterRules
184
+
185
+ # @private
186
+ class InclusionRules < FilterRules
187
+ def add(*args)
188
+ apply_standalone_filter(*args) || super
189
+ end
190
+
191
+ def add_with_low_priority(*args)
192
+ apply_standalone_filter(*args) || super
193
+ end
194
+
195
+ def include_example?(example)
196
+ @rules.empty? || super
197
+ end
198
+
199
+ def standalone?
200
+ is_standalone_filter?(@rules)
201
+ end
202
+
203
+ def split_file_scoped_rules
204
+ rules_dup = @rules.dup
205
+ locations = rules_dup.delete(:locations) { Hash.new([]) }
206
+ ids = rules_dup.delete(:ids) { Hash.new([]) }
207
+
208
+ return locations, ids, self.class.new(rules_dup)
209
+ end
210
+
211
+ private
212
+
213
+ def apply_standalone_filter(updated)
214
+ return true if standalone?
215
+ return nil unless is_standalone_filter?(updated)
216
+
217
+ replace_filters(updated)
218
+ true
219
+ end
220
+
221
+ def replace_filters(new_rules)
222
+ @rules.replace(new_rules)
223
+ opposite.clear
224
+ end
225
+
226
+ def is_standalone_filter?(rules)
227
+ rules.key?(:full_description)
228
+ end
229
+ end
230
+ end
231
+ end