rspec-core 3.0.4 → 3.12.2
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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +2 -1
- data/Changelog.md +888 -2
- data/{License.txt → LICENSE.md} +6 -5
- data/README.md +165 -24
- data/lib/rspec/autorun.rb +1 -0
- data/lib/rspec/core/backtrace_formatter.rb +19 -20
- data/lib/rspec/core/bisect/coordinator.rb +62 -0
- data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
- data/lib/rspec/core/bisect/fork_runner.rb +138 -0
- data/lib/rspec/core/bisect/server.rb +61 -0
- data/lib/rspec/core/bisect/shell_command.rb +126 -0
- data/lib/rspec/core/bisect/shell_runner.rb +73 -0
- data/lib/rspec/core/bisect/utilities.rb +69 -0
- data/lib/rspec/core/configuration.rb +1287 -246
- data/lib/rspec/core/configuration_options.rb +95 -35
- data/lib/rspec/core/did_you_mean.rb +46 -0
- data/lib/rspec/core/drb.rb +21 -12
- data/lib/rspec/core/dsl.rb +10 -6
- data/lib/rspec/core/example.rb +305 -113
- data/lib/rspec/core/example_group.rb +431 -223
- data/lib/rspec/core/example_status_persister.rb +235 -0
- data/lib/rspec/core/filter_manager.rb +86 -115
- data/lib/rspec/core/flat_map.rb +6 -4
- data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
- data/lib/rspec/core/formatters/base_formatter.rb +14 -116
- data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
- data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
- data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
- data/lib/rspec/core/formatters/console_codes.rb +29 -18
- data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
- data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
- data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
- data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
- data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
- data/lib/rspec/core/formatters/helpers.rb +45 -15
- data/lib/rspec/core/formatters/html_formatter.rb +33 -28
- data/lib/rspec/core/formatters/html_printer.rb +30 -20
- data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
- data/lib/rspec/core/formatters/json_formatter.rb +18 -9
- data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
- data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
- data/lib/rspec/core/formatters/protocol.rb +182 -0
- data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
- data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
- data/lib/rspec/core/formatters.rb +81 -41
- data/lib/rspec/core/hooks.rb +314 -244
- data/lib/rspec/core/invocations.rb +87 -0
- data/lib/rspec/core/memoized_helpers.rb +161 -51
- data/lib/rspec/core/metadata.rb +132 -61
- data/lib/rspec/core/metadata_filter.rb +224 -64
- data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
- data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
- data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
- data/lib/rspec/core/mocking_adapters/null.rb +2 -0
- data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
- data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
- data/lib/rspec/core/notifications.rb +192 -206
- data/lib/rspec/core/option_parser.rb +174 -69
- data/lib/rspec/core/ordering.rb +48 -35
- data/lib/rspec/core/output_wrapper.rb +29 -0
- data/lib/rspec/core/pending.rb +25 -33
- data/lib/rspec/core/profiler.rb +34 -0
- data/lib/rspec/core/project_initializer/.rspec +0 -2
- data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
- data/lib/rspec/core/project_initializer.rb +5 -3
- data/lib/rspec/core/rake_task.rb +99 -55
- data/lib/rspec/core/reporter.rb +128 -15
- data/lib/rspec/core/ruby_project.rb +14 -6
- data/lib/rspec/core/runner.rb +96 -45
- data/lib/rspec/core/sandbox.rb +37 -0
- data/lib/rspec/core/set.rb +54 -0
- data/lib/rspec/core/shared_example_group.rb +133 -43
- data/lib/rspec/core/shell_escape.rb +49 -0
- data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/warnings.rb +6 -6
- data/lib/rspec/core/world.rb +172 -68
- data/lib/rspec/core.rb +66 -21
- data.tar.gz.sig +0 -0
- metadata +93 -69
- metadata.gz.sig +0 -0
- data/lib/rspec/core/backport_random.rb +0 -336
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Core
|
|
3
|
+
module Formatters
|
|
4
|
+
# This class isn't loaded at runtime but serves to document all of the
|
|
5
|
+
# notifications implemented as part of the standard interface. The
|
|
6
|
+
# reporter will issue these during a normal test suite run, but a
|
|
7
|
+
# formatter will only receive those notifications it has registered
|
|
8
|
+
# itself to receive. To register a formatter call:
|
|
9
|
+
#
|
|
10
|
+
# `::RSpec::Core::Formatters.register class, :list, :of, :notifications`
|
|
11
|
+
#
|
|
12
|
+
# e.g.
|
|
13
|
+
#
|
|
14
|
+
# `::RSpec::Core::Formatters.register self, :start, :example_started`
|
|
15
|
+
#
|
|
16
|
+
# @see RSpec::Core::Formatters::BaseFormatter
|
|
17
|
+
# @see RSpec::Core::Formatters::BaseTextFormatter
|
|
18
|
+
# @see RSpec::Core::Reporter
|
|
19
|
+
class Protocol
|
|
20
|
+
# @method initialize(output)
|
|
21
|
+
# @api public
|
|
22
|
+
#
|
|
23
|
+
# @param output [IO] the formatter output
|
|
24
|
+
|
|
25
|
+
# @method start(notification)
|
|
26
|
+
# @api public
|
|
27
|
+
# @group Suite Notifications
|
|
28
|
+
#
|
|
29
|
+
# This method is invoked before any examples are run, right after
|
|
30
|
+
# they have all been collected. This can be useful for special
|
|
31
|
+
# formatters that need to provide progress on feedback (graphical ones).
|
|
32
|
+
#
|
|
33
|
+
# This will only be invoked once, and the next one to be invoked
|
|
34
|
+
# is {#example_group_started}.
|
|
35
|
+
#
|
|
36
|
+
# @param notification [Notifications::StartNotification]
|
|
37
|
+
|
|
38
|
+
# @method example_group_started(notification)
|
|
39
|
+
# @api public
|
|
40
|
+
# @group Group Notifications
|
|
41
|
+
#
|
|
42
|
+
# This method is invoked at the beginning of the execution of each
|
|
43
|
+
# example group.
|
|
44
|
+
#
|
|
45
|
+
# The next method to be invoked after this is {#example_passed},
|
|
46
|
+
# {#example_pending}, or {#example_group_finished}.
|
|
47
|
+
#
|
|
48
|
+
# @param notification [Notifications::GroupNotification] containing example_group
|
|
49
|
+
# subclass of {ExampleGroup}
|
|
50
|
+
|
|
51
|
+
# @method example_group_finished(notification)
|
|
52
|
+
# @api public
|
|
53
|
+
# @group Group Notifications
|
|
54
|
+
#
|
|
55
|
+
# Invoked at the end of the execution of each example group.
|
|
56
|
+
#
|
|
57
|
+
# @param notification [Notifications::GroupNotification] containing example_group
|
|
58
|
+
# subclass of {ExampleGroup}
|
|
59
|
+
|
|
60
|
+
# @method example_started(notification)
|
|
61
|
+
# @api public
|
|
62
|
+
# @group Example Notifications
|
|
63
|
+
#
|
|
64
|
+
# Invoked at the beginning of the execution of each example.
|
|
65
|
+
#
|
|
66
|
+
# @param notification [Notifications::ExampleNotification] containing example subclass
|
|
67
|
+
# of {Example}
|
|
68
|
+
|
|
69
|
+
# @method example_finished(notification)
|
|
70
|
+
# @api public
|
|
71
|
+
# @group Example Notifications
|
|
72
|
+
#
|
|
73
|
+
# Invoked at the end of the execution of each example.
|
|
74
|
+
#
|
|
75
|
+
# @param notification [Notifications::ExampleNotification] containing example subclass
|
|
76
|
+
# of {Example}
|
|
77
|
+
|
|
78
|
+
# @method example_passed(notification)
|
|
79
|
+
# @api public
|
|
80
|
+
# @group Example Notifications
|
|
81
|
+
#
|
|
82
|
+
# Invoked when an example passes.
|
|
83
|
+
#
|
|
84
|
+
# @param notification [Notifications::ExampleNotification] containing example subclass
|
|
85
|
+
# of {Example}
|
|
86
|
+
|
|
87
|
+
# @method example_pending(notification)
|
|
88
|
+
# @api public
|
|
89
|
+
# @group Example Notifications
|
|
90
|
+
#
|
|
91
|
+
# Invoked when an example is pending.
|
|
92
|
+
#
|
|
93
|
+
# @param notification [Notifications::ExampleNotification] containing example subclass
|
|
94
|
+
# of {Example}
|
|
95
|
+
|
|
96
|
+
# @method example_failed(notification)
|
|
97
|
+
# @api public
|
|
98
|
+
# @group Example Notifications
|
|
99
|
+
#
|
|
100
|
+
# Invoked when an example fails.
|
|
101
|
+
#
|
|
102
|
+
# @param notification [Notifications::ExampleNotification] containing example subclass
|
|
103
|
+
# of {Example}
|
|
104
|
+
|
|
105
|
+
# @method message(notification)
|
|
106
|
+
# @api public
|
|
107
|
+
# @group Suite Notifications
|
|
108
|
+
#
|
|
109
|
+
# Used by the reporter to send messages to the output stream.
|
|
110
|
+
#
|
|
111
|
+
# @param notification [Notifications::MessageNotification] containing message
|
|
112
|
+
|
|
113
|
+
# @method stop(notification)
|
|
114
|
+
# @api public
|
|
115
|
+
# @group Suite Notifications
|
|
116
|
+
#
|
|
117
|
+
# Invoked after all examples have executed, before dumping post-run
|
|
118
|
+
# reports.
|
|
119
|
+
#
|
|
120
|
+
# @param notification [Notifications::NullNotification]
|
|
121
|
+
|
|
122
|
+
# @method start_dump(notification)
|
|
123
|
+
# @api public
|
|
124
|
+
# @group Suite Notifications
|
|
125
|
+
#
|
|
126
|
+
# This method is invoked after all of the examples have executed. The
|
|
127
|
+
# next method to be invoked after this one is {#dump_failures}
|
|
128
|
+
# (BaseTextFormatter then calls {#dump_failures} once for each failed
|
|
129
|
+
# example).
|
|
130
|
+
#
|
|
131
|
+
# @param notification [Notifications::NullNotification]
|
|
132
|
+
|
|
133
|
+
# @method dump_failures(notification)
|
|
134
|
+
# @api public
|
|
135
|
+
# @group Suite Notifications
|
|
136
|
+
#
|
|
137
|
+
# Dumps detailed information about each example failure.
|
|
138
|
+
#
|
|
139
|
+
# @param notification [Notifications::NullNotification]
|
|
140
|
+
|
|
141
|
+
# @method dump_summary(summary)
|
|
142
|
+
# @api public
|
|
143
|
+
# @group Suite Notifications
|
|
144
|
+
#
|
|
145
|
+
# This method is invoked after the dumping of examples and failures.
|
|
146
|
+
# Each parameter is assigned to a corresponding attribute.
|
|
147
|
+
#
|
|
148
|
+
# @param summary [Notifications::SummaryNotification] containing duration,
|
|
149
|
+
# example_count, failure_count and pending_count
|
|
150
|
+
|
|
151
|
+
# @method dump_profile(profile)
|
|
152
|
+
# @api public
|
|
153
|
+
# @group Suite Notifications
|
|
154
|
+
#
|
|
155
|
+
# This method is invoked after the dumping the summary if profiling is
|
|
156
|
+
# enabled.
|
|
157
|
+
#
|
|
158
|
+
# @param profile [Notifications::ProfileNotification] containing duration,
|
|
159
|
+
# slowest_examples and slowest_example_groups
|
|
160
|
+
|
|
161
|
+
# @method dump_pending(notification)
|
|
162
|
+
# @api public
|
|
163
|
+
# @group Suite Notifications
|
|
164
|
+
#
|
|
165
|
+
# Outputs a report of pending examples. This gets invoked
|
|
166
|
+
# after the summary if option is set to do so.
|
|
167
|
+
#
|
|
168
|
+
# @param notification [Notifications::NullNotification]
|
|
169
|
+
|
|
170
|
+
# @method close(notification)
|
|
171
|
+
# @api public
|
|
172
|
+
# @group Suite Notifications
|
|
173
|
+
#
|
|
174
|
+
# Invoked at the end of a suite run. Allows the formatter to do any
|
|
175
|
+
# tidying up, but be aware that formatter output streams may be used
|
|
176
|
+
# elsewhere so don't actually close them.
|
|
177
|
+
#
|
|
178
|
+
# @param notification [Notifications::NullNotification]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -1,102 +1,133 @@
|
|
|
1
1
|
module RSpec
|
|
2
2
|
module Core
|
|
3
3
|
module Formatters
|
|
4
|
-
# @
|
|
5
|
-
#
|
|
6
|
-
# Extracts code snippets by looking at the backtrace of the passed error and applies synax highlighting and line numbers using html.
|
|
4
|
+
# @private
|
|
7
5
|
class SnippetExtractor
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def convert(code)
|
|
11
|
-
%Q(#{code}\n<span class="comment"># Install the coderay gem to get syntax highlighting</span>)
|
|
12
|
-
end
|
|
13
|
-
end
|
|
6
|
+
NoSuchFileError = Class.new(StandardError)
|
|
7
|
+
NoSuchLineError = Class.new(StandardError)
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
def self.extract_line_at(file_path, line_number)
|
|
10
|
+
source = source_from_file(file_path)
|
|
11
|
+
line = source.lines[line_number - 1]
|
|
12
|
+
raise NoSuchLineError unless line
|
|
13
|
+
line
|
|
20
14
|
end
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rescue LoadError
|
|
26
|
-
@@converter = NullConverter.new
|
|
16
|
+
def self.source_from_file(path)
|
|
17
|
+
raise NoSuchFileError unless File.exist?(path)
|
|
18
|
+
RSpec.world.source_from_file(path)
|
|
27
19
|
end
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# Extract lines of code corresponding to a backtrace.
|
|
32
|
-
#
|
|
33
|
-
# @param backtrace [String] the backtrace from a test failure
|
|
34
|
-
# @return [String] highlighted code snippet indicating where the test failure occured
|
|
35
|
-
#
|
|
36
|
-
# @see #post_process
|
|
37
|
-
def snippet(backtrace)
|
|
38
|
-
raw_code, line = snippet_for(backtrace[0])
|
|
39
|
-
highlighted = @@converter.convert(raw_code)
|
|
40
|
-
post_process(highlighted, line)
|
|
41
|
-
end
|
|
21
|
+
if RSpec::Support::RubyFeatures.ripper_supported?
|
|
22
|
+
NoExpressionAtLineError = Class.new(StandardError)
|
|
42
23
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if error_line =~ /(.*):(\d+)/
|
|
53
|
-
file = $1
|
|
54
|
-
line = $2.to_i
|
|
55
|
-
[lines_around(file, line), line]
|
|
56
|
-
else
|
|
57
|
-
["# Couldn't get snippet for #{error_line}", 1]
|
|
24
|
+
attr_reader :source, :beginning_line_number, :max_line_count
|
|
25
|
+
|
|
26
|
+
def self.extract_expression_lines_at(file_path, beginning_line_number, max_line_count=nil)
|
|
27
|
+
if max_line_count == 1
|
|
28
|
+
[extract_line_at(file_path, beginning_line_number)]
|
|
29
|
+
else
|
|
30
|
+
source = source_from_file(file_path)
|
|
31
|
+
new(source, beginning_line_number, max_line_count).expression_lines
|
|
32
|
+
end
|
|
58
33
|
end
|
|
59
|
-
end
|
|
60
34
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
35
|
+
def initialize(source, beginning_line_number, max_line_count=nil)
|
|
36
|
+
@source = source
|
|
37
|
+
@beginning_line_number = beginning_line_number
|
|
38
|
+
@max_line_count = max_line_count
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def expression_lines
|
|
42
|
+
line_range = line_range_of_expression
|
|
43
|
+
|
|
44
|
+
if max_line_count && line_range.count > max_line_count
|
|
45
|
+
line_range = (line_range.begin)..(line_range.begin + max_line_count - 1)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
source.lines[(line_range.begin - 1)..(line_range.end - 1)]
|
|
49
|
+
rescue SyntaxError, NoExpressionAtLineError
|
|
50
|
+
[self.class.extract_line_at(source.path, beginning_line_number)]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def line_range_of_expression
|
|
56
|
+
@line_range_of_expression ||= begin
|
|
57
|
+
line_range = line_range_of_location_nodes_in_expression
|
|
58
|
+
initial_unclosed_tokens = unclosed_tokens_in_line_range(line_range)
|
|
59
|
+
unclosed_tokens = initial_unclosed_tokens
|
|
60
|
+
|
|
61
|
+
until (initial_unclosed_tokens & unclosed_tokens).empty?
|
|
62
|
+
line_range = (line_range.begin)..(line_range.end + 1)
|
|
63
|
+
unclosed_tokens = unclosed_tokens_in_line_range(line_range)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
line_range
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def unclosed_tokens_in_line_range(line_range)
|
|
71
|
+
tokens = FlatMap.flat_map(line_range) do |line_number|
|
|
72
|
+
source.tokens_by_line_number[line_number]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
tokens.each_with_object([]) do |token, unclosed_tokens|
|
|
76
|
+
if token.opening?
|
|
77
|
+
unclosed_tokens << token
|
|
78
|
+
else
|
|
79
|
+
index = unclosed_tokens.rindex do |unclosed_token|
|
|
80
|
+
unclosed_token.closed_by?(token)
|
|
81
|
+
end
|
|
82
|
+
unclosed_tokens.delete_at(index) if index
|
|
83
|
+
end
|
|
84
|
+
end
|
|
78
85
|
end
|
|
79
|
-
rescue SecurityError
|
|
80
|
-
"# Couldn't get snippet for #{file}"
|
|
81
|
-
end
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# @return [String] completed snippet
|
|
90
|
-
def post_process(highlighted, offending_line)
|
|
91
|
-
new_lines = []
|
|
92
|
-
highlighted.split("\n").each_with_index do |line, i|
|
|
93
|
-
new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
|
|
94
|
-
new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
|
|
95
|
-
new_lines << new_line
|
|
87
|
+
def line_range_of_location_nodes_in_expression
|
|
88
|
+
line_numbers = expression_node.each_with_object(Set.new) do |node, set|
|
|
89
|
+
set << node.location.line if node.location
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
line_numbers.min..line_numbers.max
|
|
96
93
|
end
|
|
97
|
-
|
|
94
|
+
|
|
95
|
+
def expression_node
|
|
96
|
+
raise NoExpressionAtLineError if location_nodes_at_beginning_line.empty?
|
|
97
|
+
|
|
98
|
+
@expression_node ||= begin
|
|
99
|
+
common_ancestor_nodes = location_nodes_at_beginning_line.map do |node|
|
|
100
|
+
node.each_ancestor.to_a
|
|
101
|
+
end.reduce(:&)
|
|
102
|
+
|
|
103
|
+
common_ancestor_nodes.find { |node| expression_outmost_node?(node) }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def expression_outmost_node?(node)
|
|
108
|
+
return true unless node.parent
|
|
109
|
+
return false if node.type.to_s.start_with?('@')
|
|
110
|
+
![node, node.parent].all? do |n|
|
|
111
|
+
# See `Ripper::PARSER_EVENTS` for the complete list of sexp types.
|
|
112
|
+
type = n.type.to_s
|
|
113
|
+
type.end_with?('call') || type.start_with?('method_add_')
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def location_nodes_at_beginning_line
|
|
118
|
+
source.nodes_by_line_number[beginning_line_number]
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
# :nocov:
|
|
122
|
+
def self.extract_expression_lines_at(file_path, beginning_line_number, *)
|
|
123
|
+
[extract_line_at(file_path, beginning_line_number)]
|
|
124
|
+
end
|
|
125
|
+
# :nocov:
|
|
98
126
|
end
|
|
99
127
|
|
|
128
|
+
def self.least_indentation_from(lines)
|
|
129
|
+
lines.map { |line| line[/^[ \t]*/] }.min
|
|
130
|
+
end
|
|
100
131
|
end
|
|
101
132
|
end
|
|
102
133
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Core
|
|
3
|
+
module Formatters
|
|
4
|
+
# @private
|
|
5
|
+
# Provides terminal syntax highlighting of code snippets
|
|
6
|
+
# when coderay is available.
|
|
7
|
+
class SyntaxHighlighter
|
|
8
|
+
def initialize(configuration)
|
|
9
|
+
@configuration = configuration
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def highlight(lines)
|
|
13
|
+
implementation.highlight_syntax(lines)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# rubocop:disable Lint/RescueException
|
|
17
|
+
# rubocop:disable Lint/HandleExceptions
|
|
18
|
+
def self.attempt_to_add_rspec_terms_to_coderay_keywords
|
|
19
|
+
CodeRay::Scanners::Ruby::Patterns::IDENT_KIND.add(%w[
|
|
20
|
+
describe context
|
|
21
|
+
it specify
|
|
22
|
+
before after around
|
|
23
|
+
let subject
|
|
24
|
+
expect allow
|
|
25
|
+
], :keyword)
|
|
26
|
+
rescue Exception
|
|
27
|
+
# Mutating CodeRay's contants like this is not a public API
|
|
28
|
+
# and might not always work. If we cannot add our keywords
|
|
29
|
+
# to CodeRay it is not a big deal and not worth raising an
|
|
30
|
+
# error over, so we ignore it.
|
|
31
|
+
end
|
|
32
|
+
# rubocop:enable Lint/HandleExceptions
|
|
33
|
+
# rubocop:enable Lint/RescueException
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
if RSpec::Support::OS.windows?
|
|
38
|
+
# :nocov:
|
|
39
|
+
def implementation
|
|
40
|
+
WindowsImplementation
|
|
41
|
+
end
|
|
42
|
+
# :nocov:
|
|
43
|
+
else
|
|
44
|
+
def implementation
|
|
45
|
+
return color_enabled_implementation if @configuration.color_enabled?
|
|
46
|
+
NoSyntaxHighlightingImplementation
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def color_enabled_implementation
|
|
51
|
+
@color_enabled_implementation ||= begin
|
|
52
|
+
require 'coderay'
|
|
53
|
+
self.class.attempt_to_add_rspec_terms_to_coderay_keywords
|
|
54
|
+
CodeRayImplementation
|
|
55
|
+
rescue LoadError
|
|
56
|
+
NoSyntaxHighlightingImplementation
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @private
|
|
61
|
+
module CodeRayImplementation
|
|
62
|
+
RESET_CODE = "\e[0m"
|
|
63
|
+
|
|
64
|
+
def self.highlight_syntax(lines)
|
|
65
|
+
highlighted = begin
|
|
66
|
+
CodeRay.encode(lines.join("\n"), :ruby, :terminal)
|
|
67
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
|
|
68
|
+
return lines
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
highlighted.split("\n").map do |line|
|
|
72
|
+
line.sub(/\S/) { |char| char.insert(0, RESET_CODE) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @private
|
|
78
|
+
module NoSyntaxHighlightingImplementation
|
|
79
|
+
def self.highlight_syntax(lines)
|
|
80
|
+
lines
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @private
|
|
85
|
+
# Not sure why, but our code above (and/or coderay itself) does not work
|
|
86
|
+
# on Windows, so we disable the feature on Windows.
|
|
87
|
+
WindowsImplementation = NoSyntaxHighlightingImplementation
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|