cucumber 3.1.2 → 8.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 +1880 -1146
- data/CONTRIBUTING.md +220 -61
- data/README.md +143 -22
- data/bin/cucumber +1 -1
- data/lib/autotest/cucumber_mixin.rb +49 -53
- data/lib/autotest/discover.rb +3 -2
- data/lib/cucumber/cli/configuration.rb +32 -7
- data/lib/cucumber/cli/main.rb +16 -15
- data/lib/cucumber/cli/options.rb +111 -79
- data/lib/cucumber/cli/profile_loader.rb +45 -26
- data/lib/cucumber/cli/rerun_file.rb +1 -1
- data/lib/cucumber/configuration.rb +47 -31
- data/lib/cucumber/constantize.rb +3 -6
- data/lib/cucumber/deprecate.rb +32 -7
- data/lib/cucumber/errors.rb +5 -7
- data/lib/cucumber/events/envelope.rb +9 -0
- data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
- data/lib/cucumber/events/hook_test_step_created.rb +12 -0
- data/lib/cucumber/events/step_activated.rb +0 -5
- data/lib/cucumber/events/step_definition_registered.rb +0 -5
- data/lib/cucumber/events/test_case_created.rb +12 -0
- data/lib/cucumber/events/test_case_ready.rb +12 -0
- data/lib/cucumber/events/test_run_finished.rb +2 -1
- data/lib/cucumber/events/test_step_created.rb +12 -0
- data/lib/cucumber/events/undefined_parameter_type.rb +9 -0
- data/lib/cucumber/events.rb +15 -8
- data/lib/cucumber/file_specs.rb +8 -7
- data/lib/cucumber/filters/activate_steps.rb +6 -3
- data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
- data/lib/cucumber/filters/prepare_world.rb +5 -9
- data/lib/cucumber/filters/quit.rb +1 -3
- data/lib/cucumber/filters/tag_limits/verifier.rb +3 -7
- data/lib/cucumber/filters/tag_limits.rb +1 -3
- data/lib/cucumber/filters.rb +1 -0
- data/lib/cucumber/formatter/ansicolor.rb +74 -86
- data/lib/cucumber/formatter/ast_lookup.rb +163 -0
- data/lib/cucumber/formatter/backtrace_filter.rb +10 -7
- data/lib/cucumber/formatter/console.rb +76 -68
- data/lib/cucumber/formatter/console_counts.rb +4 -9
- data/lib/cucumber/formatter/console_issues.rb +12 -4
- data/lib/cucumber/formatter/duration.rb +1 -1
- data/lib/cucumber/formatter/duration_extractor.rb +4 -1
- data/lib/cucumber/formatter/errors.rb +7 -0
- data/lib/cucumber/formatter/fanout.rb +3 -1
- data/lib/cucumber/formatter/html.rb +11 -598
- data/lib/cucumber/formatter/http_io.rb +152 -0
- data/lib/cucumber/formatter/ignore_missing_messages.rb +2 -2
- data/lib/cucumber/formatter/interceptor.rb +11 -30
- data/lib/cucumber/formatter/io.rb +57 -13
- data/lib/cucumber/formatter/json.rb +119 -124
- data/lib/cucumber/formatter/junit.rb +75 -55
- data/lib/cucumber/formatter/message.rb +23 -0
- data/lib/cucumber/formatter/message_builder.rb +256 -0
- data/lib/cucumber/formatter/pretty.rb +370 -153
- data/lib/cucumber/formatter/progress.rb +31 -32
- data/lib/cucumber/formatter/publish_banner_printer.rb +77 -0
- data/lib/cucumber/formatter/query/hook_by_test_step.rb +32 -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 +42 -0
- data/lib/cucumber/formatter/rerun.rb +24 -4
- data/lib/cucumber/formatter/stepdefs.rb +1 -2
- data/lib/cucumber/formatter/steps.rb +8 -6
- data/lib/cucumber/formatter/summary.rb +17 -8
- data/lib/cucumber/formatter/unicode.rb +18 -20
- data/lib/cucumber/formatter/url_reporter.rb +17 -0
- data/lib/cucumber/formatter/usage.rb +18 -15
- data/lib/cucumber/gherkin/data_table_parser.rb +18 -6
- data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +14 -18
- data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
- data/lib/cucumber/gherkin/steps_parser.rb +17 -8
- data/lib/cucumber/glue/dsl.rb +29 -15
- data/lib/cucumber/glue/hook.rb +37 -11
- data/lib/cucumber/glue/invoke_in_world.rb +17 -22
- data/lib/cucumber/glue/proto_world.rb +47 -53
- data/lib/cucumber/glue/registry_and_more.rb +62 -17
- data/lib/cucumber/glue/registry_wrapper.rb +31 -0
- data/lib/cucumber/glue/snippet.rb +23 -22
- data/lib/cucumber/glue/step_definition.rb +48 -23
- data/lib/cucumber/glue/world_factory.rb +1 -1
- data/lib/cucumber/hooks.rb +12 -11
- data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +4 -3
- data/lib/cucumber/multiline_argument/data_table.rb +143 -123
- data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
- data/lib/cucumber/multiline_argument.rb +4 -6
- data/lib/cucumber/platform.rb +5 -5
- data/lib/cucumber/rake/task.rb +34 -25
- data/lib/cucumber/rspec/disable_option_parser.rb +15 -11
- data/lib/cucumber/rspec/doubles.rb +3 -5
- data/lib/cucumber/running_test_case.rb +3 -53
- data/lib/cucumber/runtime/after_hooks.rb +8 -4
- data/lib/cucumber/runtime/before_hooks.rb +8 -4
- data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
- data/lib/cucumber/runtime/meta_message_builder.rb +106 -0
- data/lib/cucumber/runtime/step_hooks.rb +6 -2
- data/lib/cucumber/runtime/support_code.rb +16 -15
- data/lib/cucumber/runtime/user_interface.rb +10 -19
- data/lib/cucumber/runtime.rb +78 -76
- data/lib/cucumber/step_definition_light.rb +4 -3
- data/lib/cucumber/step_definitions.rb +2 -2
- data/lib/cucumber/step_match.rb +17 -20
- data/lib/cucumber/step_match_search.rb +5 -3
- data/lib/cucumber/term/ansicolor.rb +72 -48
- data/lib/cucumber/term/banner.rb +57 -0
- data/lib/cucumber/version +1 -1
- data/lib/cucumber.rb +3 -2
- data/lib/simplecov_setup.rb +1 -1
- metadata +279 -81
- data/lib/cucumber/core_ext/string.rb +0 -11
- data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
- data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
- 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 -42
- data/lib/cucumber/formatter/html_builder.rb +0 -121
- 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 -11
- 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 -25
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'json'
|
4
4
|
require 'base64'
|
5
5
|
require 'cucumber/formatter/backtrace_filter'
|
6
6
|
require 'cucumber/formatter/io'
|
7
|
-
require 'cucumber/formatter/
|
7
|
+
require 'cucumber/formatter/ast_lookup'
|
8
8
|
|
9
9
|
module Cucumber
|
10
10
|
module Formatter
|
@@ -13,7 +13,8 @@ module Cucumber
|
|
13
13
|
include Io
|
14
14
|
|
15
15
|
def initialize(config)
|
16
|
-
@io = ensure_io(config.out_stream)
|
16
|
+
@io = ensure_io(config.out_stream, config.error_stream)
|
17
|
+
@ast_lookup = AstLookup.new(config)
|
17
18
|
@feature_hashes = []
|
18
19
|
@step_or_hook_hash = {}
|
19
20
|
config.on_event :test_case_started, &method(:on_test_case_started)
|
@@ -25,36 +26,40 @@ module Cucumber
|
|
25
26
|
|
26
27
|
def on_test_case_started(event)
|
27
28
|
test_case = event.test_case
|
28
|
-
builder = Builder.new(test_case)
|
29
|
-
unless same_feature_as_previous_test_case?(test_case
|
29
|
+
builder = Builder.new(test_case, @ast_lookup)
|
30
|
+
unless same_feature_as_previous_test_case?(test_case)
|
30
31
|
@feature_hash = builder.feature_hash
|
31
32
|
@feature_hashes << @feature_hash
|
32
33
|
end
|
33
34
|
@test_case_hash = builder.test_case_hash
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@element_hash = @test_case_hash
|
40
|
-
end
|
35
|
+
|
36
|
+
@element_hash = nil
|
37
|
+
@element_background_hash = builder.background_hash
|
38
|
+
@in_background = builder.background?
|
39
|
+
|
41
40
|
@any_step_failed = false
|
42
41
|
end
|
43
42
|
|
44
43
|
def on_test_step_started(event)
|
45
44
|
test_step = event.test_step
|
46
45
|
return if internal_hook?(test_step)
|
47
|
-
|
48
|
-
if
|
46
|
+
|
47
|
+
if @element_hash.nil?
|
48
|
+
@element_hash = create_element_hash(test_step)
|
49
|
+
feature_elements << @element_hash
|
50
|
+
end
|
51
|
+
|
52
|
+
if test_step.hook?
|
49
53
|
@step_or_hook_hash = {}
|
50
|
-
hooks_of_type(
|
54
|
+
hooks_of_type(test_step) << @step_or_hook_hash
|
51
55
|
return
|
52
56
|
end
|
53
57
|
if first_step_after_background?(test_step)
|
58
|
+
@in_background = false
|
54
59
|
feature_elements << @test_case_hash
|
55
60
|
@element_hash = @test_case_hash
|
56
61
|
end
|
57
|
-
@step_or_hook_hash = create_step_hash(test_step
|
62
|
+
@step_or_hook_hash = create_step_hash(test_step)
|
58
63
|
steps << @step_or_hook_hash
|
59
64
|
@step_hash = @step_or_hook_hash
|
60
65
|
end
|
@@ -63,55 +68,53 @@ module Cucumber
|
|
63
68
|
test_step, result = *event.attributes
|
64
69
|
result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
|
65
70
|
return if internal_hook?(test_step)
|
71
|
+
|
66
72
|
add_match_and_result(test_step, result)
|
67
73
|
@any_step_failed = true if result.failed?
|
68
74
|
end
|
69
75
|
|
70
76
|
def on_test_case_finished(event)
|
77
|
+
feature_elements << @test_case_hash if @in_background
|
78
|
+
|
71
79
|
_test_case, result = *event.attributes
|
72
80
|
result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
|
73
81
|
add_failed_around_hook(result) if result.failed? && !@any_step_failed
|
74
82
|
end
|
75
83
|
|
76
84
|
def on_test_run_finished(_event)
|
77
|
-
@io.write(
|
78
|
-
end
|
79
|
-
|
80
|
-
def puts(message)
|
81
|
-
test_step_output << message
|
85
|
+
@io.write(JSON.pretty_generate(@feature_hashes))
|
82
86
|
end
|
83
87
|
|
84
|
-
def
|
85
|
-
if
|
86
|
-
|
87
|
-
|
88
|
+
def attach(src, mime_type)
|
89
|
+
if mime_type == 'text/x.cucumber.log+plain'
|
90
|
+
test_step_output << src
|
91
|
+
return
|
92
|
+
end
|
93
|
+
if mime_type =~ /;base64$/
|
94
|
+
mime_type = mime_type[0..-8]
|
95
|
+
data = src
|
88
96
|
else
|
89
|
-
|
90
|
-
mime_type = mime_type[0..-8]
|
91
|
-
data = src
|
92
|
-
else
|
93
|
-
data = encode64(src)
|
94
|
-
end
|
97
|
+
data = encode64(src)
|
95
98
|
end
|
96
99
|
test_step_embeddings << { mime_type: mime_type, data: data }
|
97
100
|
end
|
98
101
|
|
99
102
|
private
|
100
103
|
|
101
|
-
def same_feature_as_previous_test_case?(
|
102
|
-
current_feature[:uri] ==
|
104
|
+
def same_feature_as_previous_test_case?(test_case)
|
105
|
+
current_feature[:uri] == test_case.location.file
|
103
106
|
end
|
104
107
|
|
105
108
|
def first_step_after_background?(test_step)
|
106
|
-
test_step.
|
109
|
+
@in_background && test_step.location.file == @feature_hash[:uri] && test_step.location.lines.max >= @test_case_hash[:line]
|
107
110
|
end
|
108
111
|
|
109
112
|
def internal_hook?(test_step)
|
110
|
-
test_step.
|
113
|
+
test_step.location.file.include?('lib/cucumber/')
|
111
114
|
end
|
112
115
|
|
113
116
|
def current_feature
|
114
|
-
@feature_hash ||= {}
|
117
|
+
@feature_hash ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
|
115
118
|
end
|
116
119
|
|
117
120
|
def feature_elements
|
@@ -122,16 +125,16 @@ module Cucumber
|
|
122
125
|
@element_hash[:steps] ||= []
|
123
126
|
end
|
124
127
|
|
125
|
-
def hooks_of_type(
|
126
|
-
case
|
127
|
-
when
|
128
|
-
|
129
|
-
when
|
130
|
-
|
131
|
-
when
|
132
|
-
|
128
|
+
def hooks_of_type(hook_step)
|
129
|
+
case hook_step.text
|
130
|
+
when 'Before hook'
|
131
|
+
before_hooks
|
132
|
+
when 'After hook'
|
133
|
+
after_hooks
|
134
|
+
when 'AfterStep hook'
|
135
|
+
after_step_hooks
|
133
136
|
else
|
134
|
-
|
137
|
+
raise "Unknown hook type #{hook_step}"
|
135
138
|
end
|
136
139
|
end
|
137
140
|
|
@@ -159,36 +162,44 @@ module Cucumber
|
|
159
162
|
@step_or_hook_hash[:embeddings] ||= []
|
160
163
|
end
|
161
164
|
|
162
|
-
def
|
165
|
+
def create_element_hash(test_step)
|
166
|
+
return @element_background_hash if @in_background && !first_step_after_background?(test_step)
|
167
|
+
|
168
|
+
@in_background = false
|
169
|
+
@test_case_hash
|
170
|
+
end
|
171
|
+
|
172
|
+
def create_step_hash(test_step)
|
173
|
+
step_source = @ast_lookup.step_source(test_step).step
|
163
174
|
step_hash = {
|
164
175
|
keyword: step_source.keyword,
|
165
|
-
name:
|
166
|
-
line:
|
176
|
+
name: test_step.text,
|
177
|
+
line: test_step.location.lines.min
|
167
178
|
}
|
168
|
-
step_hash[:
|
169
|
-
step_hash[:
|
170
|
-
step_hash[:rows] = create_data_table_value(step_source.multiline_arg) if step_source.multiline_arg.data_table?
|
179
|
+
step_hash[:doc_string] = create_doc_string_hash(step_source.doc_string, test_step.multiline_arg.content) unless step_source.doc_string.nil?
|
180
|
+
step_hash[:rows] = create_data_table_value(step_source.data_table) unless step_source.data_table.nil?
|
171
181
|
step_hash
|
172
182
|
end
|
173
183
|
|
174
|
-
def create_doc_string_hash(doc_string)
|
175
|
-
content_type = doc_string.
|
184
|
+
def create_doc_string_hash(doc_string, doc_string_content)
|
185
|
+
content_type = doc_string.media_type || ''
|
176
186
|
{
|
177
|
-
value:
|
187
|
+
value: doc_string_content,
|
178
188
|
content_type: content_type,
|
179
189
|
line: doc_string.location.line
|
180
190
|
}
|
181
191
|
end
|
182
192
|
|
183
193
|
def create_data_table_value(data_table)
|
184
|
-
data_table.
|
185
|
-
{ cells: row }
|
194
|
+
data_table.rows.map do |row|
|
195
|
+
{ cells: row.cells.map(&:value) }
|
186
196
|
end
|
187
197
|
end
|
188
198
|
|
189
199
|
def add_match_and_result(test_step, result)
|
190
200
|
@step_or_hook_hash[:match] = create_match_hash(test_step, result)
|
191
201
|
@step_or_hook_hash[:result] = create_result_hash(result)
|
202
|
+
result.embeddings.each { |e| embed(e['src'], e['mime_type'], e['label']) } if result.respond_to?(:embeddings)
|
192
203
|
end
|
193
204
|
|
194
205
|
def add_failed_around_hook(result)
|
@@ -226,113 +237,97 @@ module Cucumber
|
|
226
237
|
class Builder
|
227
238
|
attr_reader :feature_hash, :background_hash, :test_case_hash
|
228
239
|
|
229
|
-
def initialize(test_case)
|
240
|
+
def initialize(test_case, ast_lookup)
|
230
241
|
@background_hash = nil
|
231
|
-
test_case.
|
232
|
-
|
242
|
+
uri = test_case.location.file
|
243
|
+
feature = ast_lookup.gherkin_document(uri).feature
|
244
|
+
feature(feature, uri)
|
245
|
+
background(feature.children.first.background) unless feature.children.first.background.nil?
|
246
|
+
scenario(ast_lookup.scenario_source(test_case), test_case)
|
233
247
|
end
|
234
248
|
|
235
249
|
def background?
|
236
250
|
@background_hash != nil
|
237
251
|
end
|
238
252
|
|
239
|
-
def feature(feature)
|
253
|
+
def feature(feature, uri)
|
240
254
|
@feature_hash = {
|
241
|
-
|
242
|
-
|
255
|
+
id: create_id(feature.name),
|
256
|
+
uri: uri,
|
243
257
|
keyword: feature.keyword,
|
244
|
-
name: feature.
|
245
|
-
description: feature.description,
|
258
|
+
name: feature.name,
|
259
|
+
description: value_or_empty_string(feature.description),
|
246
260
|
line: feature.location.line
|
247
261
|
}
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
@feature_hash[:tags] + @test_case_hash[:tags]
|
252
|
-
else
|
253
|
-
@feature_hash[:tags]
|
254
|
-
end
|
255
|
-
end
|
256
|
-
@feature_hash[:comments] = Formatter.create_comments_array(feature.comments) unless feature.comments.empty?
|
257
|
-
@test_case_hash[:id].insert(0, @feature_hash[:id] + ';')
|
262
|
+
return if feature.tags.empty?
|
263
|
+
|
264
|
+
@feature_hash[:tags] = create_tags_array_from_hash_array(feature.tags)
|
258
265
|
end
|
259
266
|
|
260
267
|
def background(background)
|
261
268
|
@background_hash = {
|
262
269
|
keyword: background.keyword,
|
263
|
-
name: background.
|
264
|
-
description: background.description,
|
270
|
+
name: background.name,
|
271
|
+
description: value_or_empty_string(background.description),
|
265
272
|
line: background.location.line,
|
266
|
-
type: 'background'
|
273
|
+
type: 'background',
|
274
|
+
steps: []
|
267
275
|
}
|
268
|
-
@background_hash[:comments] = Formatter.create_comments_array(background.comments) unless background.comments.empty?
|
269
276
|
end
|
270
277
|
|
271
|
-
def scenario(
|
278
|
+
def scenario(scenario_source, test_case)
|
279
|
+
scenario = scenario_source.type == :Scenario ? scenario_source.scenario : scenario_source.scenario_outline
|
272
280
|
@test_case_hash = {
|
273
|
-
id:
|
281
|
+
id: "#{@feature_hash[:id]};#{create_id_from_scenario_source(scenario_source)}",
|
274
282
|
keyword: scenario.keyword,
|
275
|
-
name:
|
276
|
-
description: scenario.description,
|
277
|
-
line:
|
278
|
-
type: 'scenario'
|
283
|
+
name: test_case.name,
|
284
|
+
description: value_or_empty_string(scenario.description),
|
285
|
+
line: test_case.location.lines.max,
|
286
|
+
type: 'scenario',
|
287
|
+
steps: []
|
279
288
|
}
|
280
|
-
@test_case_hash[:tags] =
|
281
|
-
@test_case_hash[:comments] = Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
|
289
|
+
@test_case_hash[:tags] = create_tags_array_from_tags_array(test_case.tags) unless test_case.tags.empty?
|
282
290
|
end
|
283
291
|
|
284
|
-
|
285
|
-
@test_case_hash = {
|
286
|
-
id: create_id(scenario) + ';' + @example_id,
|
287
|
-
keyword: scenario.keyword,
|
288
|
-
name: scenario.to_s,
|
289
|
-
description: scenario.description,
|
290
|
-
line: @row.location.line,
|
291
|
-
type: 'scenario'
|
292
|
-
}
|
293
|
-
tags = []
|
294
|
-
tags += create_tags_array(scenario.tags) unless scenario.tags.empty?
|
295
|
-
tags += @examples_table_tags if @examples_table_tags
|
296
|
-
@test_case_hash[:tags] = tags unless tags.empty?
|
297
|
-
comments = []
|
298
|
-
comments += Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
|
299
|
-
comments += @examples_table_comments if @examples_table_comments
|
300
|
-
comments += @row_comments if @row_comments
|
301
|
-
@test_case_hash[:comments] = comments unless comments.empty?
|
302
|
-
end
|
292
|
+
private
|
303
293
|
|
304
|
-
def
|
305
|
-
|
306
|
-
|
307
|
-
@example_id = create_id(examples_table) + ";#{@row.number + 1}"
|
294
|
+
def value_or_empty_string(value)
|
295
|
+
value.nil? ? '' : value
|
296
|
+
end
|
308
297
|
|
309
|
-
|
310
|
-
|
298
|
+
def create_id(name)
|
299
|
+
name.downcase.tr(' ', '-')
|
311
300
|
end
|
312
301
|
|
313
|
-
def
|
314
|
-
|
315
|
-
|
302
|
+
def create_id_from_scenario_source(scenario_source)
|
303
|
+
if scenario_source.type == :Scenario
|
304
|
+
create_id(scenario_source.scenario.name)
|
305
|
+
else
|
306
|
+
scenario_outline_name = scenario_source.scenario_outline.name
|
307
|
+
examples_name = scenario_source.examples.name
|
308
|
+
row_number = calculate_row_number(scenario_source)
|
309
|
+
"#{create_id(scenario_outline_name)};#{create_id(examples_name)};#{row_number}"
|
310
|
+
end
|
316
311
|
end
|
317
312
|
|
318
|
-
|
313
|
+
def calculate_row_number(scenario_source)
|
314
|
+
scenario_source.examples.table_body.each_with_index do |row, index|
|
315
|
+
return index + 2 if row == scenario_source.row
|
316
|
+
end
|
317
|
+
end
|
319
318
|
|
320
|
-
def
|
321
|
-
|
319
|
+
def create_tags_array_from_hash_array(tags)
|
320
|
+
tags_array = []
|
321
|
+
tags.each { |tag| tags_array << { name: tag.name, line: tag.location.line } }
|
322
|
+
tags_array
|
322
323
|
end
|
323
324
|
|
324
|
-
def
|
325
|
+
def create_tags_array_from_tags_array(tags)
|
325
326
|
tags_array = []
|
326
327
|
tags.each { |tag| tags_array << { name: tag.name, line: tag.location.line } }
|
327
328
|
tags_array
|
328
329
|
end
|
329
330
|
end
|
330
331
|
end
|
331
|
-
|
332
|
-
def self.create_comments_array(comments)
|
333
|
-
comments_array = []
|
334
|
-
comments.each { |comment| comments_array << { value: comment.to_s.strip, line: comment.location.line } }
|
335
|
-
comments_array
|
336
|
-
end
|
337
332
|
end
|
338
333
|
end
|
@@ -5,6 +5,7 @@ require 'cucumber/formatter/backtrace_filter'
|
|
5
5
|
require 'cucumber/formatter/io'
|
6
6
|
require 'cucumber/formatter/interceptor'
|
7
7
|
require 'fileutils'
|
8
|
+
require 'cucumber/formatter/ast_lookup'
|
8
9
|
|
9
10
|
module Cucumber
|
10
11
|
module Formatter
|
@@ -19,6 +20,7 @@ module Cucumber
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def initialize(config)
|
23
|
+
@ast_lookup = AstLookup.new(config)
|
22
24
|
config.on_event :test_case_started, &method(:on_test_case_started)
|
23
25
|
config.on_event :test_case_finished, &method(:on_test_case_finished)
|
24
26
|
config.on_event :test_step_finished, &method(:on_test_step_finished)
|
@@ -33,17 +35,15 @@ module Cucumber
|
|
33
35
|
tests: 0,
|
34
36
|
skipped: 0,
|
35
37
|
time: 0,
|
36
|
-
builder: Builder::XmlMarkup.new(:
|
38
|
+
builder: Builder::XmlMarkup.new(indent: 2)
|
37
39
|
}
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
43
|
def on_test_case_started(event)
|
42
44
|
test_case = event.test_case
|
43
|
-
unless same_feature_as_previous_test_case?(test_case
|
44
|
-
|
45
|
-
end
|
46
|
-
@failing_step_source = nil
|
45
|
+
start_feature(test_case) unless same_feature_as_previous_test_case?(test_case)
|
46
|
+
@failing_test_step = nil
|
47
47
|
# In order to fill out <system-err/> and <system-out/>, we need to
|
48
48
|
# intercept the $stderr and $stdout
|
49
49
|
@interceptedout = Interceptor::Pipe.wrap(:stdout)
|
@@ -52,15 +52,15 @@ module Cucumber
|
|
52
52
|
|
53
53
|
def on_test_step_finished(event)
|
54
54
|
test_step, result = *event.attributes
|
55
|
-
return if @
|
55
|
+
return if @failing_test_step
|
56
56
|
|
57
|
-
@
|
57
|
+
@failing_test_step = test_step unless result.ok?(@config.strict)
|
58
58
|
end
|
59
59
|
|
60
60
|
def on_test_case_finished(event)
|
61
61
|
test_case, result = *event.attributes
|
62
62
|
result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
|
63
|
-
test_case_name = NameBuilder.new(test_case)
|
63
|
+
test_case_name = NameBuilder.new(test_case, @ast_lookup)
|
64
64
|
scenario = test_case_name.scenario_name
|
65
65
|
scenario_designation = "#{scenario}#{test_case_name.name_suffix}"
|
66
66
|
output = create_output_string(test_case, scenario, result, test_case_name.row_name)
|
@@ -71,72 +71,82 @@ module Cucumber
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def on_test_run_finished(_event)
|
74
|
-
@features_data.each { |
|
74
|
+
@features_data.each { |_file, data| end_feature(data) }
|
75
75
|
end
|
76
76
|
|
77
77
|
private
|
78
78
|
|
79
|
-
def same_feature_as_previous_test_case?(
|
80
|
-
@current_feature_data && @current_feature_data[:
|
79
|
+
def same_feature_as_previous_test_case?(test_case)
|
80
|
+
@current_feature_data && @current_feature_data[:uri] == test_case.location.file
|
81
81
|
end
|
82
82
|
|
83
|
-
def start_feature(
|
84
|
-
|
85
|
-
|
83
|
+
def start_feature(test_case)
|
84
|
+
uri = test_case.location.file
|
85
|
+
feature = @ast_lookup.gherkin_document(uri).feature
|
86
|
+
raise UnNamedFeatureError, uri if feature.name.empty?
|
87
|
+
|
88
|
+
@current_feature_data = @features_data[uri]
|
89
|
+
@current_feature_data[:uri] = uri unless @current_feature_data[:uri]
|
86
90
|
@current_feature_data[:feature] = feature unless @current_feature_data[:feature]
|
87
91
|
end
|
88
92
|
|
89
93
|
def end_feature(feature_data)
|
90
|
-
@testsuite = Builder::XmlMarkup.new(:
|
94
|
+
@testsuite = Builder::XmlMarkup.new(indent: 2)
|
91
95
|
@testsuite.instruct!
|
92
96
|
@testsuite.testsuite(
|
93
|
-
:
|
94
|
-
:
|
95
|
-
:
|
96
|
-
:
|
97
|
-
:
|
98
|
-
:
|
97
|
+
failures: feature_data[:failures],
|
98
|
+
errors: feature_data[:errors],
|
99
|
+
skipped: feature_data[:skipped],
|
100
|
+
tests: feature_data[:tests],
|
101
|
+
time: format('%<time>.6f', time: feature_data[:time]),
|
102
|
+
name: feature_data[:feature].name
|
99
103
|
) do
|
100
104
|
@testsuite << feature_data[:builder].target!
|
101
105
|
end
|
102
106
|
|
103
|
-
write_file(feature_result_filename(feature_data[:
|
107
|
+
write_file(feature_result_filename(feature_data[:uri]), @testsuite.target!)
|
104
108
|
end
|
105
109
|
|
106
110
|
def create_output_string(test_case, scenario, result, row_name)
|
107
|
-
|
111
|
+
scenario_source = @ast_lookup.scenario_source(test_case)
|
112
|
+
keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
|
113
|
+
output = "#{keyword}: #{scenario}\n\n"
|
108
114
|
return output if result.ok?(@config.strict)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
115
|
+
|
116
|
+
if scenario_source.type == :Scenario
|
117
|
+
if @failing_test_step
|
118
|
+
if @failing_test_step.hook?
|
119
|
+
output += "#{@failing_test_step.text} at #{@failing_test_step.location}\n"
|
120
|
+
else
|
121
|
+
step_source = @ast_lookup.step_source(@failing_test_step).step
|
122
|
+
output += "#{step_source.keyword}#{@failing_test_step.text}\n"
|
123
|
+
end
|
113
124
|
else # An Around hook has failed
|
114
125
|
output += "Around hook\n"
|
115
126
|
end
|
116
127
|
else
|
117
128
|
output += "Example row: #{row_name}\n"
|
118
129
|
end
|
119
|
-
output
|
120
|
-
end
|
121
|
-
|
122
|
-
def hook?(step)
|
123
|
-
['Before hook', 'After hook', 'AfterStep hook'].include? step.text
|
130
|
+
"#{output}\nMessage:\n"
|
124
131
|
end
|
125
132
|
|
126
133
|
def build_testcase(result, scenario_designation, output)
|
127
134
|
duration = ResultBuilder.new(result).test_case_duration
|
128
135
|
@current_feature_data[:time] += duration
|
129
136
|
classname = @current_feature_data[:feature].name
|
137
|
+
filename = @current_feature_data[:uri]
|
130
138
|
name = scenario_designation
|
131
139
|
|
132
|
-
|
140
|
+
testcase_attributes = get_testcase_attributes(classname, name, duration, filename)
|
141
|
+
|
142
|
+
@current_feature_data[:builder].testcase(testcase_attributes) do
|
133
143
|
if !result.passed? && result.ok?(@config.strict)
|
134
144
|
@current_feature_data[:builder].skipped
|
135
145
|
@current_feature_data[:skipped] += 1
|
136
146
|
elsif !result.passed?
|
137
147
|
status = result.to_sym
|
138
148
|
exception = get_backtrace_object(result)
|
139
|
-
@current_feature_data[:builder].failure(:
|
149
|
+
@current_feature_data[:builder].failure(message: "#{status} #{name}", type: status) do
|
140
150
|
@current_feature_data[:builder].cdata! output
|
141
151
|
@current_feature_data[:builder].cdata!(format_exception(exception)) if exception
|
142
152
|
end
|
@@ -152,13 +162,25 @@ module Cucumber
|
|
152
162
|
@current_feature_data[:tests] += 1
|
153
163
|
end
|
154
164
|
|
165
|
+
def get_testcase_attributes(classname, name, duration, filename)
|
166
|
+
{ classname: classname, name: name, time: format('%<duration>.6f', duration: duration) }.tap do |attributes|
|
167
|
+
attributes[:file] = filename if add_fileattribute?
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def add_fileattribute?
|
172
|
+
return false if @config.formats.nil? || @config.formats.empty?
|
173
|
+
|
174
|
+
!!@config.formats.find do |format|
|
175
|
+
format.first == 'junit' && format.dig(1, 'fileattribute') == 'true'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
155
179
|
def get_backtrace_object(result)
|
156
180
|
if result.failed?
|
157
|
-
|
181
|
+
result.exception
|
158
182
|
elsif result.backtrace
|
159
|
-
|
160
|
-
else
|
161
|
-
return nil
|
183
|
+
result
|
162
184
|
end
|
163
185
|
end
|
164
186
|
|
@@ -187,39 +209,35 @@ module Cucumber
|
|
187
209
|
class NameBuilder
|
188
210
|
attr_reader :scenario_name, :name_suffix, :row_name
|
189
211
|
|
190
|
-
def initialize(test_case)
|
212
|
+
def initialize(test_case, ast_lookup)
|
191
213
|
@name_suffix = ''
|
192
214
|
@row_name = ''
|
193
|
-
test_case
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
215
|
+
scenario_source = ast_lookup.scenario_source(test_case)
|
216
|
+
if scenario_source.type == :Scenario
|
217
|
+
scenario(scenario_source.scenario)
|
218
|
+
else
|
219
|
+
scenario_outline(scenario_source.scenario_outline)
|
220
|
+
examples_table_row(scenario_source.row)
|
221
|
+
end
|
198
222
|
end
|
199
223
|
|
200
224
|
def scenario(scenario)
|
201
|
-
@scenario_name =
|
202
|
-
self
|
225
|
+
@scenario_name = scenario.name.empty? ? 'Unnamed scenario' : scenario.name
|
203
226
|
end
|
204
227
|
|
205
228
|
def scenario_outline(outline)
|
206
|
-
@scenario_name =
|
207
|
-
self
|
208
|
-
end
|
209
|
-
|
210
|
-
def examples_table(*)
|
211
|
-
self
|
229
|
+
@scenario_name = outline.name.empty? ? 'Unnamed scenario outline' : outline.name
|
212
230
|
end
|
213
231
|
|
214
232
|
def examples_table_row(row)
|
215
|
-
@row_name =
|
233
|
+
@row_name = "| #{row.cells.map(&:value).join(' | ')} |"
|
216
234
|
@name_suffix = " (outline example : #{@row_name})"
|
217
|
-
self
|
218
235
|
end
|
219
236
|
end
|
220
237
|
|
221
238
|
class ResultBuilder
|
222
239
|
attr_reader :test_case_duration
|
240
|
+
|
223
241
|
def initialize(result)
|
224
242
|
@test_case_duration = 0
|
225
243
|
result.describe_to(self)
|
@@ -238,8 +256,10 @@ module Cucumber
|
|
238
256
|
def exception(*) end
|
239
257
|
|
240
258
|
def duration(duration, *)
|
241
|
-
duration.tap { |
|
259
|
+
duration.tap { |dur| @test_case_duration = dur.nanoseconds / 10**9.0 }
|
242
260
|
end
|
261
|
+
|
262
|
+
def attach(*) end
|
243
263
|
end
|
244
264
|
end
|
245
265
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cucumber/formatter/io'
|
4
|
+
require 'cucumber/formatter/message_builder'
|
5
|
+
|
6
|
+
module Cucumber
|
7
|
+
module Formatter
|
8
|
+
# The formatter used for <tt>--format message</tt>
|
9
|
+
class Message < MessageBuilder
|
10
|
+
include Io
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
@io = ensure_io(config.out_stream, config.error_stream)
|
14
|
+
super(config)
|
15
|
+
end
|
16
|
+
|
17
|
+
def output_envelope(envelope)
|
18
|
+
@io.write(envelope.to_json)
|
19
|
+
@io.write("\n")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|