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
@@ -60,6 +60,7 @@ module Cucumber
|
|
60
60
|
comments.each do |comment|
|
61
61
|
formatter.comment_line comment.to_s.strip
|
62
62
|
end
|
63
|
+
formatter.after_comment comments
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
@@ -107,6 +108,7 @@ module Cucumber
|
|
107
108
|
|
108
109
|
def accept(formatter)
|
109
110
|
formatter.before_step(self)
|
111
|
+
Ast::Comments.new(step.comments).accept(formatter)
|
110
112
|
messages.each { |message| formatter.puts(message) }
|
111
113
|
embeddings.each { |embedding| embedding.send_to_formatter(formatter) }
|
112
114
|
formatter.before_step_result *step_result_attributes
|
@@ -370,6 +372,17 @@ module Cucumber
|
|
370
372
|
|
371
373
|
Features = Struct.new(:duration)
|
372
374
|
|
375
|
+
class Background < SimpleDelegator
|
376
|
+
def initialize(feature, node)
|
377
|
+
super node
|
378
|
+
@feature = feature
|
379
|
+
end
|
380
|
+
|
381
|
+
def feature
|
382
|
+
@feature
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
373
386
|
end
|
374
387
|
end
|
375
388
|
end
|
@@ -240,7 +240,8 @@ module Cucumber
|
|
240
240
|
end
|
241
241
|
|
242
242
|
def print_summary(features)
|
243
|
-
|
243
|
+
duration = features ? features.duration : nil
|
244
|
+
print_stats(duration, @options)
|
244
245
|
print_snippets(@options)
|
245
246
|
print_passing_wip(@options)
|
246
247
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'cucumber/formatter/console'
|
2
2
|
require 'cucumber/formatter/io'
|
3
|
+
require 'cucumber/formatter/duration_extractor'
|
4
|
+
require 'cucumber/formatter/hook_query_visitor'
|
3
5
|
|
4
6
|
module Cucumber
|
5
7
|
module Formatter
|
@@ -13,73 +15,38 @@ module Cucumber
|
|
13
15
|
@runtime, @io, @options = runtime, ensure_io(path_or_io, "progress"), options
|
14
16
|
@previous_step_keyword = nil
|
15
17
|
@snippets_input = []
|
18
|
+
@total_duration = 0
|
16
19
|
end
|
17
20
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@
|
24
|
-
@io.puts
|
25
|
-
print_summary(features)
|
26
|
-
end
|
27
|
-
|
28
|
-
def before_feature_element(*args)
|
29
|
-
@exception_raised = false
|
30
|
-
end
|
31
|
-
|
32
|
-
def after_feature_element(*args)
|
33
|
-
progress(:failed) if (defined? @exception_raised) and (@exception_raised)
|
34
|
-
@exception_raised = false
|
35
|
-
end
|
36
|
-
|
37
|
-
def before_steps(*args)
|
38
|
-
progress(:failed) if (defined? @exception_raised) and (@exception_raised)
|
39
|
-
@exception_raised = false
|
40
|
-
end
|
41
|
-
|
42
|
-
def after_steps(*args)
|
43
|
-
@exception_raised = false
|
44
|
-
end
|
45
|
-
|
46
|
-
def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
|
47
|
-
progress(status)
|
48
|
-
@status = status
|
49
|
-
end
|
50
|
-
|
51
|
-
def before_outline_table(outline_table)
|
52
|
-
@outline_table = outline_table
|
53
|
-
end
|
54
|
-
|
55
|
-
def after_outline_table(outline_table)
|
56
|
-
@outline_table = nil
|
57
|
-
end
|
58
|
-
|
59
|
-
def table_cell_value(value, status)
|
60
|
-
return unless @outline_table
|
61
|
-
status ||= @status
|
62
|
-
progress(status) unless table_header_cell?(status)
|
21
|
+
def before_test_case(_test_case)
|
22
|
+
unless @profile_information_printed
|
23
|
+
print_profile_information
|
24
|
+
@profile_information_printed = true
|
25
|
+
end
|
26
|
+
@previous_step_keyword = nil
|
63
27
|
end
|
64
28
|
|
65
|
-
def
|
66
|
-
|
29
|
+
def after_test_step(test_step, result)
|
30
|
+
progress(result.to_sym) if !HookQueryVisitor.new(test_step).hook? || result.failed?
|
31
|
+
collect_snippet_data(test_step, result) unless HookQueryVisitor.new(test_step).hook?
|
67
32
|
end
|
68
33
|
|
69
|
-
def
|
70
|
-
@
|
34
|
+
def after_test_case(_test_case, result)
|
35
|
+
@total_duration += DurationExtractor.new(result).result_duration
|
71
36
|
end
|
72
37
|
|
73
|
-
def
|
74
|
-
|
38
|
+
def done
|
39
|
+
@io.puts
|
40
|
+
@io.puts
|
41
|
+
print_summary
|
75
42
|
end
|
76
43
|
|
77
44
|
private
|
78
45
|
|
79
|
-
def print_summary
|
46
|
+
def print_summary
|
80
47
|
print_steps(:pending)
|
81
48
|
print_steps(:failed)
|
82
|
-
print_stats(
|
49
|
+
print_stats(@total_duration, @options)
|
83
50
|
print_snippets(@options)
|
84
51
|
print_passing_wip(@options)
|
85
52
|
end
|
@@ -8,10 +8,11 @@ module Cucumber
|
|
8
8
|
def initialize(runtime, path_or_io, options)
|
9
9
|
@io = ensure_io(path_or_io, "rerun")
|
10
10
|
@failures = {}
|
11
|
+
@options = options
|
11
12
|
end
|
12
13
|
|
13
14
|
def after_test_case(test_case, result)
|
14
|
-
return if result.
|
15
|
+
return if result.ok?(@options[:strict])
|
15
16
|
@failures[test_case.location.file] ||= []
|
16
17
|
@failures[test_case.location.file] << test_case.location.line
|
17
18
|
end
|
@@ -14,39 +14,33 @@ module Cucumber
|
|
14
14
|
@runtime = runtime
|
15
15
|
@io = ensure_io(path_or_io, "usage")
|
16
16
|
@options = options
|
17
|
-
@stepdef_to_match = Hash.new{|h,stepdef_key| h[stepdef_key] = []}
|
17
|
+
@stepdef_to_match = Hash.new { |h, stepdef_key| h[stepdef_key] = [] }
|
18
|
+
@total_duration = 0
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def before_step(step)
|
25
|
-
@step = step
|
26
|
-
@start_time = Time.now
|
27
|
-
end
|
21
|
+
def after_test_step(test_step, result)
|
22
|
+
return if HookQueryVisitor.new(test_step).hook?
|
28
23
|
|
29
|
-
|
30
|
-
@duration = Time.now - @start_time
|
31
|
-
end
|
32
|
-
|
33
|
-
def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
|
24
|
+
step_match = @runtime.step_match(test_step.source.last.name)
|
34
25
|
step_definition = step_match.step_definition
|
35
|
-
|
36
|
-
|
26
|
+
stepdef_key = StepDefKey.new(step_definition.regexp_source, step_definition.file_colon_line)
|
27
|
+
unless @stepdef_to_match[stepdef_key].map { |key| key[:file_colon_line] }.include? test_step.location
|
28
|
+
duration = DurationExtractor.new(result).result_duration
|
37
29
|
|
38
30
|
@stepdef_to_match[stepdef_key] << {
|
39
|
-
:
|
40
|
-
:
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
31
|
+
keyword: test_step.source.last.keyword,
|
32
|
+
step_match: step_match,
|
33
|
+
status: result.to_sym,
|
34
|
+
file_colon_line: test_step.location,
|
35
|
+
duration: duration
|
44
36
|
}
|
45
37
|
end
|
46
38
|
super
|
47
39
|
end
|
48
40
|
|
49
|
-
|
41
|
+
private
|
42
|
+
|
43
|
+
def print_summary
|
50
44
|
add_unused_stepdefs
|
51
45
|
aggregate_info
|
52
46
|
|
data/lib/cucumber/hooks.rb
CHANGED
@@ -1,33 +1,42 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'cucumber/core/ast/location'
|
1
3
|
require 'cucumber/core/test/around_hook'
|
2
4
|
|
3
5
|
module Cucumber
|
4
6
|
|
5
|
-
# Hooks quack enough like `Cucumber::Core::Ast` source nodes that we can use them as
|
7
|
+
# Hooks quack enough like `Cucumber::Core::Ast` source nodes that we can use them as
|
6
8
|
# source for test steps
|
7
9
|
module Hooks
|
8
10
|
|
9
11
|
class << self
|
10
|
-
def before_hook(source, &block)
|
11
|
-
build_hook_step(source, block, BeforeHook, Core::Test::UnskippableAction)
|
12
|
+
def before_hook(source, location, &block)
|
13
|
+
build_hook_step(source, location, block, BeforeHook, Core::Test::UnskippableAction)
|
12
14
|
end
|
13
15
|
|
14
|
-
def after_hook(source, &block)
|
15
|
-
build_hook_step(source, block, AfterHook, Core::Test::UnskippableAction)
|
16
|
+
def after_hook(source, location, &block)
|
17
|
+
build_hook_step(source, location, block, AfterHook, Core::Test::UnskippableAction)
|
16
18
|
end
|
17
19
|
|
18
|
-
def after_step_hook(source, &block)
|
20
|
+
def after_step_hook(source, location, &block)
|
19
21
|
raise ArgumentError unless source.last.kind_of?(Core::Ast::Step)
|
20
|
-
build_hook_step(source, block, AfterStepHook, Core::Test::Action)
|
22
|
+
build_hook_step(source, location, block, AfterStepHook, Core::Test::Action)
|
21
23
|
end
|
22
24
|
|
23
25
|
def around_hook(source, &block)
|
24
26
|
Core::Test::AroundHook.new(&block)
|
25
27
|
end
|
26
28
|
|
29
|
+
def location(hook)
|
30
|
+
filepath, line = *hook.source_location
|
31
|
+
Core::Ast::Location.new(
|
32
|
+
Pathname.new(filepath).relative_path_from(Pathname.new(Dir.pwd)).to_path,
|
33
|
+
line)
|
34
|
+
end
|
35
|
+
|
27
36
|
private
|
28
37
|
|
29
|
-
def build_hook_step(source, block, hook_type, action_type)
|
30
|
-
action = action_type.new(&block)
|
38
|
+
def build_hook_step(source, location, block, hook_type, action_type)
|
39
|
+
action = action_type.new(location, &block)
|
31
40
|
hook = hook_type.new(action.location)
|
32
41
|
Core::Test::Step.new(source + [hook], action)
|
33
42
|
end
|
@@ -148,6 +148,22 @@ module Cucumber
|
|
148
148
|
@hashes ||= build_hashes
|
149
149
|
end
|
150
150
|
|
151
|
+
# Converts this table into an Array of Hashes where the keys are symbols.
|
152
|
+
# For example, a Table built from the following plain text:
|
153
|
+
#
|
154
|
+
# | foo | Bar | Foo Bar |
|
155
|
+
# | 2 | 3 | 5 |
|
156
|
+
# | 7 | 9 | 16 |
|
157
|
+
#
|
158
|
+
# Gets converted into the following:
|
159
|
+
#
|
160
|
+
# [{:foo => '2', :bar => '3', :foo_bar => '5'}, {:foo => '7', :bar => '9', :foo_bar => '16'}]
|
161
|
+
#
|
162
|
+
def symbolic_hashes
|
163
|
+
@header_conversion_proc = lambda { |h| symbolize_key(h) }
|
164
|
+
@symbolic_hashes ||= build_hashes
|
165
|
+
end
|
166
|
+
|
151
167
|
# Converts this table into a Hash where the first column is
|
152
168
|
# used as keys and the second column is used as values
|
153
169
|
#
|
@@ -336,7 +352,7 @@ module Cucumber
|
|
336
352
|
convert_columns!
|
337
353
|
|
338
354
|
original_width = cell_matrix[0].length
|
339
|
-
other_table_cell_matrix =
|
355
|
+
@cell_matrix, other_table_cell_matrix = pad_and_match(@cell_matrix, other_table.cell_matrix)
|
340
356
|
padded_width = cell_matrix[0].length
|
341
357
|
|
342
358
|
missing_col = cell_matrix[0].detect{|cell| cell.status == :undefined}
|
@@ -556,44 +572,36 @@ module Cucumber
|
|
556
572
|
@hashes = @rows_hash = @col_names = @rows = @columns = nil
|
557
573
|
end
|
558
574
|
|
559
|
-
# Pads
|
560
|
-
#
|
561
|
-
def
|
575
|
+
# Pads two cell matrices to same column width and matches columns according to header value.
|
576
|
+
# The first cell matrix is the reference matrix with the second matrix matched against it.
|
577
|
+
def pad_and_match(a_cell_matrix, other_cell_matrix) #:nodoc:
|
562
578
|
clear_cache!
|
563
|
-
cols =
|
564
|
-
|
579
|
+
cols = a_cell_matrix.transpose
|
580
|
+
unmatched_cols = other_cell_matrix.transpose
|
565
581
|
|
566
|
-
mapped_cols = []
|
567
582
|
|
568
|
-
cols.
|
569
|
-
|
570
|
-
candidate_cols, unmapped_cols = unmapped_cols.partition do |other_col|
|
571
|
-
other_col[0] == header
|
572
|
-
end
|
573
|
-
raise "More than one column has the header #{header}" if candidate_cols.size > 2
|
583
|
+
header_values = cols.map(&:first)
|
584
|
+
matched_cols = []
|
574
585
|
|
575
|
-
|
576
|
-
|
577
|
-
|
586
|
+
header_values.each_with_index do |v, i|
|
587
|
+
mapped_index = unmatched_cols.index{|unmapped_col| unmapped_col.first == v}
|
588
|
+
if (mapped_index)
|
589
|
+
matched_cols << unmatched_cols.delete_at(mapped_index)
|
578
590
|
else
|
579
|
-
mark_as_missing(cols[
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
end
|
591
|
+
mark_as_missing(cols[i])
|
592
|
+
empty_col = other_cell_matrix.collect {SurplusCell.new(nil, self, -1)}
|
593
|
+
empty_col.first.value = v
|
594
|
+
matched_cols << empty_col
|
584
595
|
end
|
585
|
-
mapped_cols.insert(col_index, other_padded_col)
|
586
596
|
end
|
587
597
|
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
end
|
598
|
+
|
599
|
+
empty_col = cell_matrix.collect {SurplusCell.new(nil, self, -1)}
|
600
|
+
unmatched_cols.each do
|
592
601
|
cols << empty_col
|
593
602
|
end
|
594
603
|
|
595
|
-
|
596
|
-
(mapped_cols + unmapped_cols).transpose
|
604
|
+
return cols.transpose, (matched_cols + unmatched_cols).transpose
|
597
605
|
end
|
598
606
|
|
599
607
|
def ensure_table(table_or_array) #:nodoc:
|
@@ -624,6 +632,10 @@ module Cucumber
|
|
624
632
|
end
|
625
633
|
end
|
626
634
|
|
635
|
+
def symbolize_key(key)
|
636
|
+
key.downcase.tr(' ', '_').to_sym
|
637
|
+
end
|
638
|
+
|
627
639
|
# Represents a row of cells or columns of cells
|
628
640
|
class Cells #:nodoc:
|
629
641
|
include Enumerable
|
data/lib/cucumber/platform.rb
CHANGED
@@ -4,7 +4,7 @@ require 'rbconfig'
|
|
4
4
|
|
5
5
|
module Cucumber
|
6
6
|
unless defined?(Cucumber::VERSION)
|
7
|
-
VERSION = '2.0.
|
7
|
+
VERSION = '2.0.1'
|
8
8
|
BINARY = File.expand_path(File.dirname(__FILE__) + '/../../bin/cucumber')
|
9
9
|
LIBDIR = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
10
10
|
JRUBY = defined?(JRUBY_VERSION)
|
@@ -10,6 +10,10 @@ module Cucumber
|
|
10
10
|
@proc = proc
|
11
11
|
end
|
12
12
|
|
13
|
+
def source_location
|
14
|
+
@proc.source_location
|
15
|
+
end
|
16
|
+
|
13
17
|
def invoke(pseudo_method, arguments, &block)
|
14
18
|
@rb_language.current_world.cucumber_instance_exec(false, pseudo_method, *[arguments, block].compact, &@proc)
|
15
19
|
end
|
@@ -2,7 +2,7 @@ require 'delegate'
|
|
2
2
|
|
3
3
|
module Cucumber
|
4
4
|
# Represents the current status of a running test case.
|
5
|
-
#
|
5
|
+
#
|
6
6
|
# This wraps a `Cucumber::Core::Test::Case` and delegates
|
7
7
|
# many methods to that object.
|
8
8
|
#
|
@@ -15,11 +15,11 @@ module Cucumber
|
|
15
15
|
#
|
16
16
|
# The test case might come from a regular Scenario or
|
17
17
|
# a Scenario outline. You can call the `#outline?`
|
18
|
-
# predicate to find out. If it's from an outline,
|
18
|
+
# predicate to find out. If it's from an outline,
|
19
19
|
# you get a couple of extra methods.
|
20
20
|
module RunningTestCase
|
21
21
|
def self.new(test_case)
|
22
|
-
Builder.new(test_case).
|
22
|
+
Builder.new(test_case).running_test_case
|
23
23
|
end
|
24
24
|
|
25
25
|
class Builder
|
@@ -45,7 +45,7 @@ module Cucumber
|
|
45
45
|
def examples_table_row(row)
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
48
|
+
def running_test_case
|
49
49
|
@factory.new(@test_case)
|
50
50
|
end
|
51
51
|
end
|
@@ -62,6 +62,15 @@ module Cucumber
|
|
62
62
|
hook.tag_expressions.all? { |expression| @test_case.match_tags?(expression) }
|
63
63
|
end
|
64
64
|
|
65
|
+
def exception
|
66
|
+
return unless @result.failed?
|
67
|
+
@result.exception
|
68
|
+
end
|
69
|
+
|
70
|
+
def status
|
71
|
+
@result.to_sym
|
72
|
+
end
|
73
|
+
|
65
74
|
def failed?
|
66
75
|
@result.failed?
|
67
76
|
end
|