cucumber 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +7 -9
  3. data/History.md +295 -265
  4. data/README.md +9 -7
  5. data/cucumber.gemspec +2 -2
  6. data/features/docs/cli/dry_run.feature +0 -3
  7. data/features/docs/cli/finding_steps.feature +28 -0
  8. data/features/docs/cli/run_specific_scenarios.feature +3 -1
  9. data/features/docs/cli/specifying_multiple_formatters.feature +22 -1
  10. data/features/docs/defining_steps/nested_steps.feature +0 -1
  11. data/features/docs/defining_steps/printing_messages.feature +4 -4
  12. data/features/docs/defining_steps/skip_scenario.feature +0 -2
  13. data/features/docs/exception_in_around_hook.feature +1 -3
  14. data/features/docs/formatters/html_formatter.feature +1 -0
  15. data/features/docs/formatters/json_formatter.feature +73 -62
  16. data/features/docs/formatters/junit_formatter.feature +130 -38
  17. data/features/docs/formatters/rerun_formatter.feature +60 -8
  18. data/features/docs/formatters/usage_formatter.feature +3 -7
  19. data/features/docs/getting_started.feature +1 -1
  20. data/features/docs/gherkin/background.feature +0 -11
  21. data/features/docs/gherkin/language_help.feature +5 -0
  22. data/features/docs/gherkin/outlines.feature +1 -3
  23. data/features/docs/gherkin/using_descriptions.feature +0 -1
  24. data/features/docs/raketask.feature +1 -1
  25. data/features/docs/writing_support_code/after_hooks.feature +22 -0
  26. data/features/lib/step_definitions/aruba_steps.rb +4 -0
  27. data/features/lib/step_definitions/junit_steps.rb +1 -1
  28. data/features/lib/support/normalise_output.rb +21 -4
  29. data/lib/cucumber/cli/configuration.rb +16 -13
  30. data/lib/cucumber/cli/main.rb +35 -10
  31. data/lib/cucumber/cli/options.rb +33 -9
  32. data/lib/cucumber/cli/rerun_file.rb +29 -0
  33. data/lib/cucumber/filters/prepare_world.rb +2 -3
  34. data/lib/cucumber/formatter/backtrace_filter.rb +40 -0
  35. data/lib/cucumber/formatter/console.rb +2 -3
  36. data/lib/cucumber/formatter/cucumber.css +1 -0
  37. data/lib/cucumber/formatter/duration_extractor.rb +28 -0
  38. data/lib/cucumber/formatter/hook_query_visitor.rb +40 -0
  39. data/lib/cucumber/formatter/html.rb +16 -1
  40. data/lib/cucumber/formatter/json.rb +287 -8
  41. data/lib/cucumber/formatter/junit.rb +92 -143
  42. data/lib/cucumber/formatter/legacy_api/adapter.rb +18 -54
  43. data/lib/cucumber/formatter/legacy_api/ast.rb +13 -0
  44. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +4 -0
  45. data/lib/cucumber/formatter/pretty.rb +2 -1
  46. data/lib/cucumber/formatter/progress.rb +20 -53
  47. data/lib/cucumber/formatter/rerun.rb +2 -1
  48. data/lib/cucumber/formatter/usage.rb +16 -22
  49. data/lib/cucumber/hooks.rb +18 -9
  50. data/lib/cucumber/multiline_argument/data_table.rb +40 -28
  51. data/lib/cucumber/platform.rb +1 -1
  52. data/lib/cucumber/rb_support/rb_hook.rb +4 -0
  53. data/lib/cucumber/running_test_case.rb +13 -4
  54. data/lib/cucumber/runtime/after_hooks.rb +7 -6
  55. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  56. data/lib/cucumber/runtime/step_hooks.rb +5 -4
  57. data/lib/cucumber/runtime/support_code.rb +6 -15
  58. data/lib/cucumber/step_match.rb +1 -1
  59. data/spec/cucumber/cli/configuration_spec.rb +32 -5
  60. data/spec/cucumber/cli/main_spec.rb +3 -3
  61. data/spec/cucumber/cli/options_spec.rb +60 -1
  62. data/spec/cucumber/cli/rerun_spec.rb +89 -0
  63. data/spec/cucumber/formatter/html_spec.rb +84 -5
  64. data/spec/cucumber/formatter/json_spec.rb +757 -0
  65. data/spec/cucumber/formatter/junit_spec.rb +5 -5
  66. data/spec/cucumber/formatter/legacy_api/adapter_spec.rb +69 -8
  67. data/spec/cucumber/formatter/pretty_spec.rb +96 -0
  68. data/spec/cucumber/formatter/progress_spec.rb +85 -1
  69. data/spec/cucumber/formatter/rerun_spec.rb +3 -3
  70. data/spec/cucumber/multiline_argument/data_table_spec.rb +89 -0
  71. data/spec/cucumber/running_test_case_spec.rb +57 -1
  72. metadata +70 -60
  73. data/lib/cucumber/formatter/gherkin_formatter_adapter.rb +0 -204
  74. data/lib/cucumber/formatter/gpretty.rb +0 -24
@@ -36,27 +36,34 @@ module Cucumber
36
36
  end
37
37
 
38
38
  runtime.run!
39
- failure = runtime.failure? || Cucumber.wants_to_quit
40
- @kernel.exit(failure ? 1 : 0)
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
- @kernel.exit(1)
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
- @kernel.exit(1)
56
+ exit_unable_to_finish
48
57
  rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
49
58
  @err.puts(e.message)
50
- @kernel.exit(1)
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
- @kernel.exit(1)
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
- @kernel.exit(1)
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
- exit!(1) if Cucumber.wants_to_quit
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
@@ -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
- @quiet = @disable_profile_loading = nil
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
- @quiet = true
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
- default_hook = Cucumber::Hooks.before_hook(@original_test_case.source) do
28
- #no op - legacy format adapter expects a before hooks
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(features, options)
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
@@ -197,6 +197,7 @@ body {
197
197
  background: #e0ffff;
198
198
  color: #001111;
199
199
  margin-left: 10px;
200
+ white-space: pre;
200
201
  }
201
202
  .cucumber #summary, td #summary, th #summary {
202
203
  margin: 0px;
@@ -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
- @builder.h3(:id => "scenario_#{@scenario_number}") do
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 'cucumber/formatter/gherkin_formatter_adapter'
1
+ require 'multi_json'
2
+ require 'base64'
2
3
  require 'cucumber/formatter/io'
3
- require 'gherkin/formatter/argument'
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 < GherkinFormatterAdapter
9
+ class Json
10
10
  include Io
11
11
 
12
- def initialize(runtime, io, options)
13
- @io = ensure_io(io, "json")
14
- super(Gherkin::Formatter::JSONFormatter.new(@io), false, options)
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
-