cucumber 3.2.0 → 4.0.0.rc.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 (90) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +61 -18
  3. data/CONTRIBUTING.md +1 -0
  4. data/bin/cucumber +1 -1
  5. data/lib/autotest/cucumber_mixin.rb +42 -39
  6. data/lib/cucumber/cli/configuration.rb +4 -4
  7. data/lib/cucumber/cli/main.rb +11 -12
  8. data/lib/cucumber/cli/options.rb +56 -69
  9. data/lib/cucumber/cli/profile_loader.rb +32 -20
  10. data/lib/cucumber/configuration.rb +20 -21
  11. data/lib/cucumber/constantize.rb +2 -5
  12. data/lib/cucumber/deprecate.rb +5 -5
  13. data/lib/cucumber/errors.rb +4 -6
  14. data/lib/cucumber/events.rb +1 -0
  15. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  16. data/lib/cucumber/events/step_activated.rb +2 -1
  17. data/lib/cucumber/file_specs.rb +6 -6
  18. data/lib/cucumber/filters/activate_steps.rb +5 -3
  19. data/lib/cucumber/filters/prepare_world.rb +5 -9
  20. data/lib/cucumber/filters/quit.rb +1 -3
  21. data/lib/cucumber/filters/tag_limits/verifier.rb +2 -4
  22. data/lib/cucumber/formatter/ansicolor.rb +40 -45
  23. data/lib/cucumber/formatter/ast_lookup.rb +160 -0
  24. data/lib/cucumber/formatter/backtrace_filter.rb +5 -7
  25. data/lib/cucumber/formatter/console.rb +28 -59
  26. data/lib/cucumber/formatter/console_counts.rb +4 -9
  27. data/lib/cucumber/formatter/console_issues.rb +6 -3
  28. data/lib/cucumber/formatter/duration_extractor.rb +1 -1
  29. data/lib/cucumber/formatter/fanout.rb +2 -0
  30. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  31. data/lib/cucumber/formatter/interceptor.rb +5 -7
  32. data/lib/cucumber/formatter/io.rb +8 -14
  33. data/lib/cucumber/formatter/json.rb +93 -117
  34. data/lib/cucumber/formatter/junit.rb +55 -57
  35. data/lib/cucumber/formatter/pretty.rb +346 -152
  36. data/lib/cucumber/formatter/progress.rb +28 -32
  37. data/lib/cucumber/formatter/rerun.rb +22 -4
  38. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  39. data/lib/cucumber/formatter/steps.rb +2 -3
  40. data/lib/cucumber/formatter/summary.rb +16 -8
  41. data/lib/cucumber/formatter/unicode.rb +15 -17
  42. data/lib/cucumber/formatter/usage.rb +9 -8
  43. data/lib/cucumber/gherkin/data_table_parser.rb +8 -6
  44. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  45. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  46. data/lib/cucumber/gherkin/steps_parser.rb +7 -8
  47. data/lib/cucumber/glue/dsl.rb +1 -1
  48. data/lib/cucumber/glue/hook.rb +16 -9
  49. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  50. data/lib/cucumber/glue/proto_world.rb +14 -16
  51. data/lib/cucumber/glue/registry_and_more.rb +7 -9
  52. data/lib/cucumber/glue/snippet.rb +21 -20
  53. data/lib/cucumber/glue/step_definition.rb +14 -15
  54. data/lib/cucumber/glue/world_factory.rb +1 -1
  55. data/lib/cucumber/hooks.rb +11 -11
  56. data/lib/cucumber/multiline_argument.rb +4 -6
  57. data/lib/cucumber/multiline_argument/data_table.rb +88 -59
  58. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +1 -1
  59. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  60. data/lib/cucumber/platform.rb +3 -3
  61. data/lib/cucumber/rake/task.rb +13 -16
  62. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  63. data/lib/cucumber/running_test_case.rb +2 -53
  64. data/lib/cucumber/runtime.rb +27 -57
  65. data/lib/cucumber/runtime/after_hooks.rb +3 -3
  66. data/lib/cucumber/runtime/before_hooks.rb +3 -3
  67. data/lib/cucumber/runtime/for_programming_languages.rb +3 -2
  68. data/lib/cucumber/runtime/step_hooks.rb +1 -1
  69. data/lib/cucumber/runtime/support_code.rb +10 -12
  70. data/lib/cucumber/runtime/user_interface.rb +4 -6
  71. data/lib/cucumber/step_definition_light.rb +4 -3
  72. data/lib/cucumber/step_match.rb +12 -11
  73. data/lib/cucumber/step_match_search.rb +2 -1
  74. data/lib/cucumber/term/ansicolor.rb +9 -9
  75. data/lib/cucumber/version +1 -1
  76. metadata +37 -28
  77. data/lib/cucumber/formatter/cucumber.css +0 -286
  78. data/lib/cucumber/formatter/cucumber.sass +0 -247
  79. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  80. data/lib/cucumber/formatter/html.rb +0 -611
  81. data/lib/cucumber/formatter/html_builder.rb +0 -121
  82. data/lib/cucumber/formatter/http_io.rb +0 -146
  83. data/lib/cucumber/formatter/inline-js.js +0 -30
  84. data/lib/cucumber/formatter/jquery-min.js +0 -154
  85. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  86. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  87. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  88. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  89. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  90. data/lib/cucumber/step_argument.rb +0 -25
