cucumber 3.0.2 → 4.0.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 +5 -5
- data/CHANGELOG.md +216 -17
- data/CONTRIBUTING.md +4 -21
- data/README.md +8 -10
- data/bin/cucumber +1 -1
- data/lib/autotest/cucumber.rb +1 -0
- data/lib/autotest/cucumber_mixin.rb +35 -39
- data/lib/autotest/cucumber_rails.rb +1 -0
- data/lib/autotest/cucumber_rails_rspec.rb +1 -0
- data/lib/autotest/cucumber_rails_rspec2.rb +1 -0
- data/lib/autotest/cucumber_rspec.rb +1 -0
- data/lib/autotest/cucumber_rspec2.rb +1 -0
- data/lib/autotest/discover.rb +1 -0
- data/lib/cucumber.rb +2 -1
- data/lib/cucumber/cli/configuration.rb +6 -5
- data/lib/cucumber/cli/main.rb +14 -14
- data/lib/cucumber/cli/options.rb +113 -116
- data/lib/cucumber/cli/profile_loader.rb +50 -29
- data/lib/cucumber/cli/rerun_file.rb +1 -0
- data/lib/cucumber/configuration.rb +38 -29
- data/lib/cucumber/constantize.rb +8 -10
- data/lib/cucumber/core_ext/string.rb +1 -0
- data/lib/cucumber/deprecate.rb +32 -8
- data/lib/cucumber/encoding.rb +2 -1
- data/lib/cucumber/errors.rb +6 -7
- data/lib/cucumber/events.rb +14 -7
- data/lib/cucumber/events/envelope.rb +9 -0
- data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
- data/lib/cucumber/events/gherkin_source_read.rb +1 -4
- data/lib/cucumber/events/hook_test_step_created.rb +13 -0
- data/lib/cucumber/events/step_activated.rb +6 -6
- data/lib/cucumber/events/step_definition_registered.rb +4 -8
- data/lib/cucumber/events/test_case_created.rb +13 -0
- data/lib/cucumber/events/test_case_finished.rb +0 -4
- data/lib/cucumber/events/test_case_ready.rb +12 -0
- data/lib/cucumber/events/test_case_started.rb +0 -4
- data/lib/cucumber/events/test_run_finished.rb +2 -3
- data/lib/cucumber/events/test_run_started.rb +2 -4
- data/lib/cucumber/events/test_step_created.rb +13 -0
- data/lib/cucumber/events/test_step_finished.rb +0 -4
- data/lib/cucumber/events/test_step_started.rb +1 -5
- data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
- data/lib/cucumber/file_specs.rb +7 -6
- data/lib/cucumber/filters.rb +2 -0
- data/lib/cucumber/filters/activate_steps.rb +6 -4
- data/lib/cucumber/filters/apply_after_hooks.rb +1 -0
- data/lib/cucumber/filters/apply_after_step_hooks.rb +1 -0
- data/lib/cucumber/filters/apply_around_hooks.rb +1 -0
- data/lib/cucumber/filters/apply_before_hooks.rb +1 -0
- data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
- data/lib/cucumber/filters/broadcast_test_run_started_event.rb +2 -1
- data/lib/cucumber/filters/gated_receiver.rb +1 -2
- data/lib/cucumber/filters/prepare_world.rb +6 -13
- data/lib/cucumber/filters/quit.rb +3 -6
- data/lib/cucumber/filters/randomizer.rb +6 -7
- data/lib/cucumber/filters/retry.rb +2 -2
- data/lib/cucumber/filters/tag_limits.rb +2 -2
- data/lib/cucumber/filters/tag_limits/test_case_index.rb +1 -2
- data/lib/cucumber/filters/tag_limits/verifier.rb +3 -6
- data/lib/cucumber/formatter/ansicolor.rb +33 -37
- data/lib/cucumber/formatter/ast_lookup.rb +165 -0
- data/lib/cucumber/formatter/backtrace_filter.rb +10 -10
- data/lib/cucumber/formatter/console.rb +65 -74
- data/lib/cucumber/formatter/console_counts.rb +4 -9
- data/lib/cucumber/formatter/console_issues.rb +9 -6
- data/lib/cucumber/formatter/duration.rb +2 -1
- data/lib/cucumber/formatter/duration_extractor.rb +4 -2
- data/lib/cucumber/formatter/errors.rb +6 -0
- data/lib/cucumber/formatter/fail_fast.rb +9 -6
- data/lib/cucumber/formatter/fanout.rb +3 -3
- data/lib/cucumber/formatter/html.rb +11 -602
- data/lib/cucumber/formatter/http_io.rb +146 -0
- data/lib/cucumber/formatter/ignore_missing_messages.rb +2 -3
- data/lib/cucumber/formatter/interceptor.rb +11 -18
- data/lib/cucumber/formatter/io.rb +18 -11
- data/lib/cucumber/formatter/json.rb +102 -109
- data/lib/cucumber/formatter/junit.rb +73 -68
- data/lib/cucumber/formatter/message.rb +22 -0
- data/lib/cucumber/formatter/message_builder.rb +255 -0
- data/lib/cucumber/formatter/pretty.rb +360 -153
- data/lib/cucumber/formatter/progress.rb +31 -32
- data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
- data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
- data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
- data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
- data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
- data/lib/cucumber/formatter/rerun.rb +23 -4
- data/lib/cucumber/formatter/stepdefs.rb +2 -2
- data/lib/cucumber/formatter/steps.rb +4 -5
- data/lib/cucumber/formatter/summary.rb +17 -9
- data/lib/cucumber/formatter/unicode.rb +16 -18
- data/lib/cucumber/formatter/usage.rb +30 -26
- data/lib/cucumber/gherkin/data_table_parser.rb +18 -6
- data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +83 -86
- data/lib/cucumber/gherkin/formatter/escaping.rb +13 -12
- data/lib/cucumber/gherkin/i18n.rb +1 -0
- data/lib/cucumber/gherkin/steps_parser.rb +18 -8
- data/lib/cucumber/glue/dsl.rb +2 -1
- data/lib/cucumber/glue/hook.rb +35 -11
- data/lib/cucumber/glue/invoke_in_world.rb +15 -20
- data/lib/cucumber/glue/proto_world.rb +47 -39
- data/lib/cucumber/glue/registry_and_more.rb +54 -23
- data/lib/cucumber/glue/snippet.rb +24 -27
- data/lib/cucumber/glue/step_definition.rb +51 -28
- data/lib/cucumber/glue/world_factory.rb +1 -3
- data/lib/cucumber/hooks.rb +24 -14
- data/lib/cucumber/load_path.rb +1 -0
- data/lib/cucumber/multiline_argument.rb +6 -8
- data/lib/cucumber/multiline_argument/data_table.rb +106 -73
- data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +8 -11
- data/lib/cucumber/multiline_argument/doc_string.rb +2 -1
- data/lib/cucumber/platform.rb +4 -3
- data/lib/cucumber/project_initializer.rb +1 -1
- data/lib/cucumber/rake/task.rb +21 -18
- data/lib/cucumber/rspec/disable_option_parser.rb +10 -8
- data/lib/cucumber/rspec/doubles.rb +1 -0
- data/lib/cucumber/running_test_case.rb +4 -54
- data/lib/cucumber/runtime.rb +57 -61
- data/lib/cucumber/runtime/after_hooks.rb +9 -4
- data/lib/cucumber/runtime/before_hooks.rb +9 -4
- data/lib/cucumber/runtime/for_programming_languages.rb +12 -9
- data/lib/cucumber/runtime/step_hooks.rb +5 -2
- data/lib/cucumber/runtime/support_code.rb +16 -22
- data/lib/cucumber/runtime/user_interface.rb +8 -19
- data/lib/cucumber/step_definition_light.rb +6 -4
- data/lib/cucumber/step_definitions.rb +3 -2
- data/lib/cucumber/step_match.rb +20 -18
- data/lib/cucumber/step_match_search.rb +9 -9
- data/lib/cucumber/term/ansicolor.rb +39 -39
- data/lib/cucumber/unit.rb +1 -0
- data/lib/cucumber/version +1 -1
- data/lib/simplecov_setup.rb +1 -0
- metadata +214 -127
- data/lib/cucumber/formatter/cucumber.css +0 -286
- data/lib/cucumber/formatter/cucumber.sass +0 -247
- data/lib/cucumber/formatter/hook_query_visitor.rb +0 -41
- data/lib/cucumber/formatter/html_builder.rb +0 -120
- data/lib/cucumber/formatter/inline-js.js +0 -30
- data/lib/cucumber/formatter/jquery-min.js +0 -154
- data/lib/cucumber/formatter/json_pretty.rb +0 -10
- data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
- data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
- data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
- data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
- data/lib/cucumber/step_argument.rb +0 -24
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/ModuleLength
|
4
|
+
|
2
5
|
require 'cucumber/formatter/ansicolor'
|
3
6
|
require 'cucumber/formatter/duration'
|
4
7
|
require 'cucumber/gherkin/i18n'
|
5
8
|
|
6
9
|
module Cucumber
|
7
10
|
module Formatter
|
8
|
-
|
9
11
|
# This module contains helper methods that are used by formatters that
|
10
12
|
# print output to the terminal.
|
11
13
|
#
|
@@ -46,7 +48,7 @@ module Cucumber
|
|
46
48
|
def format_string(o, status)
|
47
49
|
fmt = format_for(status)
|
48
50
|
o.to_s.split("\n").map do |line|
|
49
|
-
if Proc
|
51
|
+
if Proc == fmt.class
|
50
52
|
fmt.call(line)
|
51
53
|
else
|
52
54
|
fmt % line
|
@@ -54,10 +56,6 @@ module Cucumber
|
|
54
56
|
end.join("\n")
|
55
57
|
end
|
56
58
|
|
57
|
-
def print_steps(status)
|
58
|
-
print_elements(runtime.steps(status), status, 'steps')
|
59
|
-
end
|
60
|
-
|
61
59
|
def print_elements(elements, status, kind)
|
62
60
|
return if elements.empty?
|
63
61
|
|
@@ -109,28 +107,32 @@ module Cucumber
|
|
109
107
|
end
|
110
108
|
|
111
109
|
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/10655
|
112
|
-
def linebreaks(
|
113
|
-
return
|
114
|
-
|
110
|
+
def linebreaks(msg, max)
|
111
|
+
return msg unless max && max > 0
|
112
|
+
msg.gsub(/.{1,#{max}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }.rstrip
|
115
113
|
end
|
116
114
|
|
117
|
-
def collect_snippet_data(test_step,
|
115
|
+
def collect_snippet_data(test_step, ast_lookup)
|
118
116
|
# collect snippet data for undefined steps
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
117
|
+
keyword = ast_lookup.snippet_step_keyword(test_step)
|
118
|
+
@snippets_input << Console::SnippetData.new(keyword, test_step)
|
119
|
+
end
|
120
|
+
|
121
|
+
def collect_undefined_parameter_type_names(undefined_parameter_type)
|
122
|
+
@undefined_parameter_types << undefined_parameter_type.type_name
|
124
123
|
end
|
125
124
|
|
126
125
|
def print_snippets(options)
|
127
126
|
return unless options[:snippets]
|
128
|
-
return if runtime.steps(:undefined).empty?
|
129
127
|
|
130
|
-
snippet_text_proc = lambda
|
131
|
-
|
132
|
-
|
133
|
-
do_print_snippets(snippet_text_proc)
|
128
|
+
snippet_text_proc = lambda do |step_keyword, step_name, multiline_arg|
|
129
|
+
snippet_text(step_keyword, step_name, multiline_arg)
|
130
|
+
end
|
131
|
+
do_print_snippets(snippet_text_proc) unless @snippets_input.empty?
|
132
|
+
|
133
|
+
@undefined_parameter_types.map do |type_name|
|
134
|
+
do_print_undefined_parameter_type_snippet(type_name)
|
135
|
+
end
|
134
136
|
end
|
135
137
|
|
136
138
|
def do_print_snippets(snippet_text_proc)
|
@@ -146,10 +148,14 @@ module Cucumber
|
|
146
148
|
@io.flush
|
147
149
|
end
|
148
150
|
|
149
|
-
def print_passing_wip(
|
150
|
-
return unless
|
151
|
-
|
152
|
-
|
151
|
+
def print_passing_wip(config, passed_test_cases, ast_lookup)
|
152
|
+
return unless config.wip?
|
153
|
+
messages = passed_test_cases.map do |test_case|
|
154
|
+
scenario_source = ast_lookup.scenario_source(test_case)
|
155
|
+
keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
|
156
|
+
linebreaks("#{test_case.location.on_line(test_case.location.lines.max)}:in `#{keyword}: #{test_case.name}'", ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
|
157
|
+
end
|
158
|
+
do_print_passing_wip(messages)
|
153
159
|
end
|
154
160
|
|
155
161
|
def do_print_passing_wip(passed_messages)
|
@@ -161,62 +167,50 @@ module Cucumber
|
|
161
167
|
end
|
162
168
|
end
|
163
169
|
|
164
|
-
def
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
#activate this feature
|
170
|
-
def puts(*messages)
|
171
|
-
if @delayed_messages
|
172
|
-
@delayed_messages += messages
|
173
|
-
else
|
174
|
-
if @io
|
175
|
-
@io.puts
|
176
|
-
messages.each do |message|
|
177
|
-
@io.puts(format_string(message, :tag))
|
178
|
-
end
|
179
|
-
@io.flush
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def print_messages
|
185
|
-
@delayed_messages.each {|message| print_message(message)}
|
186
|
-
empty_messages
|
187
|
-
end
|
188
|
-
|
189
|
-
def print_table_row_messages
|
190
|
-
return if @delayed_messages.empty?
|
191
|
-
@io.print(format_string(@delayed_messages.join(', '), :tag).indent(2))
|
192
|
-
@io.flush
|
193
|
-
empty_messages
|
194
|
-
end
|
195
|
-
|
196
|
-
def print_message(message)
|
197
|
-
@io.puts(format_string(message, :tag).indent(@indent))
|
170
|
+
def attach(src, media_type)
|
171
|
+
return unless media_type == 'text/x.cucumber.log+plain'
|
172
|
+
return unless @io
|
173
|
+
@io.puts
|
174
|
+
@io.puts(format_string(src, :tag))
|
198
175
|
@io.flush
|
199
176
|
end
|
200
177
|
|
201
|
-
def empty_messages
|
202
|
-
@delayed_messages = []
|
203
|
-
end
|
204
|
-
|
205
178
|
def print_profile_information
|
206
179
|
return if @options[:skip_profile_information] || @options[:profiles].nil? || @options[:profiles].empty?
|
207
180
|
do_print_profile_information(@options[:profiles])
|
208
181
|
end
|
209
182
|
|
210
183
|
def do_print_profile_information(profiles)
|
211
|
-
profiles_sentence = profiles.size == 1
|
212
|
-
|
184
|
+
profiles_sentence = if profiles.size == 1
|
185
|
+
profiles.first
|
186
|
+
else
|
187
|
+
"#{profiles[0...-1].join(', ')} and #{profiles.last}"
|
188
|
+
end
|
189
|
+
|
190
|
+
@io.puts "Using the #{profiles_sentence} profile#{'s' if profiles.size > 1}..."
|
191
|
+
end
|
192
|
+
|
193
|
+
def do_print_undefined_parameter_type_snippet(type_name)
|
194
|
+
camelized = type_name.split(/_|-/).collect(&:capitalize).join
|
213
195
|
|
214
|
-
@io.puts
|
196
|
+
@io.puts [
|
197
|
+
"The parameter #{type_name} is not defined. You can define a new one with:",
|
198
|
+
'',
|
199
|
+
'ParameterType(',
|
200
|
+
" name: '#{type_name}',",
|
201
|
+
' regexp: /some regexp here/,',
|
202
|
+
" type: #{camelized},",
|
203
|
+
' # The transformer takes as many arguments as there are capture groups in the regexp,',
|
204
|
+
' # or just one if there are none.',
|
205
|
+
" transformer: ->(s) { #{camelized}.new(s) }",
|
206
|
+
')',
|
207
|
+
''
|
208
|
+
].join("\n")
|
215
209
|
end
|
216
210
|
|
217
211
|
private
|
218
212
|
|
219
|
-
FORMATS = Hash.new{ |hash, format| hash[format] = method(format).to_proc }
|
213
|
+
FORMATS = Hash.new { |hash, format| hash[format] = method(format).to_proc }
|
220
214
|
|
221
215
|
def format_for(*keys)
|
222
216
|
key = keys.join('_').to_sym
|
@@ -225,10 +219,6 @@ module Cucumber
|
|
225
219
|
fmt
|
226
220
|
end
|
227
221
|
|
228
|
-
def hook?(test_step)
|
229
|
-
not test_step.source.last.respond_to?(:actual_keyword)
|
230
|
-
end
|
231
|
-
|
232
222
|
def element_messages(elements, status)
|
233
223
|
elements.map do |element|
|
234
224
|
if status == :failed
|
@@ -241,18 +231,19 @@ module Cucumber
|
|
241
231
|
|
242
232
|
def snippet_text(step_keyword, step_name, multiline_arg)
|
243
233
|
keyword = Cucumber::Gherkin::I18n.code_keyword_for(step_keyword).strip
|
244
|
-
config.snippet_generators.map
|
234
|
+
config.snippet_generators.map do |generator|
|
245
235
|
generator.call(keyword, step_name, multiline_arg, config.snippet_type)
|
246
|
-
|
236
|
+
end.join("\n")
|
247
237
|
end
|
248
238
|
|
249
239
|
class SnippetData
|
250
240
|
attr_reader :actual_keyword, :step
|
251
241
|
def initialize(actual_keyword, step)
|
252
|
-
@actual_keyword
|
242
|
+
@actual_keyword = actual_keyword
|
243
|
+
@step = step
|
253
244
|
end
|
254
245
|
end
|
255
|
-
|
256
246
|
end
|
257
247
|
end
|
258
248
|
end
|
249
|
+
# rubocop:enable Metrics/ModuleLength
|
@@ -29,15 +29,10 @@ module Cucumber
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def status_counts(summary)
|
32
|
-
counts = Core::Test::Result::TYPES.map { |status|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
count > 0
|
37
|
-
}.map { |status, count|
|
38
|
-
format_string("#{count} #{status}", status)
|
39
|
-
}
|
40
|
-
"(#{counts.join(", ")})" if counts.any?
|
32
|
+
counts = Core::Test::Result::TYPES.map { |status| [status, summary.total(status)] }
|
33
|
+
counts = counts.select { |_status, count| count > 0 }
|
34
|
+
counts = counts.map { |status, count| format_string("#{count} #{status}", status) }
|
35
|
+
"(#{counts.join(', ')})" if counts.any?
|
41
36
|
end
|
42
37
|
end
|
43
38
|
end
|
@@ -5,7 +5,7 @@ module Cucumber
|
|
5
5
|
class ConsoleIssues
|
6
6
|
include Console
|
7
7
|
|
8
|
-
def initialize(config)
|
8
|
+
def initialize(config, ast_lookup = AstLookup.new(config))
|
9
9
|
@previous_test_case = nil
|
10
10
|
@issues = Hash.new { |h, k| h[k] = [] }
|
11
11
|
@config = config
|
@@ -14,10 +14,11 @@ module Cucumber
|
|
14
14
|
@previous_test_case = event.test_case
|
15
15
|
@issues[event.result.to_sym] << event.test_case unless event.result.ok?(@config.strict)
|
16
16
|
elsif event.result.passed?
|
17
|
-
@issues[:flaky] << event.test_case unless Core::Test::Result::Flaky.ok?(@config.strict)
|
17
|
+
@issues[:flaky] << event.test_case unless Core::Test::Result::Flaky.ok?(@config.strict.strict?(:flaky))
|
18
18
|
@issues[:failed].delete(event.test_case)
|
19
19
|
end
|
20
20
|
end
|
21
|
+
@ast_lookup = ast_lookup
|
21
22
|
end
|
22
23
|
|
23
24
|
def to_s
|
@@ -34,10 +35,12 @@ module Cucumber
|
|
34
35
|
|
35
36
|
def scenario_listing(type, test_cases)
|
36
37
|
return [] if test_cases.empty?
|
37
|
-
[
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
[format_string("#{type_heading(type)} Scenarios:", type)] + test_cases.map do |test_case|
|
39
|
+
scenario_source = @ast_lookup.scenario_source(test_case)
|
40
|
+
keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
|
41
|
+
source = @config.source? ? format_string(" # #{keyword}: #{test_case.name}", :comment) : ''
|
42
|
+
format_string("cucumber #{profiles_string}#{test_case.location.file}:#{test_case.location.lines.max}", type) + source
|
43
|
+
end
|
41
44
|
end
|
42
45
|
|
43
46
|
def type_heading(type)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Cucumber
|
3
4
|
module Formatter
|
4
5
|
module Duration
|
@@ -7,7 +8,7 @@ module Cucumber
|
|
7
8
|
# <tt>time</tt> format.
|
8
9
|
def format_duration(seconds)
|
9
10
|
m, s = seconds.divmod(60)
|
10
|
-
"#{m}m#{format('
|
11
|
+
"#{m}m#{format('%<seconds>.3f', seconds: s)}s"
|
11
12
|
end
|
12
13
|
end
|
13
14
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Cucumber
|
3
4
|
module Formatter
|
4
|
-
|
5
5
|
class DurationExtractor
|
6
6
|
attr_reader :result_duration
|
7
7
|
def initialize(result)
|
@@ -22,8 +22,10 @@ module Cucumber
|
|
22
22
|
def exception(*) end
|
23
23
|
|
24
24
|
def duration(duration, *)
|
25
|
-
duration.tap { |
|
25
|
+
duration.tap { |dur| @result_duration = dur.nanoseconds / 10**9.0 }
|
26
26
|
end
|
27
|
+
|
28
|
+
def attach(*) end
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
@@ -1,20 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'cucumber/formatter/io'
|
3
4
|
require 'cucumber/formatter/console'
|
4
5
|
|
5
6
|
module Cucumber
|
6
7
|
module Formatter
|
7
|
-
|
8
8
|
class FailFast
|
9
|
-
|
10
9
|
def initialize(configuration)
|
10
|
+
@previous_test_case = nil
|
11
11
|
configuration.on_event :test_case_finished do |event|
|
12
|
-
|
13
|
-
|
12
|
+
test_case, result = *event.attributes
|
13
|
+
if test_case != @previous_test_case
|
14
|
+
@previous_test_case = event.test_case
|
15
|
+
Cucumber.wants_to_quit = true unless result.ok?(configuration.strict)
|
16
|
+
elsif result.passed?
|
17
|
+
Cucumber.wants_to_quit = false
|
18
|
+
end
|
14
19
|
end
|
15
20
|
end
|
16
|
-
|
17
21
|
end
|
18
|
-
|
19
22
|
end
|
20
23
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Cucumber
|
3
4
|
module Formatter
|
4
|
-
|
5
5
|
# Forwards any messages sent to this object to all recipients
|
6
6
|
# that respond to that message.
|
7
7
|
class Fanout < BasicObject
|
@@ -13,6 +13,8 @@ module Cucumber
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def method_missing(message, *args)
|
16
|
+
super unless recipients
|
17
|
+
|
16
18
|
recipients.each do |recipient|
|
17
19
|
recipient.send(message, *args) if recipient.respond_to?(message)
|
18
20
|
end
|
@@ -21,8 +23,6 @@ module Cucumber
|
|
21
23
|
def respond_to_missing?(name, include_private = false)
|
22
24
|
recipients.any? { |recipient| recipient.respond_to?(name, include_private) }
|
23
25
|
end
|
24
|
-
|
25
26
|
end
|
26
|
-
|
27
27
|
end
|
28
28
|
end
|
@@ -1,614 +1,23 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require 'erb'
|
3
|
-
require 'cucumber/formatter/duration'
|
4
1
|
require 'cucumber/formatter/io'
|
5
|
-
require 'cucumber/
|
6
|
-
require '
|
2
|
+
require 'cucumber/html_formatter'
|
3
|
+
require 'cucumber/formatter/message_builder'
|
7
4
|
|
8
5
|
module Cucumber
|
9
6
|
module Formatter
|
10
|
-
class
|
11
|
-
|
12
|
-
# TODO: remove coupling to types
|
13
|
-
AST_CLASSES = {
|
14
|
-
Cucumber::Core::Ast::Scenario => 'scenario',
|
15
|
-
Cucumber::Core::Ast::ScenarioOutline => 'scenario outline'
|
16
|
-
}
|
17
|
-
|
18
|
-
AST_DATA_TABLE = LegacyApi::Ast::MultilineArg::DataTable
|
19
|
-
|
20
|
-
include ERB::Util # for the #h method
|
21
|
-
include Duration
|
7
|
+
class HTML < MessageBuilder
|
22
8
|
include Io
|
23
9
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@io = ensure_io(path_or_io)
|
29
|
-
@runtime = runtime
|
30
|
-
@options = options
|
31
|
-
@buffer = {}
|
32
|
-
@builder = HtmlBuilder.new(target: @io, indent: 0)
|
33
|
-
@feature_number = 0
|
34
|
-
@scenario_number = 0
|
35
|
-
@step_number = 0
|
36
|
-
@header_red = nil
|
37
|
-
@delayed_messages = []
|
38
|
-
@inside_outline = false
|
39
|
-
@previous_step_keyword = nil
|
40
|
-
end
|
41
|
-
|
42
|
-
def embed(src, mime_type, label)
|
43
|
-
if image?(mime_type)
|
44
|
-
src = src_is_file_or_data?(src) ? src : "data:#{standardize_mime_type(mime_type)},#{src}"
|
45
|
-
builder.embed(type: :image, src: set_path(src), label: label, id: next_id(:img))
|
46
|
-
else
|
47
|
-
builder.embed(type: :text, src: src, label: label, id: next_id(:text))
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def set_path(src)
|
52
|
-
if @io.respond_to?(:path) && File.file?(src)
|
53
|
-
out_dir = Pathname.new(File.dirname(File.absolute_path(@io.path)))
|
54
|
-
src = Pathname.new(File.absolute_path(src)).relative_path_from(out_dir)
|
55
|
-
end
|
56
|
-
|
57
|
-
src
|
58
|
-
end
|
59
|
-
|
60
|
-
def standardize_mime_type(mime_type)
|
61
|
-
mime_type =~ /;base[0-9]+$/ ? mime_type : mime_type + ';base64'
|
62
|
-
end
|
63
|
-
|
64
|
-
def src_is_file_or_data?(src)
|
65
|
-
File.file?(src) || src =~ /^data:image\/(png|gif|jpg|jpeg);base64,/
|
66
|
-
end
|
67
|
-
|
68
|
-
def image?(mime_type)
|
69
|
-
mime_type =~ /^image\/(png|gif|jpg|jpeg)/
|
70
|
-
end
|
71
|
-
|
72
|
-
def before_features(features)
|
73
|
-
@step_count = features && features.step_count || 0 #TODO: Make this work with core!
|
74
|
-
|
75
|
-
builder.build_document!
|
76
|
-
builder.format_features! features
|
77
|
-
end
|
78
|
-
|
79
|
-
def after_features(features)
|
80
|
-
print_stats(features)
|
81
|
-
builder << '</div>'
|
82
|
-
builder << '</body>'
|
83
|
-
builder << '</html>'
|
84
|
-
end
|
85
|
-
|
86
|
-
def before_feature(_feature)
|
87
|
-
@exceptions = []
|
88
|
-
builder << '<div class="feature">'
|
89
|
-
end
|
90
|
-
|
91
|
-
def after_feature(_feature)
|
92
|
-
builder << '</div>'
|
93
|
-
end
|
94
|
-
|
95
|
-
def before_comment(_comment)
|
96
|
-
builder << '<pre class="comment">'
|
97
|
-
end
|
98
|
-
|
99
|
-
def after_comment(_comment)
|
100
|
-
builder << '</pre>'
|
101
|
-
end
|
102
|
-
|
103
|
-
def comment_line(comment_line)
|
104
|
-
builder.text!(comment_line)
|
105
|
-
builder.br
|
106
|
-
end
|
107
|
-
|
108
|
-
def after_tags(_tags)
|
109
|
-
@tag_spacer = nil
|
110
|
-
end
|
111
|
-
|
112
|
-
def tag_name(tag_name)
|
113
|
-
builder.text!(@tag_spacer) if @tag_spacer
|
114
|
-
@tag_spacer = ' '
|
115
|
-
builder.span(tag_name, :class => 'tag')
|
116
|
-
end
|
117
|
-
|
118
|
-
def feature_name(keyword, name)
|
119
|
-
lines = name.split(/\r?\n/)
|
120
|
-
return if lines.empty?
|
121
|
-
builder.h2 do |h2|
|
122
|
-
builder.span(keyword + ': ' + lines[0], :class => 'val')
|
123
|
-
end
|
124
|
-
builder.p(:class => 'narrative') do
|
125
|
-
lines[1..-1].each do |line|
|
126
|
-
builder.text!(line.strip)
|
127
|
-
builder.br
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def before_test_case(_test_case)
|
133
|
-
@previous_step_keyword = nil
|
134
|
-
end
|
135
|
-
|
136
|
-
def before_background(_background)
|
137
|
-
@in_background = true
|
138
|
-
builder << '<div class="background">'
|
139
|
-
end
|
140
|
-
|
141
|
-
def after_background(_background)
|
142
|
-
@in_background = nil
|
143
|
-
builder << '</div>'
|
144
|
-
end
|
145
|
-
|
146
|
-
def background_name(keyword, name, _file_colon_line, _source_indent)
|
147
|
-
@listing_background = true
|
148
|
-
builder.h3(:id => "background_#{@scenario_number}") do |h3|
|
149
|
-
builder.span(keyword, :class => 'keyword')
|
150
|
-
builder.text!(' ')
|
151
|
-
builder.span(name, :class => 'val')
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def before_feature_element(feature_element)
|
156
|
-
@scenario_number+=1
|
157
|
-
@scenario_red = false
|
158
|
-
css_class = AST_CLASSES[feature_element.class]
|
159
|
-
builder << "<div class='#{css_class}'>"
|
160
|
-
@in_scenario_outline = feature_element.class == Cucumber::Core::Ast::ScenarioOutline
|
161
|
-
end
|
162
|
-
|
163
|
-
def after_feature_element(_feature_element)
|
164
|
-
unless @in_scenario_outline
|
165
|
-
print_messages
|
166
|
-
builder << '</ol>'
|
167
|
-
end
|
168
|
-
builder << '</div>'
|
169
|
-
@in_scenario_outline = nil
|
170
|
-
end
|
171
|
-
|
172
|
-
def scenario_name(keyword, name, file_colon_line, _source_indent)
|
173
|
-
builder.span(:class => 'scenario_file') do
|
174
|
-
builder << file_colon_line
|
175
|
-
end
|
176
|
-
@listing_background = false
|
177
|
-
scenario_id = "scenario_#{@scenario_number}"
|
178
|
-
if @inside_outline
|
179
|
-
@outline_row += 1
|
180
|
-
scenario_id += "_#{@outline_row}"
|
181
|
-
@scenario_red = false
|
182
|
-
end
|
183
|
-
builder.h3(:id => scenario_id) do
|
184
|
-
builder.span(keyword + ':', :class => 'keyword')
|
185
|
-
builder.text!(' ')
|
186
|
-
builder.span(name, :class => 'val')
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def before_outline_table(_outline_table)
|
191
|
-
@inside_outline = true
|
192
|
-
@outline_row = 0
|
193
|
-
builder << '<table>'
|
194
|
-
end
|
195
|
-
|
196
|
-
def after_outline_table(_outline_table)
|
197
|
-
builder << '</table>'
|
198
|
-
@outline_row = nil
|
199
|
-
@inside_outline = false
|
200
|
-
end
|
201
|
-
|
202
|
-
def before_examples(_examples)
|
203
|
-
builder << '<div class="examples">'
|
204
|
-
end
|
205
|
-
|
206
|
-
def after_examples(_examples)
|
207
|
-
builder << '</div>'
|
208
|
-
end
|
209
|
-
|
210
|
-
def examples_name(keyword, name)
|
211
|
-
builder.h4 do
|
212
|
-
builder.span(keyword, :class => 'keyword')
|
213
|
-
builder.text!(' ')
|
214
|
-
builder.span(name, :class => 'val')
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def before_steps(_steps)
|
219
|
-
builder << '<ol>'
|
220
|
-
end
|
221
|
-
|
222
|
-
def after_steps(_steps)
|
223
|
-
print_messages
|
224
|
-
builder << '</ol>' if @in_background || @in_scenario_outline
|
225
|
-
end
|
226
|
-
|
227
|
-
def before_step(step)
|
228
|
-
print_messages
|
229
|
-
@step_id = step.dom_id
|
230
|
-
@step_number += 1
|
231
|
-
@step = step
|
232
|
-
end
|
233
|
-
|
234
|
-
def after_step(_step)
|
235
|
-
move_progress
|
236
|
-
end
|
237
|
-
|
238
|
-
def before_step_result(_keyword, step_match, _multiline_arg, status, exception, _source_indent, background, _file_colon_line)
|
239
|
-
@step_match = step_match
|
240
|
-
@hide_this_step = false
|
241
|
-
if exception
|
242
|
-
if @exceptions.include?(exception)
|
243
|
-
@hide_this_step = true
|
244
|
-
return
|
245
|
-
end
|
246
|
-
@exceptions << exception
|
247
|
-
end
|
248
|
-
if status != :failed && @in_background ^ background
|
249
|
-
@hide_this_step = true
|
250
|
-
return
|
251
|
-
end
|
252
|
-
@status = status
|
253
|
-
return if @hide_this_step
|
254
|
-
set_scenario_color(status)
|
255
|
-
builder << "<li id='#{@step_id}' class='step #{status}'>"
|
256
|
-
end
|
257
|
-
|
258
|
-
def after_step_result(keyword, step_match, _multiline_arg, status, _exception, _source_indent, _background, _file_colon_line)
|
259
|
-
return if @hide_this_step
|
260
|
-
# print snippet for undefined steps
|
261
|
-
unless outline_step?(@step)
|
262
|
-
keyword = @step.actual_keyword(@previous_step_keyword)
|
263
|
-
@previous_step_keyword = keyword
|
264
|
-
end
|
265
|
-
if status == :undefined
|
266
|
-
builder.pre do |pre|
|
267
|
-
# TODO: snippet text should be an event sent to the formatter so we don't
|
268
|
-
# have this couping to the runtime.
|
269
|
-
pre << @runtime.snippet_text(keyword,step_match.instance_variable_get('@name') || '', @step.multiline_arg)
|
270
|
-
end
|
271
|
-
end
|
272
|
-
builder << '</li>'
|
273
|
-
print_messages
|
274
|
-
end
|
275
|
-
|
276
|
-
def step_name(keyword, step_match, status, _source_indent, background, _file_colon_line)
|
277
|
-
background_in_scenario = background && !@listing_background
|
278
|
-
@skip_step = background_in_scenario
|
279
|
-
|
280
|
-
unless @skip_step
|
281
|
-
build_step(keyword, step_match, status)
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
def exception(exception, _status)
|
286
|
-
return if @hide_this_step
|
287
|
-
print_messages
|
288
|
-
build_exception_detail(exception)
|
289
|
-
end
|
290
|
-
|
291
|
-
def extra_failure_content(file_colon_line)
|
292
|
-
@snippet_extractor ||= SnippetExtractor.new
|
293
|
-
"<pre class=\"ruby\"><code>#{@snippet_extractor.snippet(file_colon_line)}</code></pre>"
|
294
|
-
end
|
295
|
-
|
296
|
-
def before_multiline_arg(multiline_arg)
|
297
|
-
return if @hide_this_step || @skip_step
|
298
|
-
if AST_DATA_TABLE === multiline_arg
|
299
|
-
builder << '<table>'
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
def after_multiline_arg(multiline_arg)
|
304
|
-
return if @hide_this_step || @skip_step
|
305
|
-
if AST_DATA_TABLE === multiline_arg
|
306
|
-
builder << '</table>'
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
def doc_string(string)
|
311
|
-
return if @hide_this_step
|
312
|
-
builder.pre(:class => 'val') do |pre|
|
313
|
-
builder << h(string).gsub("\n", '
')
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
def before_table_row(table_row)
|
318
|
-
@row_id = table_row.dom_id
|
319
|
-
@col_index = 0
|
320
|
-
return if @hide_this_step
|
321
|
-
builder << "<tr class='step' id='#{@row_id}'>"
|
322
|
-
end
|
323
|
-
|
324
|
-
def after_table_row(table_row)
|
325
|
-
return if @hide_this_step
|
326
|
-
print_table_row_messages
|
327
|
-
builder << '</tr>'
|
328
|
-
if table_row.exception
|
329
|
-
builder.tr do
|
330
|
-
builder.td(:colspan => @col_index.to_s, :class => 'failed') do
|
331
|
-
builder.pre do |pre|
|
332
|
-
pre << h(format_exception(table_row.exception))
|
333
|
-
end
|
334
|
-
end
|
335
|
-
end
|
336
|
-
if table_row.exception.is_a? ::Cucumber::Pending
|
337
|
-
set_scenario_color_pending
|
338
|
-
else
|
339
|
-
set_scenario_color_failed
|
340
|
-
end
|
341
|
-
end
|
342
|
-
if @outline_row
|
343
|
-
@outline_row += 1
|
344
|
-
end
|
345
|
-
@step_number += 1
|
346
|
-
move_progress
|
347
|
-
end
|
348
|
-
|
349
|
-
def table_cell_value(value, status)
|
350
|
-
return if @hide_this_step
|
351
|
-
|
352
|
-
@cell_type = @outline_row == 0 ? :th : :td
|
353
|
-
attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'step'}
|
354
|
-
attributes[:class] += " #{status}" if status
|
355
|
-
build_cell(@cell_type, value, attributes)
|
356
|
-
set_scenario_color(status) if @inside_outline
|
357
|
-
@col_index += 1
|
358
|
-
end
|
359
|
-
|
360
|
-
def puts(message)
|
361
|
-
@delayed_messages << message
|
362
|
-
#builder.pre(message, :class => 'message')
|
363
|
-
end
|
364
|
-
|
365
|
-
def print_messages
|
366
|
-
return if @delayed_messages.empty?
|
367
|
-
|
368
|
-
#builder.ol do
|
369
|
-
@delayed_messages.each do |ann|
|
370
|
-
builder.li(:class => 'step message') do
|
371
|
-
builder << ann
|
372
|
-
end
|
373
|
-
end
|
374
|
-
#end
|
375
|
-
empty_messages
|
376
|
-
end
|
377
|
-
|
378
|
-
def print_table_row_messages
|
379
|
-
return if @delayed_messages.empty?
|
10
|
+
def initialize(config)
|
11
|
+
@io = ensure_io(config.out_stream)
|
12
|
+
@html_formatter = Cucumber::HTMLFormatter::Formatter.new(@io)
|
13
|
+
@html_formatter.write_pre_message
|
380
14
|
|
381
|
-
|
382
|
-
builder << @delayed_messages.join(', ')
|
383
|
-
end
|
384
|
-
empty_messages
|
15
|
+
super(config)
|
385
16
|
end
|
386
17
|
|
387
|
-
def
|
388
|
-
@
|
389
|
-
|
390
|
-
|
391
|
-
def after_test_case(_test_case, result)
|
392
|
-
if result.failed? && !@scenario_red
|
393
|
-
set_scenario_color_failed
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
protected
|
398
|
-
|
399
|
-
def next_id(type)
|
400
|
-
@indices ||= Hash.new { 0 }
|
401
|
-
@indices[type] += 1
|
402
|
-
"#{type}_#{@indices[type]}"
|
403
|
-
end
|
404
|
-
|
405
|
-
def build_exception_detail(exception)
|
406
|
-
backtrace = Array.new
|
407
|
-
|
408
|
-
builder.div(:class => 'message') do
|
409
|
-
message = exception.message
|
410
|
-
|
411
|
-
if defined?(RAILS_ROOT) && message.include?('Exception caught')
|
412
|
-
matches = message.match(/Showing <i>(.+)<\/i>(?:.+) #(\d+)/)
|
413
|
-
backtrace += ["#{RAILS_ROOT}/#{matches[1]}:#{matches[2]}"] if matches
|
414
|
-
matches = message.match(/<code>([^(\/)]+)<\//m)
|
415
|
-
message = matches ? matches[1] : ''
|
416
|
-
end
|
417
|
-
|
418
|
-
unless exception.instance_of?(RuntimeError)
|
419
|
-
message = "#{message} (#{exception.class})"
|
420
|
-
end
|
421
|
-
|
422
|
-
builder.pre do
|
423
|
-
builder.text!(message)
|
424
|
-
end
|
425
|
-
end
|
426
|
-
|
427
|
-
builder.div(:class => 'backtrace') do
|
428
|
-
builder.pre do
|
429
|
-
backtrace = exception.backtrace
|
430
|
-
backtrace.delete_if { |x| x =~ /\/gems\/(cucumber|rspec)/ }
|
431
|
-
builder << backtrace_line(backtrace.join("\n"))
|
432
|
-
end
|
433
|
-
end
|
434
|
-
|
435
|
-
extra = extra_failure_content(backtrace)
|
436
|
-
builder << extra unless extra == ''
|
437
|
-
end
|
438
|
-
|
439
|
-
def set_scenario_color(status)
|
440
|
-
if status.nil? || status == :undefined || status == :pending
|
441
|
-
set_scenario_color_pending
|
442
|
-
end
|
443
|
-
if status == :failed
|
444
|
-
set_scenario_color_failed
|
445
|
-
end
|
446
|
-
end
|
447
|
-
|
448
|
-
def set_scenario_color_failed
|
449
|
-
builder.script do
|
450
|
-
builder.text!("makeRed('cucumber-header');") unless @header_red
|
451
|
-
@header_red = true
|
452
|
-
scenario_or_background = @in_background ? 'background' : 'scenario'
|
453
|
-
builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
|
454
|
-
@scenario_red = true
|
455
|
-
if @options[:expand] && @inside_outline
|
456
|
-
builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}_#{@outline_row}');")
|
457
|
-
end
|
458
|
-
end
|
459
|
-
end
|
460
|
-
|
461
|
-
def set_scenario_color_pending
|
462
|
-
builder.script do
|
463
|
-
builder.text!("makeYellow('cucumber-header');") unless @header_red
|
464
|
-
scenario_or_background = @in_background ? 'background' : 'scenario'
|
465
|
-
builder.text!("makeYellow('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
|
466
|
-
end
|
467
|
-
end
|
468
|
-
|
469
|
-
def build_step(keyword, step_match, _status)
|
470
|
-
step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
|
471
|
-
builder.div(:class => 'step_name') do |div|
|
472
|
-
builder.span(keyword, :class => 'keyword')
|
473
|
-
builder.span(:class => 'step val') do |name|
|
474
|
-
name << h(step_name).gsub(/<span class="(.*?)">/, '<span class="\1">').gsub(/<\/span>/, '</span>')
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
step_file = step_match.file_colon_line
|
479
|
-
step_file.gsub(/^([^:]*\.rb):(\d*)/) do
|
480
|
-
if ENV['TM_PROJECT_DIRECTORY']
|
481
|
-
step_file = "<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
|
482
|
-
end
|
483
|
-
end
|
484
|
-
|
485
|
-
builder.div(:class => 'step_file') do |div|
|
486
|
-
builder.span do
|
487
|
-
builder << step_file
|
488
|
-
end
|
489
|
-
end
|
490
|
-
end
|
491
|
-
|
492
|
-
def build_cell(cell_type, value, attributes)
|
493
|
-
builder.__send__(cell_type, attributes) do
|
494
|
-
builder.div do
|
495
|
-
builder.span(value,:class => 'step param')
|
496
|
-
end
|
497
|
-
end
|
498
|
-
end
|
499
|
-
|
500
|
-
def move_progress
|
501
|
-
builder << " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
|
502
|
-
end
|
503
|
-
|
504
|
-
def percent_done
|
505
|
-
result = 100.0
|
506
|
-
if @step_count != 0
|
507
|
-
result = ((@step_number).to_f / @step_count.to_f * 1000).to_i / 10.0
|
508
|
-
end
|
509
|
-
result
|
510
|
-
end
|
511
|
-
|
512
|
-
def format_exception(exception)
|
513
|
-
([exception.message.to_s] + exception.backtrace).join("\n")
|
514
|
-
end
|
515
|
-
|
516
|
-
def backtrace_line(line)
|
517
|
-
if ENV['TM_PROJECT_DIRECTORY']
|
518
|
-
line.gsub(/^([^:]*\.(?:rb|feature|haml)):(\d*).*$/) do
|
519
|
-
"<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
|
520
|
-
end
|
521
|
-
else
|
522
|
-
line
|
523
|
-
end
|
524
|
-
end
|
525
|
-
|
526
|
-
def print_stats(features)
|
527
|
-
builder << "<script type=\"text/javascript\">document.getElementById('duration').innerHTML = \"Finished in <strong>#{format_duration(features.duration)} seconds</strong>\";</script>"
|
528
|
-
builder << "<script type=\"text/javascript\">document.getElementById('totals').innerHTML = \"#{print_stat_string(features)}\";</script>"
|
529
|
-
end
|
530
|
-
|
531
|
-
def print_stat_string(_features)
|
532
|
-
string = String.new
|
533
|
-
string << dump_count(@runtime.scenarios.length, 'scenario')
|
534
|
-
scenario_count = print_status_counts{|status| @runtime.scenarios(status)}
|
535
|
-
string << scenario_count if scenario_count
|
536
|
-
string << '<br />'
|
537
|
-
string << dump_count(@runtime.steps.length, 'step')
|
538
|
-
step_count = print_status_counts{|status| @runtime.steps(status)}
|
539
|
-
string << step_count if step_count
|
540
|
-
end
|
541
|
-
|
542
|
-
def print_status_counts
|
543
|
-
counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
|
544
|
-
elements = yield status
|
545
|
-
elements.any? ? "#{elements.length} #{status}" : nil
|
546
|
-
end.compact
|
547
|
-
return " (#{counts.join(', ')})" if counts.any?
|
548
|
-
end
|
549
|
-
|
550
|
-
def dump_count(count, what, state=nil)
|
551
|
-
[count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(' ')
|
552
|
-
end
|
553
|
-
|
554
|
-
def outline_step?(_step)
|
555
|
-
not @step.step.respond_to?(:actual_keyword)
|
556
|
-
end
|
557
|
-
|
558
|
-
class SnippetExtractor #:nodoc:
|
559
|
-
class NullConverter; def convert(code, _pre); code; end; end #:nodoc:
|
560
|
-
|
561
|
-
begin
|
562
|
-
require 'syntax/convertors/html'
|
563
|
-
@@converter = Syntax::Convertors::HTML.for_syntax 'ruby'
|
564
|
-
rescue LoadError
|
565
|
-
@@converter = NullConverter.new
|
566
|
-
end
|
567
|
-
|
568
|
-
def snippet(error)
|
569
|
-
raw_code, line = snippet_for(error[0])
|
570
|
-
highlighted = @@converter.convert(raw_code, false)
|
571
|
-
highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
|
572
|
-
post_process(highlighted, line)
|
573
|
-
end
|
574
|
-
|
575
|
-
def snippet_for(error_line)
|
576
|
-
if error_line =~ /(.*):(\d+)/
|
577
|
-
file = $1
|
578
|
-
line = $2.to_i
|
579
|
-
[lines_around(file, line), line]
|
580
|
-
else
|
581
|
-
["# Couldn't get snippet for #{error_line}", 1]
|
582
|
-
end
|
583
|
-
end
|
584
|
-
|
585
|
-
def lines_around(file, line)
|
586
|
-
if File.file?(file)
|
587
|
-
begin
|
588
|
-
lines = File.open(file).read.split("\n")
|
589
|
-
rescue ArgumentError
|
590
|
-
return "# Couldn't get snippet for #{file}"
|
591
|
-
end
|
592
|
-
min = [0, line-3].max
|
593
|
-
max = [line+1, lines.length-1].min
|
594
|
-
selected_lines = []
|
595
|
-
selected_lines.join("\n")
|
596
|
-
lines[min..max].join("\n")
|
597
|
-
else
|
598
|
-
"# Couldn't get snippet for #{file}"
|
599
|
-
end
|
600
|
-
end
|
601
|
-
|
602
|
-
def post_process(highlighted, offending_line)
|
603
|
-
new_lines = []
|
604
|
-
highlighted.split("\n").each_with_index do |line, i|
|
605
|
-
new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
|
606
|
-
new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
|
607
|
-
new_lines << new_line
|
608
|
-
end
|
609
|
-
new_lines.join("\n")
|
610
|
-
end
|
611
|
-
|
18
|
+
def output_envelope(envelope)
|
19
|
+
@html_formatter.write_message(envelope)
|
20
|
+
@html_formatter.write_post_message if envelope.test_run_finished
|
612
21
|
end
|
613
22
|
end
|
614
23
|
end
|