cucumber 2.0.0 → 2.0.1
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/CONTRIBUTING.md +7 -9
- data/History.md +295 -265
- data/README.md +9 -7
- data/cucumber.gemspec +2 -2
- data/features/docs/cli/dry_run.feature +0 -3
- data/features/docs/cli/finding_steps.feature +28 -0
- data/features/docs/cli/run_specific_scenarios.feature +3 -1
- data/features/docs/cli/specifying_multiple_formatters.feature +22 -1
- data/features/docs/defining_steps/nested_steps.feature +0 -1
- data/features/docs/defining_steps/printing_messages.feature +4 -4
- data/features/docs/defining_steps/skip_scenario.feature +0 -2
- data/features/docs/exception_in_around_hook.feature +1 -3
- data/features/docs/formatters/html_formatter.feature +1 -0
- data/features/docs/formatters/json_formatter.feature +73 -62
- data/features/docs/formatters/junit_formatter.feature +130 -38
- data/features/docs/formatters/rerun_formatter.feature +60 -8
- data/features/docs/formatters/usage_formatter.feature +3 -7
- data/features/docs/getting_started.feature +1 -1
- data/features/docs/gherkin/background.feature +0 -11
- data/features/docs/gherkin/language_help.feature +5 -0
- data/features/docs/gherkin/outlines.feature +1 -3
- data/features/docs/gherkin/using_descriptions.feature +0 -1
- data/features/docs/raketask.feature +1 -1
- data/features/docs/writing_support_code/after_hooks.feature +22 -0
- data/features/lib/step_definitions/aruba_steps.rb +4 -0
- data/features/lib/step_definitions/junit_steps.rb +1 -1
- data/features/lib/support/normalise_output.rb +21 -4
- data/lib/cucumber/cli/configuration.rb +16 -13
- data/lib/cucumber/cli/main.rb +35 -10
- data/lib/cucumber/cli/options.rb +33 -9
- data/lib/cucumber/cli/rerun_file.rb +29 -0
- data/lib/cucumber/filters/prepare_world.rb +2 -3
- data/lib/cucumber/formatter/backtrace_filter.rb +40 -0
- data/lib/cucumber/formatter/console.rb +2 -3
- data/lib/cucumber/formatter/cucumber.css +1 -0
- data/lib/cucumber/formatter/duration_extractor.rb +28 -0
- data/lib/cucumber/formatter/hook_query_visitor.rb +40 -0
- data/lib/cucumber/formatter/html.rb +16 -1
- data/lib/cucumber/formatter/json.rb +287 -8
- data/lib/cucumber/formatter/junit.rb +92 -143
- data/lib/cucumber/formatter/legacy_api/adapter.rb +18 -54
- data/lib/cucumber/formatter/legacy_api/ast.rb +13 -0
- data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +4 -0
- data/lib/cucumber/formatter/pretty.rb +2 -1
- data/lib/cucumber/formatter/progress.rb +20 -53
- data/lib/cucumber/formatter/rerun.rb +2 -1
- data/lib/cucumber/formatter/usage.rb +16 -22
- data/lib/cucumber/hooks.rb +18 -9
- data/lib/cucumber/multiline_argument/data_table.rb +40 -28
- data/lib/cucumber/platform.rb +1 -1
- data/lib/cucumber/rb_support/rb_hook.rb +4 -0
- data/lib/cucumber/running_test_case.rb +13 -4
- data/lib/cucumber/runtime/after_hooks.rb +7 -6
- data/lib/cucumber/runtime/before_hooks.rb +8 -4
- data/lib/cucumber/runtime/step_hooks.rb +5 -4
- data/lib/cucumber/runtime/support_code.rb +6 -15
- data/lib/cucumber/step_match.rb +1 -1
- data/spec/cucumber/cli/configuration_spec.rb +32 -5
- data/spec/cucumber/cli/main_spec.rb +3 -3
- data/spec/cucumber/cli/options_spec.rb +60 -1
- data/spec/cucumber/cli/rerun_spec.rb +89 -0
- data/spec/cucumber/formatter/html_spec.rb +84 -5
- data/spec/cucumber/formatter/json_spec.rb +757 -0
- data/spec/cucumber/formatter/junit_spec.rb +5 -5
- data/spec/cucumber/formatter/legacy_api/adapter_spec.rb +69 -8
- data/spec/cucumber/formatter/pretty_spec.rb +96 -0
- data/spec/cucumber/formatter/progress_spec.rb +85 -1
- data/spec/cucumber/formatter/rerun_spec.rb +3 -3
- data/spec/cucumber/multiline_argument/data_table_spec.rb +89 -0
- data/spec/cucumber/running_test_case_spec.rb +57 -1
- metadata +70 -60
- data/lib/cucumber/formatter/gherkin_formatter_adapter.rb +0 -204
- data/lib/cucumber/formatter/gpretty.rb +0 -24
data/lib/cucumber/cli/main.rb
CHANGED
@@ -36,27 +36,34 @@ module Cucumber
|
|
36
36
|
end
|
37
37
|
|
38
38
|
runtime.run!
|
39
|
-
|
40
|
-
|
39
|
+
if Cucumber.wants_to_quit
|
40
|
+
exit_unable_to_finish
|
41
|
+
else
|
42
|
+
if runtime.failure?
|
43
|
+
exit_tests_failed
|
44
|
+
else
|
45
|
+
exit_ok
|
46
|
+
end
|
47
|
+
end
|
48
|
+
rescue SystemExit => e
|
49
|
+
@kernel.exit(e.status)
|
41
50
|
rescue FileNotFoundException => e
|
42
51
|
@err.puts(e.message)
|
43
52
|
@err.puts("Couldn't open #{e.path}")
|
44
|
-
|
53
|
+
exit_unable_to_finish
|
45
54
|
rescue FeatureFolderNotFoundException => e
|
46
55
|
@err.puts(e.message + ". You can use `cucumber --init` to get started.")
|
47
|
-
|
56
|
+
exit_unable_to_finish
|
48
57
|
rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
|
49
58
|
@err.puts(e.message)
|
50
|
-
|
51
|
-
rescue SystemExit => e
|
52
|
-
@kernel.exit(e.status)
|
59
|
+
exit_unable_to_finish
|
53
60
|
rescue Errno::EACCES, Errno::ENOENT => e
|
54
61
|
@err.puts("#{e.message} (#{e.class})")
|
55
|
-
|
62
|
+
exit_unable_to_finish
|
56
63
|
rescue Exception => e
|
57
64
|
@err.puts("#{e.message} (#{e.class})")
|
58
65
|
@err.puts(e.backtrace.join("\n"))
|
59
|
-
|
66
|
+
exit_unable_to_finish
|
60
67
|
end
|
61
68
|
|
62
69
|
def configuration
|
@@ -68,9 +75,27 @@ module Cucumber
|
|
68
75
|
|
69
76
|
private
|
70
77
|
|
78
|
+
|
79
|
+
def exit_ok
|
80
|
+
@kernel.exit 0
|
81
|
+
end
|
82
|
+
|
83
|
+
def exit_tests_failed
|
84
|
+
@kernel.exit 1
|
85
|
+
end
|
86
|
+
|
87
|
+
def exit_unable_to_finish
|
88
|
+
@kernel.exit 2
|
89
|
+
end
|
90
|
+
|
91
|
+
# stops the program immediately, without running at_exit blocks
|
92
|
+
def exit_unable_to_finish!
|
93
|
+
@kernel.exit! 2
|
94
|
+
end
|
95
|
+
|
71
96
|
def trap_interrupt
|
72
97
|
trap('INT') do
|
73
|
-
|
98
|
+
exit_unable_to_finish! if Cucumber.wants_to_quit
|
74
99
|
Cucumber.wants_to_quit = true
|
75
100
|
STDERR.puts "\nExiting... Interrupt again to exit immediately."
|
76
101
|
end
|
data/lib/cucumber/cli/options.rb
CHANGED
@@ -65,7 +65,7 @@ module Cucumber
|
|
65
65
|
@profile_loader = options[:profile_loader]
|
66
66
|
@options[:skip_profile_information] = options[:skip_profile_information]
|
67
67
|
|
68
|
-
@
|
68
|
+
@disable_profile_loading = nil
|
69
69
|
end
|
70
70
|
|
71
71
|
def [](key)
|
@@ -116,8 +116,12 @@ module Cucumber
|
|
116
116
|
opts.on("--i18n LANG",
|
117
117
|
"List keywords for in a particular language",
|
118
118
|
%{Run with "--i18n help" to see all languages}) do |lang|
|
119
|
+
require 'gherkin/i18n'
|
120
|
+
|
119
121
|
if lang == 'help'
|
120
122
|
list_languages_and_exit
|
123
|
+
elsif !Gherkin::I18n::LANGUAGES.keys.include? lang
|
124
|
+
indicate_invalid_language_and_exit(lang)
|
121
125
|
else
|
122
126
|
list_keywords_and_exit(lang)
|
123
127
|
end
|
@@ -188,6 +192,7 @@ module Cucumber
|
|
188
192
|
opts.on("-d", "--dry-run", "Invokes formatters without executing the steps.",
|
189
193
|
"This also omits the loading of your support/env.rb file if it exists.") do
|
190
194
|
@options[:dry_run] = true
|
195
|
+
@options[:duration] = false
|
191
196
|
end
|
192
197
|
opts.on("-m", "--no-multiline",
|
193
198
|
"Don't print multiline strings and tables under steps.") do
|
@@ -207,7 +212,12 @@ module Cucumber
|
|
207
212
|
end
|
208
213
|
|
209
214
|
opts.on("-q", "--quiet", "Alias for --no-snippets --no-source.") do
|
210
|
-
@
|
215
|
+
@options[:snippets] = false
|
216
|
+
@options[:source] = false
|
217
|
+
@options[:duration] = false
|
218
|
+
end
|
219
|
+
opts.on("--no-duration", "Don't print the duration at the end of the summary") do
|
220
|
+
@options[:duration] = false
|
211
221
|
end
|
212
222
|
opts.on("-b", "--backtrace", "Show full backtrace for all errors.") do
|
213
223
|
Cucumber.use_full_backtrace = true
|
@@ -252,17 +262,13 @@ TEXT
|
|
252
262
|
end
|
253
263
|
end.parse!
|
254
264
|
|
255
|
-
if @quiet
|
256
|
-
@options[:snippets] = @options[:source] = false
|
257
|
-
else
|
258
|
-
@options[:snippets] = true if @options[:snippets].nil?
|
259
|
-
@options[:source] = true if @options[:source].nil?
|
260
|
-
end
|
261
265
|
@args.map! { |a| "#{a}:#{@options[:lines]}" } if @options[:lines]
|
262
266
|
|
263
267
|
extract_environment_variables
|
264
268
|
@options[:paths] = @args.dup #whatver is left over
|
265
269
|
|
270
|
+
check_formatter_stream_conflicts()
|
271
|
+
|
266
272
|
merge_profiles
|
267
273
|
|
268
274
|
self
|
@@ -276,6 +282,13 @@ TEXT
|
|
276
282
|
@options[:filters] ||= []
|
277
283
|
end
|
278
284
|
|
285
|
+
def check_formatter_stream_conflicts()
|
286
|
+
streams = @options[:formats].uniq.map { |(_, stream)| stream }
|
287
|
+
if streams != streams.uniq
|
288
|
+
raise "All but one formatter must use --out, only one can print to each stream (or STDOUT)"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
279
292
|
protected
|
280
293
|
|
281
294
|
attr_reader :options, :profiles, :expanded_args
|
@@ -353,6 +366,7 @@ TEXT
|
|
353
366
|
end
|
354
367
|
@options[:source] &= other_options[:source]
|
355
368
|
@options[:snippets] &= other_options[:snippets]
|
369
|
+
@options[:duration] &= other_options[:duration]
|
356
370
|
@options[:strict] |= other_options[:strict]
|
357
371
|
@options[:dry_run] |= other_options[:dry_run]
|
358
372
|
|
@@ -369,6 +383,13 @@ TEXT
|
|
369
383
|
self
|
370
384
|
end
|
371
385
|
|
386
|
+
def indicate_invalid_language_and_exit(lang)
|
387
|
+
require 'gherkin/i18n'
|
388
|
+
@out_stream.write("Invalid language '#{lang}'. Available languages are:\n")
|
389
|
+
@out_stream.write(Gherkin::I18n.language_table)
|
390
|
+
Kernel.exit(0)
|
391
|
+
end
|
392
|
+
|
372
393
|
def list_keywords_and_exit(lang)
|
373
394
|
require 'gherkin/i18n'
|
374
395
|
@out_stream.write(Gherkin::I18n.get(lang).keyword_table)
|
@@ -391,7 +412,10 @@ TEXT
|
|
391
412
|
:tag_expressions => [],
|
392
413
|
:name_regexps => [],
|
393
414
|
:env_vars => {},
|
394
|
-
:diff_enabled => true
|
415
|
+
:diff_enabled => true,
|
416
|
+
:snippets => true,
|
417
|
+
:source => true,
|
418
|
+
:duration => true
|
395
419
|
}
|
396
420
|
end
|
397
421
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Cucumber
|
2
|
+
module Cli
|
3
|
+
class RerunFile
|
4
|
+
attr_reader :path
|
5
|
+
|
6
|
+
def self.can_read?(path)
|
7
|
+
path[0] == '@' && File.file?(real_path(path))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.real_path(path)
|
11
|
+
path[1..-1] # remove leading @
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(path)
|
15
|
+
@path = self.class.real_path(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def features
|
19
|
+
lines.map { |l| l.scan(/(?:^| )(.*?\.feature(?:(?::\d+)*))/) }.flatten
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def lines
|
25
|
+
IO.read(@path).split("\n")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -24,9 +24,8 @@ module Cucumber
|
|
24
24
|
end
|
25
25
|
around_hooks = [init_scenario] + @original_test_case.around_hooks
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
end
|
27
|
+
empty_hook = proc {} #no op - legacy format adapter expects a before hooks
|
28
|
+
default_hook = Cucumber::Hooks.before_hook(@original_test_case.source, Cucumber::Hooks.location(empty_hook), &empty_hook)
|
30
29
|
steps = [default_hook] + @original_test_case.test_steps
|
31
30
|
|
32
31
|
@original_test_case.with_around_hooks(around_hooks).with_steps(steps)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'cucumber/platform'
|
2
|
+
|
3
|
+
|
4
|
+
module Cucumber
|
5
|
+
module Formatter
|
6
|
+
|
7
|
+
class BacktraceFilter
|
8
|
+
BACKTRACE_FILTER_PATTERNS = \
|
9
|
+
[/vendor\/rails|lib\/cucumber|bin\/cucumber:|lib\/rspec|gems\/|minitest|test\/unit|.gem\/ruby|lib\/ruby/]
|
10
|
+
if(::Cucumber::JRUBY)
|
11
|
+
BACKTRACE_FILTER_PATTERNS << /org\/jruby/
|
12
|
+
end
|
13
|
+
PWD_PATTERN = /#{::Regexp.escape(::Dir.pwd)}\//m
|
14
|
+
|
15
|
+
def initialize(exception)
|
16
|
+
@exception = exception
|
17
|
+
end
|
18
|
+
|
19
|
+
def exception
|
20
|
+
return @exception if ::Cucumber.use_full_backtrace
|
21
|
+
@exception.backtrace.each{|line| line.gsub!(PWD_PATTERN, "./")}
|
22
|
+
|
23
|
+
filtered = (@exception.backtrace || []).reject do |line|
|
24
|
+
BACKTRACE_FILTER_PATTERNS.detect { |p| line =~ p }
|
25
|
+
end
|
26
|
+
|
27
|
+
if ::ENV['CUCUMBER_TRUNCATE_OUTPUT']
|
28
|
+
# Strip off file locations
|
29
|
+
filtered = filtered.map do |line|
|
30
|
+
line =~ /(.*):in `/ ? $1 : line
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@exception.set_backtrace(filtered)
|
35
|
+
@exception
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -76,7 +76,7 @@ module Cucumber
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
-
def print_stats(
|
79
|
+
def print_stats(duration, options)
|
80
80
|
failures = collect_failing_scenarios(runtime)
|
81
81
|
if !failures.empty?
|
82
82
|
print_failing_scenarios(failures, options.custom_profiles, options[:source])
|
@@ -84,8 +84,7 @@ module Cucumber
|
|
84
84
|
|
85
85
|
@io.puts scenario_summary(runtime) {|status_count, status| format_string(status_count, status)}
|
86
86
|
@io.puts step_summary(runtime) {|status_count, status| format_string(status_count, status)}
|
87
|
-
|
88
|
-
@io.puts(format_duration(features.duration)) if features && features.duration
|
87
|
+
@io.puts(format_duration(duration)) if duration && options[:duration]
|
89
88
|
|
90
89
|
if runtime.configuration.randomize?
|
91
90
|
@io.puts
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Cucumber
|
2
|
+
module Formatter
|
3
|
+
|
4
|
+
class DurationExtractor
|
5
|
+
attr_reader :result_duration
|
6
|
+
def initialize(result)
|
7
|
+
@result_duration = 0
|
8
|
+
result.describe_to(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def passed(*) end
|
12
|
+
|
13
|
+
def failed(*) end
|
14
|
+
|
15
|
+
def undefined(*) end
|
16
|
+
|
17
|
+
def skipped(*) end
|
18
|
+
|
19
|
+
def pending(*) end
|
20
|
+
|
21
|
+
def exception(*) end
|
22
|
+
|
23
|
+
def duration(duration, *)
|
24
|
+
duration.tap { |duration| @result_duration = duration.nanoseconds / 10**9.0 }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Cucumber
|
2
|
+
module Formatter
|
3
|
+
class HookQueryVisitor
|
4
|
+
attr_reader :type
|
5
|
+
|
6
|
+
def initialize(test_step)
|
7
|
+
@hook = false
|
8
|
+
@type = :no_hook
|
9
|
+
test_step.source.last.describe_to(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def hook?
|
13
|
+
@hook
|
14
|
+
end
|
15
|
+
|
16
|
+
def step(*)
|
17
|
+
end
|
18
|
+
|
19
|
+
def before_hook(*)
|
20
|
+
@hook = true
|
21
|
+
@type = :before
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_hook(*)
|
25
|
+
@hook = true
|
26
|
+
@type = :after
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_step_hook(*)
|
30
|
+
@hook = true
|
31
|
+
@type = :after_step
|
32
|
+
end
|
33
|
+
|
34
|
+
def around_hook(*)
|
35
|
+
@hook = true
|
36
|
+
@type = :around
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -206,7 +206,13 @@ module Cucumber
|
|
206
206
|
@builder << file_colon_line
|
207
207
|
end
|
208
208
|
@listing_background = false
|
209
|
-
|
209
|
+
scenario_id = "scenario_#{@scenario_number}"
|
210
|
+
if @inside_outline
|
211
|
+
@outline_row += 1
|
212
|
+
scenario_id += "_#{@outline_row}"
|
213
|
+
@scenario_red = false
|
214
|
+
end
|
215
|
+
@builder.h3(:id => scenario_id) do
|
210
216
|
@builder.span(keyword + ':', :class => 'keyword')
|
211
217
|
@builder.text!(' ')
|
212
218
|
@builder.span(name, :class => 'val')
|
@@ -414,6 +420,12 @@ module Cucumber
|
|
414
420
|
@delayed_messages = []
|
415
421
|
end
|
416
422
|
|
423
|
+
def after_test_case(test_case, result)
|
424
|
+
if result.failed? and not @scenario_red
|
425
|
+
set_scenario_color_failed
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
417
429
|
protected
|
418
430
|
|
419
431
|
def build_exception_detail(exception)
|
@@ -462,6 +474,9 @@ module Cucumber
|
|
462
474
|
scenario_or_background = @in_background ? "background" : "scenario"
|
463
475
|
@builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
|
464
476
|
@scenario_red = true
|
477
|
+
if @options[:expand] and @inside_outline
|
478
|
+
@builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}_#{@outline_row}');")
|
479
|
+
end
|
465
480
|
end
|
466
481
|
end
|
467
482
|
|
@@ -1,19 +1,298 @@
|
|
1
|
-
require '
|
1
|
+
require 'multi_json'
|
2
|
+
require 'base64'
|
2
3
|
require 'cucumber/formatter/io'
|
3
|
-
require '
|
4
|
-
require 'gherkin/formatter/json_formatter'
|
4
|
+
require 'cucumber/formatter/hook_query_visitor'
|
5
5
|
|
6
6
|
module Cucumber
|
7
7
|
module Formatter
|
8
8
|
# The formatter used for <tt>--format json</tt>
|
9
|
-
class Json
|
9
|
+
class Json
|
10
10
|
include Io
|
11
11
|
|
12
|
-
def initialize(runtime, io,
|
13
|
-
@
|
14
|
-
|
12
|
+
def initialize(runtime, io, _options)
|
13
|
+
@runtime = runtime
|
14
|
+
@io = ensure_io(io, 'json')
|
15
|
+
@feature_hashes = []
|
15
16
|
end
|
17
|
+
|
18
|
+
def before_test_case(test_case)
|
19
|
+
builder = Builder.new(test_case)
|
20
|
+
unless same_feature_as_previous_test_case?(test_case.feature)
|
21
|
+
@feature_hash = builder.feature_hash
|
22
|
+
@feature_hashes << @feature_hash
|
23
|
+
end
|
24
|
+
@test_case_hash = builder.test_case_hash
|
25
|
+
if builder.background?
|
26
|
+
feature_elements << builder.background_hash
|
27
|
+
@element_hash = builder.background_hash
|
28
|
+
else
|
29
|
+
feature_elements << @test_case_hash
|
30
|
+
@element_hash = @test_case_hash
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def before_test_step(test_step)
|
35
|
+
return if internal_hook?(test_step)
|
36
|
+
hook_query = HookQueryVisitor.new(test_step)
|
37
|
+
if hook_query.hook?
|
38
|
+
@step_or_hook_hash = {}
|
39
|
+
hooks_of_type(hook_query) << @step_or_hook_hash
|
40
|
+
return
|
41
|
+
end
|
42
|
+
if first_step_after_background?(test_step)
|
43
|
+
feature_elements << @test_case_hash
|
44
|
+
@element_hash = @test_case_hash
|
45
|
+
end
|
46
|
+
@step_or_hook_hash = create_step_hash(test_step.source.last)
|
47
|
+
steps << @step_or_hook_hash
|
48
|
+
@step_hash = @step_or_hook_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def after_test_step(test_step, result)
|
52
|
+
return if internal_hook?(test_step)
|
53
|
+
add_match_and_result(test_step, result)
|
54
|
+
end
|
55
|
+
|
56
|
+
def done
|
57
|
+
@io.write(MultiJson.dump(@feature_hashes, pretty: true))
|
58
|
+
end
|
59
|
+
|
60
|
+
def puts(message)
|
61
|
+
test_step_output << message
|
62
|
+
end
|
63
|
+
|
64
|
+
def embed(src, mime_type, _label)
|
65
|
+
if File.file?(src)
|
66
|
+
content = File.open(src, 'rb') { |f| f.read }
|
67
|
+
data = encode64(content)
|
68
|
+
else
|
69
|
+
if mime_type =~ /;base64$/
|
70
|
+
mime_type = mime_type[0..-8]
|
71
|
+
data = src
|
72
|
+
else
|
73
|
+
data = encode64(src)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
test_step_embeddings << { mime_type: mime_type, data: data }
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def same_feature_as_previous_test_case?(feature)
|
82
|
+
current_feature[:uri] == feature.file && current_feature[:line] == feature.location.line
|
83
|
+
end
|
84
|
+
|
85
|
+
def first_step_after_background?(test_step)
|
86
|
+
test_step.source[1].name != @element_hash[:name]
|
87
|
+
end
|
88
|
+
|
89
|
+
def internal_hook?(test_step)
|
90
|
+
test_step.source.last.location.file.include?('lib/cucumber/')
|
91
|
+
end
|
92
|
+
|
93
|
+
def current_feature
|
94
|
+
@feature_hash ||= {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def feature_elements
|
98
|
+
@feature_hash[:elements] ||= []
|
99
|
+
end
|
100
|
+
|
101
|
+
def steps
|
102
|
+
@element_hash[:steps] ||= []
|
103
|
+
end
|
104
|
+
|
105
|
+
def hooks_of_type(hook_query)
|
106
|
+
case hook_query.type
|
107
|
+
when :before
|
108
|
+
return before_hooks
|
109
|
+
when :after
|
110
|
+
return after_hooks
|
111
|
+
when :after_step
|
112
|
+
return after_step_hooks
|
113
|
+
else
|
114
|
+
fail 'Unkown hook type ' + hook_query.type.to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def before_hooks
|
119
|
+
@element_hash[:before] ||= []
|
120
|
+
end
|
121
|
+
|
122
|
+
def after_hooks
|
123
|
+
@element_hash[:after] ||= []
|
124
|
+
end
|
125
|
+
|
126
|
+
def after_step_hooks
|
127
|
+
@step_hash[:after] ||= []
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_step_output
|
131
|
+
@step_or_hook_hash[:output] ||= []
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_step_embeddings
|
135
|
+
@step_or_hook_hash[:embeddings] ||= []
|
136
|
+
end
|
137
|
+
|
138
|
+
def create_step_hash(step_source)
|
139
|
+
step_hash = {
|
140
|
+
keyword: step_source.keyword,
|
141
|
+
name: step_source.name,
|
142
|
+
line: step_source.location.line
|
143
|
+
}
|
144
|
+
step_hash[:comments] = Formatter.create_comments_array(step_source.comments) unless step_source.comments.empty?
|
145
|
+
step_hash[:doc_string] = create_doc_string_hash(step_source.multiline_arg) if step_source.multiline_arg.doc_string?
|
146
|
+
step_hash
|
147
|
+
end
|
148
|
+
|
149
|
+
def create_doc_string_hash(doc_string)
|
150
|
+
{
|
151
|
+
value: doc_string.content,
|
152
|
+
content_type: doc_string.content_type,
|
153
|
+
line: doc_string.location.line
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
def add_match_and_result(test_step, result)
|
158
|
+
@step_or_hook_hash[:match] = create_match_hash(test_step, result)
|
159
|
+
@step_or_hook_hash[:result] = create_result_hash(result)
|
160
|
+
end
|
161
|
+
|
162
|
+
def create_match_hash(test_step, result)
|
163
|
+
{ location: test_step.action_location }
|
164
|
+
end
|
165
|
+
|
166
|
+
def create_result_hash(result)
|
167
|
+
result_hash = {
|
168
|
+
status: result.to_sym
|
169
|
+
}
|
170
|
+
result_hash[:error_message] = create_error_message(result) if result.failed? || result.pending?
|
171
|
+
result.duration.tap { |duration| result_hash[:duration] = duration.nanoseconds }
|
172
|
+
result_hash
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_error_message(result)
|
176
|
+
message_element = result.failed? ? result.exception : result
|
177
|
+
message = "#{message_element.message} (#{message_element.class})"
|
178
|
+
([message] + message_element.backtrace).join("\n")
|
179
|
+
end
|
180
|
+
|
181
|
+
def encode64(data)
|
182
|
+
# strip newlines from the encoded data
|
183
|
+
Base64.encode64(data).gsub(/\n/, '')
|
184
|
+
end
|
185
|
+
|
186
|
+
class Builder
|
187
|
+
attr_reader :feature_hash, :background_hash, :test_case_hash
|
188
|
+
|
189
|
+
def initialize(test_case)
|
190
|
+
@background_hash = nil
|
191
|
+
test_case.describe_source_to(self)
|
192
|
+
test_case.feature.background.describe_to(self)
|
193
|
+
end
|
194
|
+
|
195
|
+
def background?
|
196
|
+
@background_hash != nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def feature(feature)
|
200
|
+
@feature_hash = {
|
201
|
+
uri: feature.file,
|
202
|
+
id: create_id(feature),
|
203
|
+
keyword: feature.keyword,
|
204
|
+
name: feature.name,
|
205
|
+
description: feature.description,
|
206
|
+
line: feature.location.line
|
207
|
+
}
|
208
|
+
unless feature.tags.empty?
|
209
|
+
@feature_hash[:tags] = create_tags_array(feature.tags)
|
210
|
+
if @test_case_hash[:tags]
|
211
|
+
@test_case_hash[:tags] = @feature_hash[:tags] + @test_case_hash[:tags]
|
212
|
+
else
|
213
|
+
@test_case_hash[:tags] = @feature_hash[:tags]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
@feature_hash[:comments] = Formatter.create_comments_array(feature.comments) unless feature.comments.empty?
|
217
|
+
@test_case_hash[:id].insert(0, @feature_hash[:id] + ';')
|
218
|
+
end
|
219
|
+
|
220
|
+
def background(background)
|
221
|
+
@background_hash = {
|
222
|
+
keyword: background.keyword,
|
223
|
+
name: background.name,
|
224
|
+
description: background.description,
|
225
|
+
line: background.location.line,
|
226
|
+
type: 'background'
|
227
|
+
}
|
228
|
+
@background_hash[:comments] = Formatter.create_comments_array(background.comments) unless background.comments.empty?
|
229
|
+
end
|
230
|
+
|
231
|
+
def scenario(scenario)
|
232
|
+
@test_case_hash = {
|
233
|
+
id: create_id(scenario),
|
234
|
+
keyword: scenario.keyword,
|
235
|
+
name: scenario.name,
|
236
|
+
description: scenario.description,
|
237
|
+
line: scenario.location.line,
|
238
|
+
type: 'scenario'
|
239
|
+
}
|
240
|
+
@test_case_hash[:tags] = create_tags_array(scenario.tags) unless scenario.tags.empty?
|
241
|
+
@test_case_hash[:comments] = Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
|
242
|
+
end
|
243
|
+
|
244
|
+
def scenario_outline(scenario)
|
245
|
+
@test_case_hash = {
|
246
|
+
id: create_id(scenario) + ';' + @example_id,
|
247
|
+
keyword: scenario.keyword,
|
248
|
+
name: scenario.name,
|
249
|
+
description: scenario.description,
|
250
|
+
line: @row.location.line,
|
251
|
+
type: 'scenario'
|
252
|
+
}
|
253
|
+
tags = []
|
254
|
+
tags += create_tags_array(scenario.tags) unless scenario.tags.empty?
|
255
|
+
tags += @examples_table_tags if @examples_table_tags
|
256
|
+
@test_case_hash[:tags] = tags unless tags.empty?
|
257
|
+
comments = []
|
258
|
+
comments += Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty?
|
259
|
+
comments += @examples_table_comments if @examples_table_comments
|
260
|
+
comments += @row_comments if @row_comments
|
261
|
+
@test_case_hash[:comments] = comments unless comments.empty?
|
262
|
+
end
|
263
|
+
|
264
|
+
def examples_table(examples_table)
|
265
|
+
# the json file have traditionally used the header row as row 1,
|
266
|
+
# wheras cucumber-ruby-core used the first example row as row 1.
|
267
|
+
@example_id = create_id(examples_table) + ";#{@row.number + 1}"
|
268
|
+
|
269
|
+
@examples_table_tags = create_tags_array(examples_table.tags) unless examples_table.tags.empty?
|
270
|
+
@examples_table_comments = Formatter.create_comments_array(examples_table.comments) unless examples_table.comments.empty?
|
271
|
+
end
|
272
|
+
|
273
|
+
def examples_table_row(row)
|
274
|
+
@row = row
|
275
|
+
@row_comments = Formatter.create_comments_array(row.comments) unless row.comments.empty?
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
def create_id(element)
|
281
|
+
element.name.downcase.gsub(/ /, '-')
|
282
|
+
end
|
283
|
+
|
284
|
+
def create_tags_array(tags)
|
285
|
+
tags_array = []
|
286
|
+
tags.each { |tag| tags_array << { name: tag.name, line: tag.location.line } }
|
287
|
+
tags_array
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def self.create_comments_array(comments)
|
293
|
+
comments_array = []
|
294
|
+
comments.each { |comment| comments_array << { value: comment.to_s.strip, line: comment.location.line } }
|
295
|
+
comments_array
|
16
296
|
end
|
17
297
|
end
|
18
298
|
end
|
19
|
-
|