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.
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
-