@@ -1,247 +0,0 @@
1
- /* cucumber.css is generated from cucumber.sass */
2
- /* Regenerate with rake sass */
3
-
4
- $step_left: 5px solid
5
- $step_bottom: 1px solid
6
-
7
- $failed: #fffbd3
8
- $failed_border: #c20000
9
- $failed_text: #c20000
10
-
11
- $passed: #dbffb4
12
- $passed_border: #65c400
13
- $passed_text: #3d7700
14
-
15
- $skipped: #e0ffff
16
- $skipped_border: aqua
17
- $skipped_text: #001111
18
-
19
- $pending: #fcfb98
20
- $pending_border: #faf834
21
- $pending_text: #131313
22
-
23
- $undefined: #fcfb98
24
- $undefined_border: #faf834
25
- $undefined_text: #131313
26
-
27
- $message: #e0ffff
28
- $message_border: aqua
29
- $message_text: #001111
30
-
31
- body
32
- font-size: 0px
33
- color: white
34
- margin: 0px
35
- padding: 0px
36
-
37
- .cucumber,td,th
38
- font: normal 11px "Lucida Grande", Helvetica, sans-serif
39
- background: white
40
- color: black
41
- #cucumber-header
42
- background: #65c400
43
- color: white
44
- height: 6em
45
- #expand-collapse
46
- p
47
- float: right
48
- margin: 0 0 0 10px
49
- .scenario
50
- h3
51
- font-size: 11px
52
- padding: 3px
53
- margin: 0
54
- background: #65c400
55
- color: white
56
- font-weight: bold
57
- h1
58
- margin: 0px 10px 0px 10px
59
- padding: 10px
60
- font-family: "Lucida Grande", Helvetica, sans-serif
61
- font-size: 2em
62
- position: absolute
63
- h4
64
- margin-bottom: 2px
65
- div.feature
66
- padding: 2px
67
- margin: 0px 10px 5px 10px
68
- div.examples
69
- padding: 0em 0em 0em 1em
70
- .stats
71
- margin: 2em
72
- .summary
73
- ul.features
74
- li
75
- display: inline
76
- .step_name
77
- float: left
78
- .step_file
79
- text-align: right
80
- color: #999999
81
- a
82
- color: #999999
83
- .scenario_file
84
- float: right
85
- color: #999999
86
- .tag
87
- font-weight: bold
88
- color: #246ac1
89
- .backtrace
90
- margin-top: 0
91
- margin-bottom: 0
92
- margin-left: 1em
93
- color: black
94
- a
95
- text-decoration: none
96
- color: #be5c00
97
- &:hover
98
- text-decoration: underline
99
- &:visited
100
- font-weight: normal
101
- div.examples
102
- margin: 5px 0px 5px 15px
103
- color: black
104
- .outline
105
- table
106
- margin: 0px 0px 5px 10px
107
- table
108
- border-collapse: collapse
109
- td
110
- padding: 3px 3px 3px 5px
111
- td.failed, td.passed, td.skipped, td.pending, td.undefined
112
- padding-left: 18px
113
- padding-right: 10px
114
- td.failed
115
- border-left: $step_left $failed_border
116
- border-bottom: $step_bottom $failed_border
117
- background: $failed
118
- color: $failed_text
119
- td.passed
120
- border-left: $step_left $passed_border
121
- border-bottom: $step_bottom $passed_border
122
- background: $passed
123
- color: $passed_text
124
- td.skipped
125
- border-left: $step_left $skipped_border
126
- border-bottom: $step_bottom $skipped_border
127
- background: $skipped
128
- color: $skipped_text
129
- td.pending
130
- border-left: $step_left $pending_border
131
- border-bottom: $step_bottom $pending_border
132
- background: $pending
133
- color: $pending_text
134
- td.undefined
135
- border-left: $step_left $undefined_border
136
- border-bottom: $step_bottom $undefined_border
137
- background: $undefined
138
- color: $undefined_text
139
- td.message
140
- border-left: $step_left $message_border
141
- border-bottom: $step_bottom $message_border
142
- background: $message
143
- color: $message_text
144
- ol
145
- list-style: none
146
- margin: 0px
147
- padding: 0px
148
- li.step
149
- padding: 3px 3px 3px 18px
150
- margin: 5px 0px 5px 5px
151
- li
152
- margin: 0em 0em 0em 1em
153
- padding: 0em 0em 0em 0.2em
154
- span.param
155
- font-weight: bold
156
- li.failed
157
- border-left: $step_left $failed_border
158
- border-bottom: $step_bottom $failed_border
159
- background: $failed
160
- color: $failed_text
161
- li.passed
162
- border-left: $step_left $passed_border
163
- border-bottom: $step_bottom $passed_border
164
- background: $passed
165
- color: $passed_text
166
- li.skipped
167
- border-left: $step_left $skipped_border
168
- border-bottom: $step_bottom $skipped_border
169
- background: $skipped
170
- color: $skipped_text
171
- li.pending
172
- border-left: $step_left $pending_border
173
- border-bottom: $step_bottom $pending_border
174
- background: $pending
175
- color: $pending_text
176
- li.undefined
177
- border-left: $step_left $undefined_border
178
- border-bottom: $step_bottom $undefined_border
179
- background: $undefined
180
- color: $undefined_text
181
- li.message
182
- border-left: $step_left $message_border
183
- border-bottom: $step_bottom $message_border
184
- background: $message
185
- color: $message_text
186
- margin-left: 10px
187
- #summary
188
- margin: 0px
189
- padding: 5px 10px
190
- text-align: right
191
- top: 0px
192
- right: 0px
193
- float: right
194
- p
195
- margin: 0 0 0 2px
196
- #totals
197
- font-size: 1.2em
198
-
199
- .ruby
200
- font-size: 12px
201
- font-family: monospace
202
- color: white
203
- background: black
204
- padding: 0.1em 0 0.2em 0
205
- .keyword
206
- color: #ff6600
207
- .constant
208
- color: #339999
209
- .attribute
210
- color: white
211
- .global
212
- color: white
213
- .module
214
- color: white
215
- .class
216
- color: white
217
- .string
218
- color: #66ff00
219
- .ident
220
- color: white
221
- .method
222
- color: #ffcc00
223
- .number
224
- color: white
225
- .char
226
- color: white
227
- .comment
228
- color: #9933cc
229
- .symbol
230
- color: white
231
- .regex
232
- color: #44b4cc
233
- .punct
234
- color: white
235
- .escape
236
- color: white
237
- .interp
238
- color: white
239
- .expr
240
- color: white
241
- .offending
242
- background: #333333
243
- .linenum
244
- width: 75px
245
- padding: 0.1em 1em 0.2em 0
246
- color: black
247
- background: #fffbd3
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Cucumber
4
- module Formatter
5
- class HookQueryVisitor
6
- attr_reader :type
7
-
8
- def initialize(test_step)
9
- @hook = false
10
- @type = :no_hook
11
- test_step.source.last.describe_to(self)
12
- end
13
-
14
- def hook?
15
- @hook
16
- end
17
-
18
- def step(*)
19
- end
20
-
21
- def before_hook(*)
22
- @hook = true
23
- @type = :before
24
- end
25
-
26
- def after_hook(*)
27
- @hook = true
28
- @type = :after
29
- end
30
-
31
- def after_step_hook(*)
32
- @hook = true
33
- @type = :after_step
34
- end
35
-
36
- def around_hook(*)
37
- @hook = true
38
- @type = :around
39
- end
40
- end
41
- end
42
- end
@@ -1,611 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'erb'
4
- require 'cucumber/formatter/duration'
5
- require 'cucumber/formatter/io'
6
- require 'cucumber/formatter/html_builder'
7
- require 'pathname'
8
-
9
- module Cucumber
10
- module Formatter
11
- class Html
12
- # TODO: remove coupling to types
13
- AST_CLASSES = {
14
- Cucumber::Core::Ast::Scenario => 'scenario',
15
- Cucumber::Core::Ast::ScenarioOutline => 'scenario outline'
16
- }
17
-
18
- AST_DATA_TABLE = LegacyApi::Ast::MultilineArg::DataTable
19
-
20
- include ERB::Util # for the #h method
21
- include Duration
22
- include Io
23
-
24
- attr_reader :builder
25
- private :builder
26
-
27
- def initialize(runtime, path_or_io, options)
28
- @io = ensure_io(path_or_io)
29
- @runtime = runtime
30
- @options = options
31
- @buffer = {}
32
- @builder = HtmlBuilder.new(target: @io, indent: 0)
33
- @feature_number = 0
34
- @scenario_number = 0
35
- @step_number = 0
36
- @header_red = nil
37
- @delayed_messages = []
38
- @inside_outline = false
39
- @previous_step_keyword = nil
40
- end
41
-
42
- def embed(src, mime_type, label)
43
- if image?(mime_type)
44
- src = src_is_file_or_data?(src) ? src : "data:#{standardize_mime_type(mime_type)},#{src}"
45
- builder.embed(type: :image, src: path(src), label: label, id: next_id(:img))
46
- else
47
- builder.embed(type: :text, src: src, label: label, id: next_id(:text))
48
- end
49
- end
50
-
51
- def path(src)
52
- if @io.respond_to?(:path) && File.file?(src)
53
- out_dir = Pathname.new(File.dirname(File.absolute_path(@io.path)))
54
- src = Pathname.new(File.absolute_path(src)).relative_path_from(out_dir)
55
- end
56
-
57
- src
58
- end
59
-
60
- def standardize_mime_type(mime_type)
61
- mime_type =~ /;base[0-9]+$/ ? mime_type : mime_type + ';base64'
62
- end
63
-
64
- def src_is_file_or_data?(src)
65
- File.file?(src) || src =~ /^data:image\/(png|gif|jpg|jpeg);base64,/
66
- end
67
-
68
- def image?(mime_type)
69
- mime_type =~ /^image\/(png|gif|jpg|jpeg)/
70
- end
71
-
72
- def before_features(features)
73
- @step_count = features && features.step_count || 0 # TODO: Make this work with core!
74
-
75
- builder.build_document!
76
- builder.format_features! features
77
- end
78
-
79
- def after_features(features)
80
- print_stats(features)
81
- builder << '</div>'
82
- builder << '</body>'
83
- builder << '</html>'
84
- end
85
-
86
- def before_feature(_feature)
87
- @exceptions = []
88
- builder << '<div class="feature">'
89
- end
90
-
91
- def after_feature(_feature)
92
- builder << '</div>'
93
- end
94
-
95
- def before_comment(_comment)
96
- builder << '<pre class="comment">'
97
- end
98
-
99
- def after_comment(_comment)
100
- builder << '</pre>'
101
- end
102
-
103
- def comment_line(comment_line)
104
- builder.text!(comment_line)
105
- builder.br
106
- end
107
-
108
- def after_tags(_tags)
109
- @tag_spacer = nil
110
- end
111
-
112
- def tag_name(tag_name)
113
- builder.text!(@tag_spacer) if @tag_spacer
114
- @tag_spacer = ' '
115
- builder.span(tag_name, :class => 'tag')
116
- end
117
-
118
- def feature_name(keyword, name)
119
- lines = name.split(/\r?\n/)
120
- return if lines.empty?
121
- builder.h2 do |h2|
122
- builder.span(keyword + ': ' + lines[0], :class => 'val')
123
- end
124
- builder.p(:class => 'narrative') do
125
- lines[1..-1].each do |line|
126
- builder.text!(line.strip)
127
- builder.br
128
- end
129
- end
130
- end
131
-
132
- def before_test_case(_test_case)
133
- @previous_step_keyword = nil
134
- end
135
-
136
- def before_background(_background)
137
- @in_background = true
138
- builder << '<div class="background">'
139
- end
140
-
141
- def after_background(_background)
142
- @in_background = nil
143
- builder << '</div>'
144
- end
145
-
146
- def background_name(keyword, name, _file_colon_line, _source_indent)
147
- @listing_background = true
148
- builder.h3(:id => "background_#{@scenario_number}") do |h3|
149
- builder.span(keyword, :class => 'keyword')
150
- builder.text!(' ')
151
- builder.span(name, :class => 'val')
152
- end
153
- end
154
-
155
- def before_feature_element(feature_element)
156
- @scenario_number += 1
157
- @scenario_red = false
158
- css_class = AST_CLASSES[feature_element.class]
159
- builder << "<div class='#{css_class}'>"
160
- @in_scenario_outline = feature_element.class == Cucumber::Core::Ast::ScenarioOutline
161
- end
162
-
163
- def after_feature_element(_feature_element)
164
- unless @in_scenario_outline
165
- print_messages
166
- builder << '</ol>'
167
- end
168
- builder << '</div>'
169
- @in_scenario_outline = nil
170
- end
171
-
172
- def scenario_name(keyword, name, file_colon_line, _source_indent)
173
- builder.span(:class => 'scenario_file') do
174
- builder << file_colon_line
175
- end
176
- @listing_background = false
177
- scenario_id = "scenario_#{@scenario_number}"
178
- if @inside_outline
179
- @outline_row += 1
180
- scenario_id += "_#{@outline_row}"
181
- @scenario_red = false
182
- end
183
- builder.h3(:id => scenario_id) do
184
- builder.span(keyword + ':', :class => 'keyword')
185
- builder.text!(' ')
186
- builder.span(name, :class => 'val')
187
- end
188
- end
189
-
190
- def before_outline_table(_outline_table)
191
- @inside_outline = true
192
- @outline_row = 0
193
- builder << '<table>'
194
- end
195
-
196
- def after_outline_table(_outline_table)
197
- builder << '</table>'
198
- @outline_row = nil
199
- @inside_outline = false
200
- end
201
-
202
- def before_examples(_examples)
203
- builder << '<div class="examples">'
204
- end
205
-
206
- def after_examples(_examples)
207
- builder << '</div>'
208
- end
209
-
210
- def examples_name(keyword, name)
211
- builder.h4 do
212
- builder.span(keyword, :class => 'keyword')
213
- builder.text!(' ')
214
- builder.span(name, :class => 'val')
215
- end
216
- end
217
-
218
- def before_steps(_steps)
219
- builder << '<ol>'
220
- end
221
-
222
- def after_steps(_steps)
223
- print_messages
224
- builder << '</ol>' if @in_background || @in_scenario_outline
225
- end
226
-
227
- def before_step(step)
228
- print_messages
229
- @step_id = step.dom_id
230
- @step_number += 1
231
- @step = step
232
- end
233
-
234
- def after_step(_step)
235
- move_progress
236
- end
237
-
238
- def before_step_result(_keyword, step_match, _multiline_arg, status, exception, _source_indent, background, _file_colon_line)
239
- @step_match = step_match
240
- @hide_this_step = false
241
- if exception
242
- if @exceptions.include?(exception)
243
- @hide_this_step = true
244
- return
245
- end
246
- @exceptions << exception
247
- end
248
- if status != :failed && @in_background ^ background
249
- @hide_this_step = true
250
- return
251
- end
252
- @status = status
253
- return if @hide_this_step
254
- scenario_color(status)
255
- builder << "<li id='#{@step_id}' class='step #{status}'>"
256
- end
257
-
258
- def after_step_result(keyword, step_match, _multiline_arg, status, _exception, _source_indent, _background, _file_colon_line)
259
- return if @hide_this_step
260
- # print snippet for undefined steps
261
- unless outline_step?(@step)
262
- keyword = @step.actual_keyword(@previous_step_keyword)
263
- @previous_step_keyword = keyword
264
- end
265
- if status == :undefined
266
- builder.pre do |pre|
267
- # TODO: snippet text should be an event sent to the formatter so we don't
268
- # have this couping to the runtime.
269
- pre << @runtime.snippet_text(keyword, step_match.instance_variable_get('@name') || '', @step.multiline_arg)
270
- end
271
- end
272
- builder << '</li>'
273
- print_messages
274
- end
275
-
276
- def step_name(keyword, step_match, status, _source_indent, background, _file_colon_line)
277
- background_in_scenario = background && !@listing_background
278
- @skip_step = background_in_scenario
279
-
280
- unless @skip_step
281
- build_step(keyword, step_match, status)
282
- end
283
- end
284
-
285
- def exception(exception, _status)
286
- return if @hide_this_step
287
- print_messages
288
- build_exception_detail(exception)
289
- end
290
-
291
- def extra_failure_content(file_colon_line)
292
- @snippet_extractor ||= SnippetExtractor.new
293
- "<pre class=\"ruby\"><code>#{@snippet_extractor.snippet(file_colon_line)}</code></pre>"
294
- end
295
-
296
- def before_multiline_arg(multiline_arg)
297
- return if @hide_this_step || @skip_step
298
- if AST_DATA_TABLE === multiline_arg
299
- builder << '<table>'
300
- end
301
- end
302
-
303
- def after_multiline_arg(multiline_arg)
304
- return if @hide_this_step || @skip_step
305
- if AST_DATA_TABLE === multiline_arg
306
- builder << '</table>'
307
- end
308
- end
309
-
310
- def doc_string(string)
311
- return if @hide_this_step
312
- builder.pre(:class => 'val') do |pre|
313
- builder << h(string).gsub("\n", '&#x000A;')
314
- end
315
- end
316
-
317
- def before_table_row(table_row)
318
- @row_id = table_row.dom_id
319
- @col_index = 0
320
- return if @hide_this_step
321
- builder << "<tr class='step' id='#{@row_id}'>"
322
- end
323
-
324
- def after_table_row(table_row)
325
- return if @hide_this_step
326
- print_table_row_messages
327
- builder << '</tr>'
328
- if table_row.exception
329
- builder.tr do
330
- builder.td(:colspan => @col_index.to_s, :class => 'failed') do
331
- builder.pre do |pre|
332
- pre << h(format_exception(table_row.exception))
333
- end
334
- end
335
- end
336
- if table_row.exception.is_a? ::Cucumber::Pending
337
- set_scenario_color_pending
338
- else
339
- set_scenario_color_failed
340
- end
341
- end
342
- if @outline_row
343
- @outline_row += 1
344
- end
345
- @step_number += 1
346
- move_progress
347
- end
348
-
349
- def table_cell_value(value, status)
350
- return if @hide_this_step
351
-
352
- @cell_type = @outline_row == 0 ? :th : :td
353
- attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'step'}
354
- attributes[:class] += " #{status}" if status
355
- build_cell(@cell_type, value, attributes)
356
- scenario_color(status) if @inside_outline
357
- @col_index += 1
358
- end
359
-
360
- def puts(message)
361
- @delayed_messages << message
362
- end
363
-
364
- def print_messages
365
- return if @delayed_messages.empty?
366
-
367
- @delayed_messages.each do |ann|
368
- builder.li(:class => 'step message') do
369
- builder << ann
370
- end
371
- end
372
- empty_messages
373
- end
374
-
375
- def print_table_row_messages
376
- return if @delayed_messages.empty?
377
-
378
- builder.td(:class => 'message') do
379
- builder << @delayed_messages.join(', ')
380
- end
381
- empty_messages
382
- end
383
-
384
- def empty_messages
385
- @delayed_messages = []
386
- end
387
-
388
- def after_test_case(_test_case, result)
389
- if result.failed? && !@scenario_red
390
- set_scenario_color_failed
391
- end
392
- end
393
-
394
- protected
395
-
396
- def next_id(type)
397
- @indices ||= Hash.new { 0 }
398
- @indices[type] += 1
399
- "#{type}_#{@indices[type]}"
400
- end
401
-
402
- def build_exception_detail(exception)
403
- backtrace = Array.new
404
-
405
- builder.div(:class => 'message') do
406
- message = exception.message
407
-
408
- if defined?(RAILS_ROOT) && message.include?('Exception caught')
409
- matches = message.match(/Showing <i>(.+)<\/i>(?:.+) #(\d+)/)
410
- backtrace += ["#{RAILS_ROOT}/#{matches[1]}:#{matches[2]}"] if matches
411
- matches = message.match(/<code>([^(\/)]+)<\//m)
412
- message = matches ? matches[1] : ''
413
- end
414
-
415
- unless exception.instance_of?(RuntimeError)
416
- message = "#{message} (#{exception.class})"
417
- end
418
-
419
- builder.pre do
420
- builder.text!(message)
421
- end
422
- end
423
-
424
- builder.div(:class => 'backtrace') do
425
- builder.pre do
426
- backtrace = exception.backtrace
427
- backtrace.delete_if { |x| x =~ /\/gems\/(cucumber|rspec)/ }
428
- builder << backtrace_line(backtrace.join("\n"))
429
- end
430
- end
431
-
432
- extra = extra_failure_content(backtrace)
433
- builder << extra unless extra == ''
434
- end
435
-
436
- def scenario_color(status)
437
- if status.nil? || status == :undefined || status == :pending
438
- set_scenario_color_pending
439
- end
440
- if status == :failed
441
- set_scenario_color_failed
442
- end
443
- end
444
-
445
- def set_scenario_color_failed
446
- builder.script do
447
- builder.text!("makeRed('cucumber-header');") unless @header_red
448
- @header_red = true
449
- scenario_or_background = @in_background ? 'background' : 'scenario'
450
- builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
451
- @scenario_red = true
452
- if @options[:expand] && @inside_outline
453
- builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}_#{@outline_row}');")
454
- end
455
- end
456
- end
457
-
458
- def set_scenario_color_pending
459
- builder.script do
460
- builder.text!("makeYellow('cucumber-header');") unless @header_red
461
- scenario_or_background = @in_background ? 'background' : 'scenario'
462
- builder.text!("makeYellow('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
463
- end
464
- end
465
-
466
- def build_step(keyword, step_match, _status)
467
- step_name = step_match.format_args(lambda { |param| %{<span class="param">#{param}</span>} })
468
- builder.div(:class => 'step_name') do |div|
469
- builder.span(keyword, :class => 'keyword')
470
- builder.span(:class => 'step val') do |name|
471
- name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
472
- end
473
- end
474
-
475
- step_file = step_match.file_colon_line
476
- step_file.gsub(/^([^:]*\.rb):(\d*)/) do
477
- if ENV['TM_PROJECT_DIRECTORY']
478
- step_file = "<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
479
- end
480
- end
481
-
482
- builder.div(:class => 'step_file') do |div|
483
- builder.span do
484
- builder << step_file
485
- end
486
- end
487
- end
488
-
489
- def build_cell(cell_type, value, attributes)
490
- builder.__send__(cell_type, attributes) do
491
- builder.div do
492
- builder.span(value, :class => 'step param')
493
- end
494
- end
495
- end
496
-
497
- def move_progress
498
- builder << " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
499
- end
500
-
501
- def percent_done
502
- result = 100.0
503
- if @step_count != 0
504
- result = ((@step_number).to_f / @step_count.to_f * 1000).to_i / 10.0
505
- end
506
- result
507
- end
508
-
509
- def format_exception(exception)
510
- ([exception.message.to_s] + exception.backtrace).join("\n")
511
- end
512
-
513
- def backtrace_line(line)
514
- if ENV['TM_PROJECT_DIRECTORY']
515
- line.gsub(/^([^:]*\.(?:rb|feature|haml)):(\d*).*$/) do
516
- "<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
517
- end
518
- else
519
- line
520
- end
521
- end
522
-
523
- def print_stats(features)
524
- builder << "<script type=\"text/javascript\">document.getElementById('duration').innerHTML = \"Finished in <strong>#{format_duration(features.duration)} seconds</strong>\";</script>"
525
- builder << "<script type=\"text/javascript\">document.getElementById('totals').innerHTML = \"#{print_stat_string(features)}\";</script>"
526
- end
527
-
528
- def print_stat_string(_features)
529
- string = String.new
530
- string << dump_count(@runtime.scenarios.length, 'scenario')
531
- scenario_count = print_status_counts { |status| @runtime.scenarios(status) }
532
- string << scenario_count if scenario_count
533
- string << '<br />'
534
- string << dump_count(@runtime.steps.length, 'step')
535
- step_count = print_status_counts { |status| @runtime.steps(status) }
536
- string << step_count if step_count
537
- end
538
-
539
- def print_status_counts
540
- counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
541
- elements = yield status
542
- elements.any? ? "#{elements.length} #{status}" : nil
543
- end.compact
544
- return " (#{counts.join(', ')})" if counts.any?
545
- end
546
-
547
- def dump_count(count, what, state = nil)
548
- [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(' ')
549
- end
550
-
551
- def outline_step?(_step)
552
- not @step.step.respond_to?(:actual_keyword)
553
- end
554
-
555
- class SnippetExtractor #:nodoc:
556
- class NullConverter; def convert(code, _pre); code; end; end #:nodoc:
557
-
558
- begin
559
- require 'syntax/convertors/html'
560
- @@converter = Syntax::Convertors::HTML.for_syntax 'ruby'
561
- rescue LoadError
562
- @@converter = NullConverter.new
563
- end
564
-
565
- def snippet(error)
566
- raw_code, line = snippet_for(error[0])
567
- highlighted = @@converter.convert(raw_code, false)
568
- highlighted += "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
569
- post_process(highlighted, line)
570
- end
571
-
572
- def snippet_for(error_line)
573
- if error_line =~ /(.*):(\d+)/
574
- file = $1
575
- line = $2.to_i
576
- [lines_around(file, line), line]
577
- else
578
- ["# Couldn't get snippet for #{error_line}", 1]
579
- end
580
- end
581
-
582
- def lines_around(file, line)
583
- if File.file?(file)
584
- begin
585
- lines = File.open(file).read.split("\n")
586
- rescue ArgumentError
587
- return "# Couldn't get snippet for #{file}"
588
- end
589
- min = [0, line - 3].max
590
- max = [line + 1, lines.length - 1].min
591
- selected_lines = []
592
- selected_lines.join("\n")
593
- lines[min..max].join("\n")
594
- else
595
- "# Couldn't get snippet for #{file}"
596
- end
597
- end
598
-
599
- def post_process(highlighted, offending_line)
600
- new_lines = []
601
- highlighted.split("\n").each_with_index do |line, i|
602
- new_line = "<span class=\"linenum\">#{offending_line + i - 2}</span>#{line}"
603
- new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
604
- new_lines << new_line
605
- end
606
- new_lines.join("\n")
607
- end
608
- end
609
- end
610
- end
611
- end