cucumber 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|