cucumber 10.2.0 → 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +17 -8
- data/VERSION +1 -1
- data/lib/cucumber/cli/configuration.rb +1 -1
- data/lib/cucumber/cli/main.rb +22 -0
- data/lib/cucumber/cli/options.rb +3 -18
- data/lib/cucumber/cli/profile_loader.rb +2 -2
- data/lib/cucumber/cli/rerun_file.rb +1 -1
- data/lib/cucumber/cli.rb +3 -0
- data/lib/cucumber/configuration.rb +33 -24
- data/lib/cucumber/events/base.rb +25 -0
- data/lib/cucumber/events/envelope.rb +29 -2
- data/lib/cucumber/events/gherkin_source_parsed.rb +12 -3
- data/lib/cucumber/events/gherkin_source_read.rb +11 -3
- data/lib/cucumber/events/hook_test_step_created.rb +12 -2
- data/lib/cucumber/events/step_activated.rb +13 -7
- data/lib/cucumber/events/step_definition_registered.rb +12 -4
- data/lib/cucumber/events/test_case_created.rb +11 -3
- data/lib/cucumber/events/test_case_finished.rb +12 -4
- data/lib/cucumber/events/test_case_ready.rb +10 -4
- data/lib/cucumber/events/test_case_started.rb +11 -2
- data/lib/cucumber/events/test_run_finished.rb +10 -3
- data/lib/cucumber/events/test_run_hook_finished.rb +19 -0
- data/lib/cucumber/events/test_run_hook_started.rb +20 -0
- data/lib/cucumber/events/test_run_started.rb +11 -2
- data/lib/cucumber/events/test_step_created.rb +12 -2
- data/lib/cucumber/events/test_step_finished.rb +12 -2
- data/lib/cucumber/events/test_step_started.rb +11 -2
- data/lib/cucumber/events/undefined_parameter_type.rb +11 -3
- data/lib/cucumber/events.rb +2 -0
- data/lib/cucumber/filters/fire_before_all_hooks.rb +36 -0
- data/lib/cucumber/filters/randomizer.rb +5 -5
- data/lib/cucumber/filters/reverser.rb +41 -0
- data/lib/cucumber/filters.rb +1 -12
- data/lib/cucumber/formatter/ansicolor.rb +3 -0
- data/lib/cucumber/formatter/console.rb +15 -7
- data/lib/cucumber/formatter/console_issues.rb +5 -5
- data/lib/cucumber/formatter/duration_extractor.rb +2 -0
- data/lib/cucumber/formatter/fail_fast.rb +3 -4
- data/lib/cucumber/formatter/global_hooks_summary.rb +36 -0
- data/lib/cucumber/formatter/html.rb +1 -3
- data/lib/cucumber/formatter/json.rb +5 -3
- data/lib/cucumber/formatter/junit.rb +5 -34
- data/lib/cucumber/formatter/message.rb +8 -4
- data/lib/cucumber/formatter/message_builder.rb +249 -143
- data/lib/cucumber/formatter/pretty.rb +2 -5
- data/lib/cucumber/formatter/progress.rb +1 -2
- data/lib/cucumber/formatter/rerun.rb +80 -30
- data/lib/cucumber/formatter/usage.rb +2 -3
- data/lib/cucumber/formatter.rb +3 -0
- data/lib/cucumber/glue/proto_world.rb +6 -4
- data/lib/cucumber/glue/registry_and_more.rb +122 -20
- data/lib/cucumber/glue.rb +3 -0
- data/lib/cucumber/multiline_argument/data_table.rb +0 -11
- data/lib/cucumber/platform.rb +2 -2
- data/lib/cucumber/query.rb +273 -0
- data/lib/cucumber/rake/forked_cucumber_runner.rb +57 -0
- data/lib/cucumber/rake/in_process_cucumber_runner.rb +27 -0
- data/lib/cucumber/rake/task.rb +36 -106
- data/lib/cucumber/rake.rb +3 -0
- data/lib/cucumber/repository.rb +136 -0
- data/lib/cucumber/rspec/disable_option_parser.rb +3 -3
- data/lib/cucumber/runtime/user_interface.rb +2 -2
- data/lib/cucumber/runtime.rb +29 -9
- data/lib/cucumber/step_match.rb +1 -1
- data/lib/cucumber.rb +2 -13
- metadata +33 -30
- data/lib/cucumber/formatter/errors.rb +0 -9
- data/lib/cucumber/formatter/query/hook_by_test_step.rb +0 -34
- data/lib/cucumber/formatter/query/pickle_by_test.rb +0 -28
- data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +0 -28
- data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +0 -42
- data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +0 -44
- data/lib/cucumber/formatter/query/test_run_started.rb +0 -17
- data/lib/cucumber/step_definition_light.rb +0 -29
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cucumber/repository'
|
|
4
|
+
require 'cucumber/messages'
|
|
5
|
+
|
|
6
|
+
# Given one Cucumber Message, find another.
|
|
7
|
+
#
|
|
8
|
+
# Queries can be made while the test run is incomplete - and this will naturally return incomplete results
|
|
9
|
+
# see <a href="https://github.com/cucumber/messages?tab=readme-ov-file#message-overview">Cucumber Messages - Message Overview</a>
|
|
10
|
+
#
|
|
11
|
+
module Cucumber
|
|
12
|
+
class Query
|
|
13
|
+
attr_reader :repository
|
|
14
|
+
private :repository
|
|
15
|
+
|
|
16
|
+
include Cucumber::Messages::Helpers::TimeConversion
|
|
17
|
+
include Cucumber::Messages::Helpers::TestStepResultComparator
|
|
18
|
+
|
|
19
|
+
def initialize(repository)
|
|
20
|
+
@repository = repository
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# TODO: count methods (1/2) Complete
|
|
24
|
+
# Missing: countMostSevereTestStepResultStatus
|
|
25
|
+
|
|
26
|
+
# TODO: findAll methods (11/12) Complete
|
|
27
|
+
# Missing: findAllUndefinedParameterTypes
|
|
28
|
+
|
|
29
|
+
# TODO: find****By methods (16/25) Complete
|
|
30
|
+
# Missing: findSuggestionsBy (2 variants)
|
|
31
|
+
# Missing: findUnambiguousStepDefinitionBy (1 variant)
|
|
32
|
+
# Missing: findTestStepFinishedAndTestStepBy (1 variant)
|
|
33
|
+
# Missing: findAttachmentsBy (2 variants)
|
|
34
|
+
# Missing: findTestCaseDurationBy (2 variant)
|
|
35
|
+
# REDUNDANT: findLineageBy (9 variants!)
|
|
36
|
+
# REDUNDANT: findLocationOf (1 variant) - This strictly speaking isn't a findBy but is located within them
|
|
37
|
+
# To Review: findMostSevereTestStepResultBy (2 variants)
|
|
38
|
+
# To Review: findTestRunDuration (1 variant) - This strictly speaking isn't a findBy but is located within them
|
|
39
|
+
|
|
40
|
+
def count_test_cases_started
|
|
41
|
+
find_all_test_case_started.length
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_all_pickles
|
|
45
|
+
repository.pickle_by_id.values
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def find_all_pickle_steps
|
|
49
|
+
repository.pickle_step_by_id.values
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_all_step_definitions
|
|
53
|
+
repository.step_definition_by_id.values
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# This finds all test cases from the following conditions (UNION)
|
|
57
|
+
# -> Test cases that have started, but not yet finished
|
|
58
|
+
# -> Test cases that have started, finished, but that will NOT be retried
|
|
59
|
+
def find_all_test_case_started
|
|
60
|
+
repository.test_case_started_by_id.values.select do |test_case_started|
|
|
61
|
+
test_case_finished = find_test_case_finished_by(test_case_started)
|
|
62
|
+
test_case_finished.nil? || !test_case_finished.will_be_retried
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# This finds all test cases that have finished AND will not be retried
|
|
67
|
+
def find_all_test_case_finished
|
|
68
|
+
repository.test_case_finished_by_test_case_started_id.values.reject(&:will_be_retried)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def find_all_test_cases
|
|
72
|
+
repository.test_case_by_id.values
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def find_all_test_run_hook_started
|
|
76
|
+
repository.test_run_hook_started_by_id.values
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def find_all_test_run_hook_finished
|
|
80
|
+
repository.test_run_hook_finished_by_test_run_hook_started_id.values
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_all_test_step_started
|
|
84
|
+
repository.test_steps_started_by_test_case_started_id.values.flatten
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def find_all_test_step_finished
|
|
88
|
+
repository.test_steps_finished_by_test_case_started_id.values.flatten
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def find_all_test_steps
|
|
92
|
+
repository.test_step_by_id.values
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# This method will be called with 1 of these 3 messages
|
|
96
|
+
# [TestStep || TestRunHookStarted || TestRunHookFinished]
|
|
97
|
+
def find_hook_by(message)
|
|
98
|
+
ensure_only_message_types!(message, %i[test_step test_run_hook_started test_run_hook_finished], '#find_hook_by')
|
|
99
|
+
|
|
100
|
+
if message.is_a?(Cucumber::Messages::TestRunHookFinished)
|
|
101
|
+
test_run_hook_started_message = find_test_run_hook_started_by(message)
|
|
102
|
+
test_run_hook_started_message ? find_hook_by(test_run_hook_started_message) : nil
|
|
103
|
+
else
|
|
104
|
+
repository.hook_by_id[message.hook_id]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def find_meta
|
|
109
|
+
repository.meta
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# This method will be called with 1 of these 2 messages
|
|
113
|
+
# [TestCaseStarted || TestCaseFinished]
|
|
114
|
+
def find_most_severe_test_step_result_by(message)
|
|
115
|
+
ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_most_severe_test_step_result_by')
|
|
116
|
+
|
|
117
|
+
if message.is_a?(Cucumber::Messages::TestCaseStarted)
|
|
118
|
+
find_test_steps_finished_by(message)
|
|
119
|
+
.map(&:test_step_result)
|
|
120
|
+
.max_by { |test_step_result| test_step_result_rankings[test_step_result.status] }
|
|
121
|
+
# Java code: "PREVIOUS".max(comparing(TestStepResult::getStatus, new TestStepResultStatusComparator()));
|
|
122
|
+
else
|
|
123
|
+
test_case_started_message = find_test_case_started_by(message)
|
|
124
|
+
test_case_started_message && find_most_severe_test_step_result_by(test_case_started_message)
|
|
125
|
+
# Java code: return findTestCaseStartedBy(testCaseFinished).flatMap(this::findMostSevereTestStepResultBy);
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# This method will be called with 1 of these 5 messages
|
|
130
|
+
# [TestCase || TestCaseStarted || TestCaseFinished || TestStepStarted || TestStepFinished]
|
|
131
|
+
def find_pickle_by(message)
|
|
132
|
+
ensure_only_message_types!(message, %i[test_case test_case_started test_case_finished test_step_started test_step_finished], '#find_pickle_by')
|
|
133
|
+
|
|
134
|
+
test_case_message = message.is_a?(Cucumber::Messages::TestCase) ? message : find_test_case_by(message)
|
|
135
|
+
repository.pickle_by_id[test_case_message.pickle_id]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# This method will be called with only 1 message
|
|
139
|
+
# [TestStep]
|
|
140
|
+
def find_pickle_step_by(test_step)
|
|
141
|
+
ensure_only_message_types!(test_step, %i[test_step], '#find_pickle_step_by')
|
|
142
|
+
|
|
143
|
+
repository.pickle_step_by_id[test_step.pickle_step_id]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# This method will be called with only 1 message
|
|
147
|
+
# [PickleStep]
|
|
148
|
+
def find_step_by(pickle_step)
|
|
149
|
+
ensure_only_message_types!(pickle_step, %i[pickle_step], '#find_step_by')
|
|
150
|
+
|
|
151
|
+
repository.step_by_id[pickle_step.ast_node_ids.first]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# This method will be called with only 1 message
|
|
155
|
+
# [TestStep]
|
|
156
|
+
def find_step_definitions_by(test_step)
|
|
157
|
+
ensure_only_message_types!(test_step, %i[test_step], '#find_step_definitions_by')
|
|
158
|
+
|
|
159
|
+
ids = test_step.step_definition_ids.nil? ? [] : test_step.step_definition_ids
|
|
160
|
+
ids.map { |id| repository.step_definition_by_id[id] }.compact
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# This method will be called with 1 of these 4 messages
|
|
164
|
+
# [TestCaseStarted || TestCaseFinished || TestStepStarted || TestStepFinished]
|
|
165
|
+
def find_test_case_by(message)
|
|
166
|
+
ensure_only_message_types!(message, %i[test_case_started test_case_finished test_step_started test_step_finished], '#find_test_case_by')
|
|
167
|
+
|
|
168
|
+
test_case_started_message = message.is_a?(Cucumber::Messages::TestCaseStarted) ? message : find_test_case_started_by(message)
|
|
169
|
+
repository.test_case_by_id[test_case_started_message.test_case_id]
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# This method will be called with 1 of these 3 messages
|
|
173
|
+
# [TestCaseFinished || TestStepStarted || TestStepFinished]
|
|
174
|
+
def find_test_case_started_by(message)
|
|
175
|
+
ensure_only_message_types!(message, %i[test_case_finished test_step_started test_step_finished], '#find_test_case_started_by')
|
|
176
|
+
|
|
177
|
+
repository.test_case_started_by_id[message.test_case_started_id]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# This method will be called with only 1 message
|
|
181
|
+
# [TestCaseStarted]
|
|
182
|
+
def find_test_case_finished_by(test_case_started)
|
|
183
|
+
ensure_only_message_types!(test_case_started, %i[test_case_started], '#find_test_case_finished_by')
|
|
184
|
+
|
|
185
|
+
repository.test_case_finished_by_test_case_started_id[test_case_started.id]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def find_test_run_duration
|
|
189
|
+
if repository.test_run_started.nil? || repository.test_run_finished.nil?
|
|
190
|
+
nil
|
|
191
|
+
else
|
|
192
|
+
timestamp_to_time(repository.test_run_finished.timestamp) - timestamp_to_time(repository.test_run_started.timestamp)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# This method will be called with only 1 message
|
|
197
|
+
# [TestRunHookFinished]
|
|
198
|
+
def find_test_run_hook_started_by(test_run_hook_finished)
|
|
199
|
+
ensure_only_message_types!(test_run_hook_finished, %i[test_run_hook_finished], '#find_test_run_hook_started_by')
|
|
200
|
+
|
|
201
|
+
repository.test_run_hook_started_by_id[test_run_hook_finished.test_run_hook_started_id]
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# This method will be called with only 1 message
|
|
205
|
+
# [TestRunHookStarted]
|
|
206
|
+
def find_test_run_hook_finished_by(test_run_hook_started)
|
|
207
|
+
ensure_only_message_types!(test_run_hook_started, %i[test_run_hook_started], '#find_test_run_hook_finished_by')
|
|
208
|
+
|
|
209
|
+
repository.test_run_hook_finished_by_test_run_hook_started_id[test_run_hook_started.id]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def find_test_run_started
|
|
213
|
+
repository.test_run_started
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def find_test_run_finished
|
|
217
|
+
repository.test_run_finished
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# This method will be called with 1 of these 2 messages
|
|
221
|
+
# [TestStepStarted || TestStepFinished]
|
|
222
|
+
def find_test_step_by(message)
|
|
223
|
+
ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_step_by')
|
|
224
|
+
|
|
225
|
+
repository.test_step_by_id[message.test_step_id]
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# This method will be called with 1 of these 2 messages
|
|
229
|
+
# [TestCaseStarted || TestCaseFinished]
|
|
230
|
+
def find_test_steps_started_by(message)
|
|
231
|
+
ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_steps_started_by')
|
|
232
|
+
|
|
233
|
+
key = message.is_a?(Cucumber::Messages::TestCaseStarted) ? message.id : message.test_case_started_id
|
|
234
|
+
repository.test_steps_started_by_test_case_started_id.fetch(key, [])
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# This method will be called with 1 of these 2 messages
|
|
238
|
+
# [TestCaseStarted || TestCaseFinished]
|
|
239
|
+
def find_test_steps_finished_by(message)
|
|
240
|
+
ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_steps_finished_by')
|
|
241
|
+
|
|
242
|
+
if message.is_a?(Cucumber::Messages::TestCaseStarted)
|
|
243
|
+
repository.test_steps_finished_by_test_case_started_id.fetch(message.id, [])
|
|
244
|
+
else
|
|
245
|
+
test_case_started_message = find_test_case_started_by(message)
|
|
246
|
+
test_case_started_message.nil? ? [] : find_test_steps_finished_by(test_case_started_message)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
private
|
|
251
|
+
|
|
252
|
+
def ensure_only_message_types!(supplied_message, permissible_message_types, method_name)
|
|
253
|
+
raise ArgumentError, "Supplied argument is not a Cucumber Message. Argument: #{supplied_message.class}" unless supplied_message.is_a?(Cucumber::Messages::Message)
|
|
254
|
+
|
|
255
|
+
permitted_klasses = permissible_message_types.map { |message| message_types[message] }
|
|
256
|
+
raise ArgumentError, "Supplied message type '#{supplied_message.class}' is not permitted to be used when calling #{method_name}" unless permitted_klasses.include?(supplied_message.class)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def message_types
|
|
260
|
+
{
|
|
261
|
+
pickle_step: Cucumber::Messages::PickleStep,
|
|
262
|
+
test_case: Cucumber::Messages::TestCase,
|
|
263
|
+
test_case_started: Cucumber::Messages::TestCaseStarted,
|
|
264
|
+
test_case_finished: Cucumber::Messages::TestCaseFinished,
|
|
265
|
+
test_run_hook_started: Cucumber::Messages::TestRunHookStarted,
|
|
266
|
+
test_run_hook_finished: Cucumber::Messages::TestRunHookFinished,
|
|
267
|
+
test_step: Cucumber::Messages::TestStep,
|
|
268
|
+
test_step_started: Cucumber::Messages::TestStepStarted,
|
|
269
|
+
test_step_finished: Cucumber::Messages::TestStepFinished
|
|
270
|
+
}
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rake/dsl_definition'
|
|
4
|
+
|
|
5
|
+
module Cucumber
|
|
6
|
+
module Rake
|
|
7
|
+
class ForkedCucumberRunner # :nodoc:
|
|
8
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
|
9
|
+
|
|
10
|
+
def initialize(libs, cucumber_bin, cucumber_opts, bundler, feature_files)
|
|
11
|
+
@libs = libs
|
|
12
|
+
@cucumber_bin = cucumber_bin
|
|
13
|
+
@cucumber_opts = cucumber_opts
|
|
14
|
+
@bundler = bundler
|
|
15
|
+
@feature_files = feature_files
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load_path
|
|
19
|
+
[format('"%<path>s"', path: @libs.join(File::PATH_SEPARATOR))]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def quoted_binary(cucumber_bin)
|
|
23
|
+
[format('"%<path>s"', path: cucumber_bin)]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def use_bundler
|
|
27
|
+
@bundler.nil? ? File.exist?('./Gemfile') && bundler_gem_available? : @bundler
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def bundler_gem_available?
|
|
31
|
+
Gem::Specification.find_by_name('bundler')
|
|
32
|
+
rescue Gem::LoadError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def cmd
|
|
37
|
+
if use_bundler
|
|
38
|
+
[
|
|
39
|
+
Cucumber::RUBY_BINARY, '-S', 'bundle', 'exec', 'cucumber',
|
|
40
|
+
@cucumber_opts, @feature_files
|
|
41
|
+
].flatten
|
|
42
|
+
else
|
|
43
|
+
[
|
|
44
|
+
Cucumber::RUBY_BINARY, '-I', load_path,
|
|
45
|
+
quoted_binary(@cucumber_bin), @cucumber_opts, @feature_files
|
|
46
|
+
].flatten
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def run
|
|
51
|
+
sh cmd.join(' ') do |ok, res|
|
|
52
|
+
exit res.exitstatus unless ok
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rake/dsl_definition'
|
|
4
|
+
|
|
5
|
+
require_relative '../cli/main'
|
|
6
|
+
|
|
7
|
+
module Cucumber
|
|
8
|
+
module Rake
|
|
9
|
+
class InProcessCucumberRunner
|
|
10
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
|
11
|
+
|
|
12
|
+
attr_reader :args
|
|
13
|
+
|
|
14
|
+
def initialize(libs, cucumber_opts, feature_files)
|
|
15
|
+
raise 'libs must be an Array when running in-process' unless libs.instance_of? Array
|
|
16
|
+
|
|
17
|
+
libs.reverse_each { |lib| $LOAD_PATH.unshift(lib) }
|
|
18
|
+
@args = (cucumber_opts + feature_files).flatten.compact
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
failure = Cucumber::Cli::Main.execute(args)
|
|
23
|
+
raise 'Cucumber failed' if failure
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/cucumber/rake/task.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'cucumber/platform'
|
|
4
|
-
require 'cucumber/gherkin/formatter/ansi_escapes'
|
|
5
3
|
require 'rake/dsl_definition'
|
|
6
4
|
|
|
5
|
+
require_relative '../gherkin/formatter/ansi_escapes'
|
|
6
|
+
require_relative '../platform'
|
|
7
|
+
require_relative 'forked_cucumber_runner'
|
|
8
|
+
require_relative 'in_process_cucumber_runner'
|
|
9
|
+
|
|
7
10
|
module Cucumber
|
|
8
11
|
module Rake
|
|
9
12
|
# Defines a Rake task for running features.
|
|
@@ -26,120 +29,35 @@ module Cucumber
|
|
|
26
29
|
include Cucumber::Gherkin::Formatter::AnsiEscapes
|
|
27
30
|
include ::Rake::DSL if defined?(::Rake::DSL)
|
|
28
31
|
|
|
29
|
-
class InProcessCucumberRunner # :nodoc:
|
|
30
|
-
include ::Rake::DSL if defined?(::Rake::DSL)
|
|
31
|
-
|
|
32
|
-
attr_reader :args
|
|
33
|
-
|
|
34
|
-
def initialize(libs, cucumber_opts, feature_files)
|
|
35
|
-
raise 'libs must be an Array when running in-process' unless libs.instance_of? Array
|
|
36
|
-
|
|
37
|
-
libs.reverse_each { |lib| $LOAD_PATH.unshift(lib) }
|
|
38
|
-
@args = (
|
|
39
|
-
cucumber_opts +
|
|
40
|
-
feature_files
|
|
41
|
-
).flatten.compact
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def run
|
|
45
|
-
require 'cucumber/cli/main'
|
|
46
|
-
failure = Cucumber::Cli::Main.execute(args)
|
|
47
|
-
raise 'Cucumber failed' if failure
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
class ForkedCucumberRunner # :nodoc:
|
|
52
|
-
include ::Rake::DSL if defined?(::Rake::DSL)
|
|
53
|
-
|
|
54
|
-
def initialize(libs, cucumber_bin, cucumber_opts, bundler, feature_files)
|
|
55
|
-
@libs = libs
|
|
56
|
-
@cucumber_bin = cucumber_bin
|
|
57
|
-
@cucumber_opts = cucumber_opts
|
|
58
|
-
@bundler = bundler
|
|
59
|
-
@feature_files = feature_files
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def load_path
|
|
63
|
-
[format('"%<path>s"', path: @libs.join(File::PATH_SEPARATOR))]
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def quoted_binary(cucumber_bin)
|
|
67
|
-
[format('"%<path>s"', path: cucumber_bin)]
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def use_bundler
|
|
71
|
-
@bundler.nil? ? File.exist?('./Gemfile') && bundler_gem_available? : @bundler
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def bundler_gem_available?
|
|
75
|
-
Gem::Specification.find_by_name('bundler')
|
|
76
|
-
rescue Gem::LoadError
|
|
77
|
-
false
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def cmd
|
|
81
|
-
if use_bundler
|
|
82
|
-
[
|
|
83
|
-
Cucumber::RUBY_BINARY, '-S', 'bundle', 'exec', 'cucumber',
|
|
84
|
-
@cucumber_opts, @feature_files
|
|
85
|
-
].flatten
|
|
86
|
-
else
|
|
87
|
-
[
|
|
88
|
-
Cucumber::RUBY_BINARY, '-I', load_path,
|
|
89
|
-
quoted_binary(@cucumber_bin), @cucumber_opts, @feature_files
|
|
90
|
-
].flatten
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def run
|
|
95
|
-
sh cmd.join(' ') do |ok, res|
|
|
96
|
-
exit res.exitstatus unless ok
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Directories to add to the Ruby $LOAD_PATH
|
|
102
|
-
attr_accessor :libs
|
|
103
|
-
|
|
104
32
|
# Name of the cucumber binary to use for running features. Defaults to Cucumber::BINARY
|
|
105
33
|
attr_accessor :binary
|
|
106
34
|
|
|
35
|
+
# Whether or not to run with bundler (bundle exec). Setting this to false may speed
|
|
36
|
+
# up the execution. The default value is true if Bundler is installed and you have
|
|
37
|
+
# a Gemfile, false otherwise.
|
|
38
|
+
#
|
|
39
|
+
# Note that this attribute has no effect if you don't run in forked mode.
|
|
40
|
+
attr_accessor :bundler
|
|
41
|
+
|
|
107
42
|
# Extra options to pass to the cucumber binary. Can be overridden by the CUCUMBER_OPTS environment variable.
|
|
108
43
|
# It's recommended to pass an Array, but if it's a String it will be #split by ' '.
|
|
109
44
|
attr_reader :cucumber_opts
|
|
110
45
|
|
|
111
|
-
def cucumber_opts=(opts) # :nodoc:
|
|
112
|
-
unless opts.instance_of? String
|
|
113
|
-
@cucumber_opts = opts
|
|
114
|
-
return
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
@cucumber_opts = opts.split(' ')
|
|
118
|
-
return if @cucumber_opts.length <= 1
|
|
119
|
-
|
|
120
|
-
$stderr.puts 'WARNING: consider using an array rather than a space-delimited string with cucumber_opts to avoid undesired behavior.'
|
|
121
|
-
end
|
|
122
|
-
|
|
123
46
|
# Whether or not to fork a new ruby interpreter. Defaults to true. You may gain
|
|
124
47
|
# some startup speed if you set it to false, but this may also cause issues with
|
|
125
48
|
# your load path and gems.
|
|
126
49
|
attr_accessor :fork
|
|
127
50
|
|
|
51
|
+
# Directories to add to the Ruby $LOAD_PATH
|
|
52
|
+
attr_accessor :libs
|
|
53
|
+
|
|
128
54
|
# Define what profile to be used. When used with cucumber_opts it is simply appended
|
|
129
55
|
# to it. Will be ignored when CUCUMBER_OPTS is used.
|
|
130
56
|
attr_accessor :profile
|
|
131
57
|
|
|
132
|
-
# Whether or not to run with bundler (bundle exec). Setting this to false may speed
|
|
133
|
-
# up the execution. The default value is true if Bundler is installed and you have
|
|
134
|
-
# a Gemfile, false otherwise.
|
|
135
|
-
#
|
|
136
|
-
# Note that this attribute has no effect if you don't run in forked mode.
|
|
137
|
-
attr_accessor :bundler
|
|
138
|
-
|
|
139
58
|
# Name of the running task
|
|
140
59
|
attr_reader :task_name
|
|
141
60
|
|
|
142
|
-
# Define Cucumber Rake task
|
|
143
61
|
def initialize(task_name = 'cucumber', desc = 'Run Cucumber features')
|
|
144
62
|
@task_name = task_name
|
|
145
63
|
@desc = desc
|
|
@@ -151,24 +69,29 @@ module Cucumber
|
|
|
151
69
|
define_task
|
|
152
70
|
end
|
|
153
71
|
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
72
|
+
def cucumber_opts=(opts) # :nodoc:
|
|
73
|
+
unless opts.instance_of? String
|
|
74
|
+
@cucumber_opts = opts
|
|
75
|
+
return
|
|
158
76
|
end
|
|
159
|
-
end
|
|
160
77
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return ForkedCucumberRunner.new(libs, binary, cucumber_opts, bundler, feature_files) if fork
|
|
78
|
+
@cucumber_opts = opts.split(' ')
|
|
79
|
+
return if @cucumber_opts.length <= 1
|
|
164
80
|
|
|
165
|
-
|
|
81
|
+
$stderr.puts 'WARNING: consider using an array rather than a space-delimited string with cucumber_opts to avoid undesired behavior.'
|
|
166
82
|
end
|
|
167
83
|
|
|
168
84
|
def cucumber_opts_with_profile # :nodoc:
|
|
169
85
|
Array(cucumber_opts).concat(Array(@profile).flat_map { |p| ['--profile', p] })
|
|
170
86
|
end
|
|
171
87
|
|
|
88
|
+
def define_task # :nodoc:
|
|
89
|
+
desc @desc
|
|
90
|
+
task @task_name do
|
|
91
|
+
runner.run
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
172
95
|
def feature_files # :nodoc:
|
|
173
96
|
make_command_line_safe(FileList[ENV['FEATURE'] || []])
|
|
174
97
|
end
|
|
@@ -176,6 +99,13 @@ module Cucumber
|
|
|
176
99
|
def make_command_line_safe(list)
|
|
177
100
|
list.map { |string| string.gsub(' ', '\ ') }
|
|
178
101
|
end
|
|
102
|
+
|
|
103
|
+
def runner(_task_args = nil) # :nodoc:
|
|
104
|
+
cucumber_opts = [ENV['CUCUMBER_OPTS']&.split(/\s+/) || cucumber_opts_with_profile]
|
|
105
|
+
return ForkedCucumberRunner.new(libs, binary, cucumber_opts, bundler, feature_files) if fork
|
|
106
|
+
|
|
107
|
+
InProcessCucumberRunner.new(libs, cucumber_opts, feature_files)
|
|
108
|
+
end
|
|
179
109
|
end
|
|
180
110
|
end
|
|
181
111
|
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cucumber
|
|
4
|
+
# In memory repository i.e. a thread based link to cucumber-query
|
|
5
|
+
class Repository
|
|
6
|
+
attr_accessor :meta, :test_run_started, :test_run_finished
|
|
7
|
+
attr_reader :attachments_by_test_case_started_id, :attachments_by_test_run_hook_started_id,
|
|
8
|
+
:hook_by_id,
|
|
9
|
+
:pickle_by_id, :pickle_step_by_id,
|
|
10
|
+
:step_by_id, :step_definition_by_id,
|
|
11
|
+
:test_case_by_id, :test_case_started_by_id, :test_case_finished_by_test_case_started_id,
|
|
12
|
+
:test_run_hook_started_by_id, :test_run_hook_finished_by_test_run_hook_started_id,
|
|
13
|
+
:test_step_by_id, :test_steps_started_by_test_case_started_id, :test_steps_finished_by_test_case_started_id
|
|
14
|
+
|
|
15
|
+
# TODO: Missing structs (2)
|
|
16
|
+
# final Map<String, List<Suggestion>> suggestionsByPickleStepId = new LinkedHashMap<>();
|
|
17
|
+
# final List<UndefinedParameterType> undefinedParameterTypes = new ArrayList<>();
|
|
18
|
+
|
|
19
|
+
# TODO: Missing handlers
|
|
20
|
+
# Source
|
|
21
|
+
#
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
@attachments_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] }
|
|
25
|
+
@attachments_by_test_run_hook_started_id = Hash.new { |hash, key| hash[key] = [] }
|
|
26
|
+
@hook_by_id = {}
|
|
27
|
+
@pickle_by_id = {}
|
|
28
|
+
@pickle_step_by_id = {}
|
|
29
|
+
@step_by_id = {}
|
|
30
|
+
@step_definition_by_id = {}
|
|
31
|
+
@test_case_by_id = {}
|
|
32
|
+
@test_case_started_by_id = {}
|
|
33
|
+
@test_case_finished_by_test_case_started_id = {}
|
|
34
|
+
@test_run_hook_started_by_id = {}
|
|
35
|
+
@test_run_hook_finished_by_test_run_hook_started_id = {}
|
|
36
|
+
@test_step_by_id = {}
|
|
37
|
+
@test_steps_started_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] }
|
|
38
|
+
@test_steps_finished_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update(envelope)
|
|
42
|
+
return self.meta = envelope.meta if envelope.meta
|
|
43
|
+
return self.test_run_started = envelope.test_run_started if envelope.test_run_started
|
|
44
|
+
return self.test_run_finished = envelope.test_run_finished if envelope.test_run_finished
|
|
45
|
+
return update_attachment(envelope.attachment) if envelope.attachment
|
|
46
|
+
return update_gherkin_document(envelope.gherkin_document) if envelope.gherkin_document
|
|
47
|
+
return update_hook(envelope.hook) if envelope.hook
|
|
48
|
+
return update_pickle(envelope.pickle) if envelope.pickle
|
|
49
|
+
return update_step_definition(envelope.step_definition) if envelope.step_definition
|
|
50
|
+
return update_test_run_hook_started(envelope.test_run_hook_started) if envelope.test_run_hook_started
|
|
51
|
+
return update_test_run_hook_finished(envelope.test_run_hook_finished) if envelope.test_run_hook_finished
|
|
52
|
+
return update_test_case_started(envelope.test_case_started) if envelope.test_case_started
|
|
53
|
+
return update_test_case_finished(envelope.test_case_finished) if envelope.test_case_finished
|
|
54
|
+
return update_test_step_started(envelope.test_step_started) if envelope.test_step_started
|
|
55
|
+
return update_test_step_finished(envelope.test_step_finished) if envelope.test_step_finished
|
|
56
|
+
return update_test_case(envelope.test_case) if envelope.test_case
|
|
57
|
+
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def update_attachment(attachment)
|
|
64
|
+
attachments_by_test_case_started_id[attachment.test_case_started_id] << attachment if attachment.test_case_started_id
|
|
65
|
+
attachments_by_test_run_hook_started_id[attachment.test_run_hook_started_id] << attachment if attachment.test_run_hook_started_id
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def update_feature(feature)
|
|
69
|
+
feature.children.each do |feature_child|
|
|
70
|
+
update_steps(feature_child.background.steps) if feature_child.background
|
|
71
|
+
update_scenario(feature_child.scenario) if feature_child.scenario
|
|
72
|
+
next unless feature_child.rule
|
|
73
|
+
|
|
74
|
+
feature_child.rule.children.each do |rule_child|
|
|
75
|
+
update_steps(rule_child.background.steps) if rule_child.background
|
|
76
|
+
update_scenario(rule_child.scenario) if rule_child.scenario
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def update_gherkin_document(gherkin_document)
|
|
82
|
+
# TODO: Update lineage at a later date. Java Impl -> lineageById.putAll(Lineages.of(document));
|
|
83
|
+
update_feature(gherkin_document.feature) if gherkin_document.feature
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def update_hook(hook)
|
|
87
|
+
hook_by_id[hook.id] = hook
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def update_pickle(pickle)
|
|
91
|
+
pickle_by_id[pickle.id] = pickle
|
|
92
|
+
pickle.steps.each { |pickle_step| pickle_step_by_id[pickle_step.id] = pickle_step }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def update_scenario(scenario)
|
|
96
|
+
update_steps(scenario.steps)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def update_steps(steps)
|
|
100
|
+
steps.each { |step| step_by_id[step.id] = step }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def update_step_definition(step_definition)
|
|
104
|
+
step_definition_by_id[step_definition.id] = step_definition
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def update_test_case(test_case)
|
|
108
|
+
test_case_by_id[test_case.id] = test_case
|
|
109
|
+
test_case.test_steps.each { |test_step| test_step_by_id[test_step.id] = test_step }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def update_test_case_started(test_case_started)
|
|
113
|
+
test_case_started_by_id[test_case_started.id] = test_case_started
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def update_test_case_finished(test_case_finished)
|
|
117
|
+
test_case_finished_by_test_case_started_id[test_case_finished.test_case_started_id] = test_case_finished
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def update_test_run_hook_started(test_run_hook_started)
|
|
121
|
+
test_run_hook_started_by_id[test_run_hook_started.id] = test_run_hook_started
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def update_test_run_hook_finished(test_run_hook_finished)
|
|
125
|
+
test_run_hook_finished_by_test_run_hook_started_id[test_run_hook_finished.test_run_hook_started_id] = test_run_hook_finished
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def update_test_step_started(test_step_started)
|
|
129
|
+
test_steps_started_by_test_case_started_id[test_step_started.test_case_started_id] << test_step_started
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def update_test_step_finished(test_step_finished)
|
|
133
|
+
test_steps_finished_by_test_case_started_id[test_step_finished.test_case_started_id] << test_step_finished
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|