rspec-core 3.3.0 → 3.4.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +1 -1
- data/Changelog.md +88 -0
- data/{License.txt → LICENSE.md} +6 -5
- data/README.md +18 -3
- data/lib/rspec/core/bisect/example_minimizer.rb +78 -39
- data/lib/rspec/core/configuration.rb +87 -25
- data/lib/rspec/core/configuration_options.rb +1 -1
- data/lib/rspec/core/example.rb +55 -7
- data/lib/rspec/core/example_group.rb +28 -8
- data/lib/rspec/core/example_status_persister.rb +16 -16
- data/lib/rspec/core/formatters/bisect_progress_formatter.rb +44 -15
- data/lib/rspec/core/formatters/exception_presenter.rb +150 -59
- data/lib/rspec/core/formatters/helpers.rb +1 -1
- data/lib/rspec/core/formatters/html_formatter.rb +3 -3
- data/lib/rspec/core/formatters/html_printer.rb +2 -3
- data/lib/rspec/core/formatters/html_snippet_extractor.rb +116 -0
- data/lib/rspec/core/formatters/protocol.rb +9 -0
- data/lib/rspec/core/formatters/snippet_extractor.rb +124 -97
- data/lib/rspec/core/formatters.rb +2 -1
- data/lib/rspec/core/hooks.rb +2 -2
- data/lib/rspec/core/memoized_helpers.rb +2 -2
- data/lib/rspec/core/metadata.rb +3 -2
- data/lib/rspec/core/metadata_filter.rb +11 -6
- data/lib/rspec/core/notifications.rb +3 -2
- data/lib/rspec/core/option_parser.rb +22 -4
- data/lib/rspec/core/project_initializer/spec/spec_helper.rb +2 -2
- data/lib/rspec/core/rake_task.rb +12 -3
- data/lib/rspec/core/reporter.rb +18 -2
- data/lib/rspec/core/ruby_project.rb +1 -1
- data/lib/rspec/core/shared_example_group.rb +2 -0
- data/lib/rspec/core/source/location.rb +13 -0
- data/lib/rspec/core/source/node.rb +93 -0
- data/lib/rspec/core/source/syntax_highlighter.rb +71 -0
- data/lib/rspec/core/source/token.rb +43 -0
- data/lib/rspec/core/source.rb +76 -0
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/world.rb +25 -6
- data.tar.gz.sig +0 -0
- metadata +14 -11
- metadata.gz.sig +0 -0
- data/lib/rspec/core/bisect/subset_enumerator.rb +0 -39
- data/lib/rspec/core/mutex.rb +0 -63
- data/lib/rspec/core/reentrant_mutex.rb +0 -52
@@ -1,114 +1,141 @@
|
|
1
|
+
RSpec::Support.require_rspec_core "source"
|
2
|
+
|
1
3
|
module RSpec
|
2
4
|
module Core
|
3
5
|
module Formatters
|
4
|
-
# @
|
5
|
-
#
|
6
|
-
# Extracts code snippets by looking at the backtrace of the passed error
|
7
|
-
# and applies synax highlighting and line numbers using html.
|
6
|
+
# @private
|
8
7
|
class SnippetExtractor
|
9
|
-
|
10
|
-
|
11
|
-
def self.convert(code)
|
12
|
-
%Q(#{code}\n<span class="comment"># Install the coderay gem to get syntax highlighting</span>)
|
13
|
-
end
|
14
|
-
end
|
8
|
+
NoSuchFileError = Class.new(StandardError)
|
9
|
+
NoSuchLineError = Class.new(StandardError)
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
def self.extract_line_at(file_path, line_number)
|
12
|
+
source = source_from_file(file_path)
|
13
|
+
line = source.lines[line_number - 1]
|
14
|
+
raise NoSuchLineError unless line
|
15
|
+
line
|
21
16
|
end
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
require 'coderay'
|
27
|
-
@@converter = CoderayConverter
|
28
|
-
# rubocop:disable Lint/HandleExceptions
|
29
|
-
rescue LoadError
|
30
|
-
# it'll fall back to the NullConverter assigned above
|
31
|
-
# rubocop:enable Lint/HandleExceptions
|
18
|
+
def self.source_from_file(path)
|
19
|
+
raise NoSuchFileError unless File.exist?(path)
|
20
|
+
RSpec.world.source_cache.source_from_file(path)
|
32
21
|
end
|
33
22
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
# Create a snippet from a line of code.
|
55
|
-
#
|
56
|
-
# @param error_line [String] file name with line number (i.e.
|
57
|
-
# 'foo_spec.rb:12')
|
58
|
-
# @return [String] lines around the target line within the file
|
59
|
-
#
|
60
|
-
# @see #lines_around
|
61
|
-
def snippet_for(error_line)
|
62
|
-
if error_line =~ /(.*):(\d+)/
|
63
|
-
file = Regexp.last_match[1]
|
64
|
-
line = Regexp.last_match[2].to_i
|
65
|
-
[lines_around(file, line), line]
|
66
|
-
else
|
67
|
-
["# Couldn't get snippet for #{error_line}", 1]
|
23
|
+
if RSpec::Support::RubyFeatures.ripper_supported?
|
24
|
+
NoExpressionAtLineError = Class.new(StandardError)
|
25
|
+
|
26
|
+
PAREN_TOKEN_TYPE_PAIRS = {
|
27
|
+
:on_lbracket => :on_rbracket,
|
28
|
+
:on_lparen => :on_rparen,
|
29
|
+
:on_lbrace => :on_rbrace,
|
30
|
+
:on_heredoc_beg => :on_heredoc_end
|
31
|
+
}
|
32
|
+
|
33
|
+
attr_reader :source, :beginning_line_number, :max_line_count
|
34
|
+
|
35
|
+
def self.extract_expression_lines_at(file_path, beginning_line_number, max_line_count=nil)
|
36
|
+
if max_line_count == 1
|
37
|
+
[extract_line_at(file_path, beginning_line_number)]
|
38
|
+
else
|
39
|
+
source = source_from_file(file_path)
|
40
|
+
new(source, beginning_line_number, max_line_count).expression_lines
|
41
|
+
end
|
68
42
|
end
|
69
|
-
end
|
70
43
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
#
|
76
|
-
# @param file [String] filename
|
77
|
-
# @param line [Fixnum] line number
|
78
|
-
# @return [String] lines around the target line within the file (2 above
|
79
|
-
# and 1 below).
|
80
|
-
def lines_around(file, line)
|
81
|
-
if File.file?(file)
|
82
|
-
lines = File.read(file).split("\n")
|
83
|
-
min = [0, line - 3].max
|
84
|
-
max = [line + 1, lines.length - 1].min
|
85
|
-
selected_lines = []
|
86
|
-
selected_lines.join("\n")
|
87
|
-
lines[min..max].join("\n")
|
88
|
-
else
|
89
|
-
"# Couldn't get snippet for #{file}"
|
44
|
+
def initialize(source, beginning_line_number, max_line_count=nil)
|
45
|
+
@source = source
|
46
|
+
@beginning_line_number = beginning_line_number
|
47
|
+
@max_line_count = max_line_count
|
90
48
|
end
|
91
|
-
rescue SecurityError
|
92
|
-
"# Couldn't get snippet for #{file}"
|
93
|
-
end
|
94
49
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
50
|
+
def expression_lines
|
51
|
+
line_range = line_range_of_expression
|
52
|
+
|
53
|
+
if max_line_count && line_range.count > max_line_count
|
54
|
+
line_range = (line_range.begin)..(line_range.begin + max_line_count - 1)
|
55
|
+
end
|
56
|
+
|
57
|
+
source.lines[(line_range.begin - 1)..(line_range.end - 1)]
|
58
|
+
rescue SyntaxError, NoExpressionAtLineError
|
59
|
+
[self.class.extract_line_at(source.path, beginning_line_number)]
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def line_range_of_expression
|
65
|
+
@line_range_of_expression ||= begin
|
66
|
+
line_range = line_range_of_location_nodes_in_expression
|
67
|
+
initial_unclosed_parens = unclosed_paren_tokens_in_line_range(line_range)
|
68
|
+
unclosed_parens = initial_unclosed_parens
|
69
|
+
|
70
|
+
until (initial_unclosed_parens & unclosed_parens).empty?
|
71
|
+
line_range = (line_range.begin)..(line_range.end + 1)
|
72
|
+
unclosed_parens = unclosed_paren_tokens_in_line_range(line_range)
|
73
|
+
end
|
74
|
+
|
75
|
+
line_range
|
76
|
+
end
|
110
77
|
end
|
111
|
-
|
78
|
+
|
79
|
+
def unclosed_paren_tokens_in_line_range(line_range)
|
80
|
+
tokens = FlatMap.flat_map(line_range) do |line_number|
|
81
|
+
source.tokens_by_line_number[line_number]
|
82
|
+
end
|
83
|
+
|
84
|
+
tokens.each_with_object([]) do |token, unclosed_tokens|
|
85
|
+
if PAREN_TOKEN_TYPE_PAIRS.keys.include?(token.type)
|
86
|
+
unclosed_tokens << token
|
87
|
+
else
|
88
|
+
index = unclosed_tokens.rindex do |unclosed_token|
|
89
|
+
PAREN_TOKEN_TYPE_PAIRS[unclosed_token.type] == token.type
|
90
|
+
end
|
91
|
+
unclosed_tokens.delete_at(index) if index
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def line_range_of_location_nodes_in_expression
|
97
|
+
line_numbers = expression_node.each_with_object(Set.new) do |node, set|
|
98
|
+
set << node.location.line if node.location
|
99
|
+
end
|
100
|
+
|
101
|
+
line_numbers.min..line_numbers.max
|
102
|
+
end
|
103
|
+
|
104
|
+
def expression_node
|
105
|
+
raise NoExpressionAtLineError if location_nodes_at_beginning_line.empty?
|
106
|
+
|
107
|
+
@expression_node ||= begin
|
108
|
+
common_ancestor_nodes = location_nodes_at_beginning_line.map do |node|
|
109
|
+
node.each_ancestor.to_a
|
110
|
+
end.reduce(:&)
|
111
|
+
|
112
|
+
common_ancestor_nodes.find { |node| expression_outmost_node?(node) }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def expression_outmost_node?(node)
|
117
|
+
return true unless node.parent
|
118
|
+
return false if node.type.to_s.start_with?('@')
|
119
|
+
![node, node.parent].all? do |n|
|
120
|
+
# See `Ripper::PARSER_EVENTS` for the complete list of sexp types.
|
121
|
+
type = n.type.to_s
|
122
|
+
type.end_with?('call') || type.start_with?('method_add_')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def location_nodes_at_beginning_line
|
127
|
+
source.nodes_by_line_number[beginning_line_number]
|
128
|
+
end
|
129
|
+
else
|
130
|
+
# :nocov:
|
131
|
+
def self.extract_expression_lines_at(file_path, beginning_line_number, *)
|
132
|
+
[extract_line_at(file_path, beginning_line_number)]
|
133
|
+
end
|
134
|
+
# :nocov:
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.least_indentation_from(lines)
|
138
|
+
lines.map { |line| line[/^[ \t]*/] }.min
|
112
139
|
end
|
113
140
|
end
|
114
141
|
end
|
@@ -73,6 +73,7 @@ module RSpec::Core::Formatters
|
|
73
73
|
autoload :ProfileFormatter, 'rspec/core/formatters/profile_formatter'
|
74
74
|
autoload :JsonFormatter, 'rspec/core/formatters/json_formatter'
|
75
75
|
autoload :BisectFormatter, 'rspec/core/formatters/bisect_formatter'
|
76
|
+
autoload :ExceptionPresenter, 'rspec/core/formatters/exception_presenter'
|
76
77
|
|
77
78
|
# Register the formatter class
|
78
79
|
# @param formatter_class [Class] formatter class to register
|
@@ -182,7 +183,7 @@ module RSpec::Core::Formatters
|
|
182
183
|
|
183
184
|
def duplicate_formatter_exists?(new_formatter)
|
184
185
|
@formatters.any? do |formatter|
|
185
|
-
formatter.class
|
186
|
+
formatter.class == new_formatter.class && formatter.output == new_formatter.output
|
186
187
|
end
|
187
188
|
end
|
188
189
|
|
data/lib/rspec/core/hooks.rb
CHANGED
@@ -362,7 +362,7 @@ module RSpec
|
|
362
362
|
class AfterHook < Hook
|
363
363
|
def run(example)
|
364
364
|
example.instance_exec(example, &block)
|
365
|
-
rescue
|
365
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex
|
366
366
|
example.set_exception(ex)
|
367
367
|
end
|
368
368
|
end
|
@@ -371,7 +371,7 @@ module RSpec
|
|
371
371
|
class AfterContextHook < Hook
|
372
372
|
def run(example)
|
373
373
|
example.instance_exec(example, &block)
|
374
|
-
rescue
|
374
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
|
375
375
|
# TODO: Come up with a better solution for this.
|
376
376
|
RSpec.configuration.reporter.message <<-EOS
|
377
377
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
RSpec::Support.require_rspec_support 'reentrant_mutex'
|
2
2
|
|
3
3
|
module RSpec
|
4
4
|
module Core
|
@@ -148,7 +148,7 @@ module RSpec
|
|
148
148
|
class ThreadsafeMemoized
|
149
149
|
def initialize
|
150
150
|
@memoized = {}
|
151
|
-
@mutex = ReentrantMutex.new
|
151
|
+
@mutex = Support::ReentrantMutex.new
|
152
152
|
end
|
153
153
|
|
154
154
|
def fetch_or_store(key)
|
data/lib/rspec/core/metadata.rb
CHANGED
@@ -147,12 +147,13 @@ module RSpec
|
|
147
147
|
end
|
148
148
|
|
149
149
|
relative_file_path = Metadata.relative_path(file_path)
|
150
|
+
absolute_file_path = File.expand_path(relative_file_path)
|
150
151
|
metadata[:file_path] = relative_file_path
|
151
152
|
metadata[:line_number] = line_number.to_i
|
152
153
|
metadata[:location] = "#{relative_file_path}:#{line_number}"
|
153
|
-
metadata[:absolute_file_path] =
|
154
|
+
metadata[:absolute_file_path] = absolute_file_path
|
154
155
|
metadata[:rerun_file_path] ||= relative_file_path
|
155
|
-
metadata[:scoped_id] = build_scoped_id_for(
|
156
|
+
metadata[:scoped_id] = build_scoped_id_for(absolute_file_path)
|
156
157
|
end
|
157
158
|
|
158
159
|
def file_path_and_line_number_from(backtrace)
|
@@ -15,22 +15,19 @@ module RSpec
|
|
15
15
|
# @private
|
16
16
|
def filter_applies?(key, value, metadata)
|
17
17
|
silence_metadata_example_group_deprecations do
|
18
|
-
return filter_applies_to_any_value?(key, value, metadata) if Array === metadata[key] && !(Proc === value)
|
19
18
|
return location_filter_applies?(value, metadata) if key == :locations
|
20
19
|
return id_filter_applies?(value, metadata) if key == :ids
|
21
20
|
return filters_apply?(key, value, metadata) if Hash === value
|
22
21
|
|
23
22
|
return false unless metadata.key?(key)
|
23
|
+
return true if TrueClass === value && !!metadata[key]
|
24
|
+
return filter_applies_to_any_value?(key, value, metadata) if Array === metadata[key] && !(Proc === value)
|
24
25
|
|
25
26
|
case value
|
26
27
|
when Regexp
|
27
28
|
metadata[key] =~ value
|
28
29
|
when Proc
|
29
|
-
|
30
|
-
when 0 then value.call
|
31
|
-
when 2 then value.call(metadata[key], metadata)
|
32
|
-
else value.call(metadata[key])
|
33
|
-
end
|
30
|
+
proc_filter_applies?(key, value, metadata)
|
34
31
|
else
|
35
32
|
metadata[key].to_s == value.to_s
|
36
33
|
end
|
@@ -61,6 +58,14 @@ module RSpec
|
|
61
58
|
!(relevant_line_numbers(metadata) & preceding_declaration_lines).empty?
|
62
59
|
end
|
63
60
|
|
61
|
+
def proc_filter_applies?(key, proc, metadata)
|
62
|
+
case proc.arity
|
63
|
+
when 0 then proc.call
|
64
|
+
when 2 then proc.call(metadata[key], metadata)
|
65
|
+
else proc.call(metadata[key])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
64
69
|
def relevant_line_numbers(metadata)
|
65
70
|
Metadata.ascend(metadata).map { |meta| meta[:line_number] }
|
66
71
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
RSpec::Support.require_rspec_core "formatters/exception_presenter"
|
2
2
|
RSpec::Support.require_rspec_core "formatters/helpers"
|
3
3
|
RSpec::Support.require_rspec_core "shell_escape"
|
4
|
-
RSpec::Support.require_rspec_support "encoded_string"
|
5
4
|
|
6
5
|
module RSpec::Core
|
7
6
|
# Notifications are value objects passed to formatters to provide them
|
@@ -10,6 +9,7 @@ module RSpec::Core
|
|
10
9
|
# @private
|
11
10
|
module NullColorizer
|
12
11
|
module_function
|
12
|
+
|
13
13
|
def wrap(line, _code_or_symbol)
|
14
14
|
line
|
15
15
|
end
|
@@ -281,7 +281,8 @@ module RSpec::Core
|
|
281
281
|
# @attr pending_examples [Array<RSpec::Core::Example>] the pending examples
|
282
282
|
# @attr load_time [Float] the number of seconds taken to boot RSpec
|
283
283
|
# and load the spec files
|
284
|
-
SummaryNotification = Struct.new(:duration, :examples, :failed_examples,
|
284
|
+
SummaryNotification = Struct.new(:duration, :examples, :failed_examples,
|
285
|
+
:pending_examples, :load_time)
|
285
286
|
class SummaryNotification
|
286
287
|
# @api
|
287
288
|
# @return [Fixnum] the number of examples run
|
@@ -34,6 +34,8 @@ module RSpec::Core
|
|
34
34
|
private
|
35
35
|
|
36
36
|
# rubocop:disable MethodLength
|
37
|
+
# rubocop:disable Metrics/AbcSize
|
38
|
+
# rubocop:disable CyclomaticComplexity
|
37
39
|
def parser(options)
|
38
40
|
OptionParser.new do |parser|
|
39
41
|
parser.banner = "Usage: rspec [options] [files or directories]\n\n"
|
@@ -69,7 +71,18 @@ module RSpec::Core
|
|
69
71
|
bisect_and_exit(argument)
|
70
72
|
end
|
71
73
|
|
72
|
-
parser.on('--[no-]fail-fast', 'Abort the run
|
74
|
+
parser.on('--[no-]fail-fast[=COUNT]', 'Abort the run after a certain number of failures (1 by default).') do |argument|
|
75
|
+
if argument == true
|
76
|
+
value = 1
|
77
|
+
elsif argument == false || argument == 0
|
78
|
+
value = false
|
79
|
+
else
|
80
|
+
begin
|
81
|
+
value = Integer(argument)
|
82
|
+
rescue ArgumentError
|
83
|
+
RSpec.warning "Expected an integer value for `--fail-fast`, got: #{argument.inspect}", :call_site => nil
|
84
|
+
end
|
85
|
+
end
|
73
86
|
set_fail_fast(options, value)
|
74
87
|
end
|
75
88
|
|
@@ -174,12 +187,16 @@ FILTERING
|
|
174
187
|
parser.on("--next-failure", "Apply `--only-failures` and abort after one failure.",
|
175
188
|
" (Equivalent to `--only-failures --fail-fast --order defined`)") do
|
176
189
|
configure_only_failures(options)
|
177
|
-
set_fail_fast(options,
|
190
|
+
set_fail_fast(options, 1)
|
178
191
|
options[:order] ||= 'defined'
|
179
192
|
end
|
180
193
|
|
181
194
|
parser.on('-P', '--pattern PATTERN', 'Load files matching pattern (default: "spec/**/*_spec.rb").') do |o|
|
182
|
-
options[:pattern]
|
195
|
+
if options[:pattern]
|
196
|
+
options[:pattern] += ',' + o
|
197
|
+
else
|
198
|
+
options[:pattern] = o
|
199
|
+
end
|
183
200
|
end
|
184
201
|
|
185
202
|
parser.on('--exclude-pattern PATTERN',
|
@@ -246,10 +263,11 @@ FILTERING
|
|
246
263
|
raise OptionParser::InvalidOption.new
|
247
264
|
end
|
248
265
|
end
|
249
|
-
|
250
266
|
end
|
251
267
|
end
|
268
|
+
# rubocop:enable Metrics/AbcSize
|
252
269
|
# rubocop:enable MethodLength
|
270
|
+
# rubocop:enable CyclomaticComplexity
|
253
271
|
|
254
272
|
def add_tag_filter(options, filter_type, tag_name, value=true)
|
255
273
|
(options[filter_type] ||= {})[tag_name] = value
|
@@ -57,9 +57,9 @@ RSpec.configure do |config|
|
|
57
57
|
|
58
58
|
# Limits the available syntax to the non-monkey patched syntax that is
|
59
59
|
# recommended. For more details, see:
|
60
|
-
# - http://
|
60
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
61
61
|
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
62
|
-
# - http://
|
62
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
63
63
|
config.disable_monkey_patching!
|
64
64
|
|
65
65
|
# This setting enables warnings. It's recommended, but in some cases may
|
data/lib/rspec/core/rake_task.rb
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rake/tasklib'
|
3
|
-
require 'rspec/support
|
4
|
-
|
3
|
+
require 'rspec/support'
|
4
|
+
|
5
|
+
RSpec::Support.require_rspec_support "ruby_features"
|
6
|
+
|
7
|
+
# :nocov:
|
8
|
+
unless RSpec::Support.respond_to?(:require_rspec_core)
|
9
|
+
RSpec::Support.define_optimized_require_for_rspec(:core) { |f| require_relative "../#{f}" }
|
10
|
+
end
|
11
|
+
# :nocov:
|
12
|
+
|
13
|
+
RSpec::Support.require_rspec_core "shell_escape"
|
5
14
|
|
6
15
|
module RSpec
|
7
16
|
module Core
|
@@ -91,7 +100,7 @@ module RSpec
|
|
91
100
|
|
92
101
|
def file_inclusion_specification
|
93
102
|
if ENV['SPEC']
|
94
|
-
FileList[
|
103
|
+
FileList[ENV['SPEC']].sort
|
95
104
|
elsif String === pattern && !File.exist?(pattern)
|
96
105
|
"--pattern #{escape pattern}"
|
97
106
|
else
|
data/lib/rspec/core/reporter.rb
CHANGED
@@ -8,7 +8,7 @@ module RSpec::Core
|
|
8
8
|
:close, :deprecation, :deprecation_summary, :dump_failures, :dump_pending,
|
9
9
|
:dump_profile, :dump_summary, :example_failed, :example_group_finished,
|
10
10
|
:example_group_started, :example_passed, :example_pending, :example_started,
|
11
|
-
:message, :seed, :start, :start_dump, :stop
|
11
|
+
:message, :seed, :start, :start_dump, :stop, :example_finished
|
12
12
|
])
|
13
13
|
|
14
14
|
def initialize(configuration)
|
@@ -124,6 +124,11 @@ module RSpec::Core
|
|
124
124
|
notify :example_started, Notifications::ExampleNotification.for(example)
|
125
125
|
end
|
126
126
|
|
127
|
+
# @private
|
128
|
+
def example_finished(example)
|
129
|
+
notify :example_finished, Notifications::ExampleNotification.for(example)
|
130
|
+
end
|
131
|
+
|
127
132
|
# @private
|
128
133
|
def example_passed(example)
|
129
134
|
notify :example_passed, Notifications::ExampleNotification.for(example)
|
@@ -192,6 +197,17 @@ module RSpec::Core
|
|
192
197
|
exit!(exit_status)
|
193
198
|
end
|
194
199
|
|
200
|
+
# @private
|
201
|
+
def fail_fast_limit_met?
|
202
|
+
return false unless (fail_fast = @configuration.fail_fast)
|
203
|
+
|
204
|
+
if fail_fast == true
|
205
|
+
@failed_examples.any?
|
206
|
+
else
|
207
|
+
fail_fast <= @failed_examples.size
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
195
211
|
private
|
196
212
|
|
197
213
|
def close
|
@@ -201,7 +217,7 @@ module RSpec::Core
|
|
201
217
|
def mute_profile_output?
|
202
218
|
# Don't print out profiled info if there are failures and `--fail-fast` is
|
203
219
|
# used, it just clutters the output.
|
204
|
-
!@configuration.profile_examples? ||
|
220
|
+
!@configuration.profile_examples? || fail_fast_limit_met?
|
205
221
|
end
|
206
222
|
|
207
223
|
def seed_used?
|
@@ -96,6 +96,7 @@ module RSpec
|
|
96
96
|
# Shared examples top level DSL.
|
97
97
|
module TopLevelDSL
|
98
98
|
# @private
|
99
|
+
# rubocop:disable Lint/NestedMethodDefinition
|
99
100
|
def self.definitions
|
100
101
|
proc do
|
101
102
|
def shared_examples(name, *args, &block)
|
@@ -105,6 +106,7 @@ module RSpec
|
|
105
106
|
alias shared_examples_for shared_examples
|
106
107
|
end
|
107
108
|
end
|
109
|
+
# rubocop:enable Lint/NestedMethodDefinition
|
108
110
|
|
109
111
|
# @private
|
110
112
|
def self.exposed_globally?
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Core
|
3
|
+
class Source
|
4
|
+
# @private
|
5
|
+
# Represents a source location of node or token.
|
6
|
+
Location = Struct.new(:line, :column) do
|
7
|
+
def self.location?(array)
|
8
|
+
array.is_a?(Array) && array.size == 2 && array.all? { |e| e.is_a?(Integer) }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
RSpec::Support.require_rspec_core "source/location"
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Core
|
5
|
+
class Source
|
6
|
+
# @private
|
7
|
+
# A wrapper for Ripper AST node which is generated with `Ripper.sexp`.
|
8
|
+
class Node
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
attr_reader :sexp, :parent
|
12
|
+
|
13
|
+
def self.sexp?(array)
|
14
|
+
array.is_a?(Array) && array.first.is_a?(Symbol)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(ripper_sexp, parent=nil)
|
18
|
+
@sexp = ripper_sexp.freeze
|
19
|
+
@parent = parent
|
20
|
+
end
|
21
|
+
|
22
|
+
def type
|
23
|
+
sexp[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
def args
|
27
|
+
@args ||= raw_args.map do |raw_arg|
|
28
|
+
if Node.sexp?(raw_arg)
|
29
|
+
Node.new(raw_arg, self)
|
30
|
+
elsif Location.location?(raw_arg)
|
31
|
+
Location.new(*raw_arg)
|
32
|
+
elsif raw_arg.is_a?(Array)
|
33
|
+
GroupNode.new(raw_arg, self)
|
34
|
+
else
|
35
|
+
raw_arg
|
36
|
+
end
|
37
|
+
end.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
def children
|
41
|
+
@children ||= args.select { |arg| arg.is_a?(Node) }.freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
def location
|
45
|
+
@location ||= args.find { |arg| arg.is_a?(Location) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def each(&block)
|
49
|
+
return to_enum(__method__) unless block_given?
|
50
|
+
|
51
|
+
yield self
|
52
|
+
|
53
|
+
children.each do |child|
|
54
|
+
child.each(&block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_ancestor
|
59
|
+
return to_enum(__method__) unless block_given?
|
60
|
+
|
61
|
+
current_node = self
|
62
|
+
|
63
|
+
while (current_node = current_node.parent)
|
64
|
+
yield current_node
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def inspect
|
69
|
+
"#<#{self.class} #{type}>"
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def raw_args
|
75
|
+
sexp[1..-1] || []
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @private
|
80
|
+
class GroupNode < Node
|
81
|
+
def type
|
82
|
+
:group
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def raw_args
|
88
|
+
sexp
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|