cucumber 2.3.3 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +48 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +39 -0
  4. data/.travis.yml +4 -5
  5. data/Gemfile +3 -1
  6. data/History.md +18 -2
  7. data/LICENSE +1 -1
  8. data/README.md +5 -2
  9. data/cucumber.gemspec +2 -13
  10. data/examples/i18n/eo/features/adicio.feature +6 -6
  11. data/examples/i18n/eo/features/divido.feature +6 -6
  12. data/examples/i18n/eo/features/step_definitions/calculator_steps.rb +2 -2
  13. data/features/docs/cli/randomize.feature +91 -15
  14. data/features/docs/cli/retry_failing_tests.feature +32 -0
  15. data/features/docs/defining_steps/snippets.feature +1 -1
  16. data/features/docs/formatters/json_formatter.feature +2 -6
  17. data/features/docs/formatters/junit_formatter.feature +47 -1
  18. data/features/docs/writing_support_code/after_step_hooks.feature +53 -0
  19. data/features/docs/{defining_steps → writing_support_code}/transforms.feature +42 -7
  20. data/features/lib/step_definitions/cucumber_steps.rb +28 -0
  21. data/features/lib/step_definitions/retry_steps.rb +35 -0
  22. data/lib/cucumber/cli/configuration.rb +4 -0
  23. data/lib/cucumber/cli/options.rb +7 -2
  24. data/lib/cucumber/cli/rerun_file.rb +1 -1
  25. data/lib/cucumber/configuration.rb +4 -0
  26. data/lib/cucumber/events/finished_testing.rb +9 -0
  27. data/lib/cucumber/filters/randomizer.rb +6 -1
  28. data/lib/cucumber/filters/retry.rb +32 -0
  29. data/lib/cucumber/formatter/cucumber.css +0 -0
  30. data/lib/cucumber/formatter/cucumber.sass +0 -0
  31. data/lib/cucumber/formatter/event_bus_report.rb +1 -0
  32. data/lib/cucumber/formatter/json.rb +18 -8
  33. data/lib/cucumber/formatter/junit.rb +56 -42
  34. data/lib/cucumber/gherkin/data_table_parser.rb +17 -9
  35. data/lib/cucumber/gherkin/steps_parser.rb +13 -21
  36. data/lib/cucumber/platform.rb +1 -0
  37. data/lib/cucumber/rake/task.rb +1 -1
  38. data/lib/cucumber/rb_support/rb_hook.rb +1 -1
  39. data/lib/cucumber/rb_support/snippet.rb +1 -1
  40. data/lib/cucumber/runtime.rb +4 -5
  41. data/lib/cucumber/runtime/for_programming_languages.rb +2 -0
  42. data/lib/cucumber/runtime/step_hooks.rb +1 -1
  43. data/lib/cucumber/step_match.rb +1 -1
  44. data/lib/cucumber/version +1 -1
  45. data/spec/cucumber/cli/configuration_spec.rb +7 -0
  46. data/spec/cucumber/cli/options_spec.rb +14 -0
  47. data/spec/cucumber/cli/rerun_spec.rb +3 -7
  48. data/spec/cucumber/filters/retry_spec.rb +79 -0
  49. data/spec/cucumber/formatter/event_bus_report_spec.rb +9 -0
  50. data/spec/cucumber/formatter/json_spec.rb +1 -1
  51. data/spec/cucumber/formatter/junit_spec.rb +2 -2
  52. data/spec/cucumber/formatter/spec_helper.rb +6 -1
  53. data/spec/cucumber/rake/task_spec.rb +85 -0
  54. data/spec/cucumber/rb_support/snippet_spec.rb +2 -2
  55. metadata +19 -139
File without changes
File without changes
@@ -29,6 +29,7 @@ module Cucumber
29
29
  end
30
30
 
31
31
  def done
32
+ @config.notify Events::FinishedTesting.new
32
33
  end
33
34
  end
34
35
 
@@ -1,5 +1,6 @@
1
1
  require 'multi_json'
2
2
  require 'base64'
3
+ require 'cucumber/formatter/backtrace_filter'
3
4
  require 'cucumber/formatter/io'
4
5
  require 'cucumber/formatter/hook_query_visitor'
5
6
 
@@ -9,13 +10,18 @@ module Cucumber
9
10
  class Json
10
11
  include Io
11
12
 
12
- def initialize(runtime, io, _options)
13
- @runtime = runtime
14
- @io = ensure_io(io)
13
+ def initialize(config)
14
+ config.on_event :before_test_case, &method(:on_before_test_case)
15
+ config.on_event :after_test_case, &method(:on_after_test_case)
16
+ config.on_event :before_test_step, &method(:on_before_test_step)
17
+ config.on_event :after_test_step, &method(:on_after_test_step)
18
+ config.on_event :finished_testing, &method(:on_finished_testing)
19
+ @io = ensure_io(config.out_stream)
15
20
  @feature_hashes = []
16
21
  end
17
22
 
18
- def before_test_case(test_case)
23
+ def on_before_test_case(event)
24
+ test_case = event.test_case
19
25
  builder = Builder.new(test_case)
20
26
  unless same_feature_as_previous_test_case?(test_case.feature)
21
27
  @feature_hash = builder.feature_hash
@@ -32,7 +38,8 @@ module Cucumber
32
38
  @any_step_failed = false
33
39
  end
34
40
 
35
- def before_test_step(test_step)
41
+ def on_before_test_step(event)
42
+ test_step = event.test_step
36
43
  return if internal_hook?(test_step)
37
44
  hook_query = HookQueryVisitor.new(test_step)
38
45
  if hook_query.hook?
@@ -49,17 +56,20 @@ module Cucumber
49
56
  @step_hash = @step_or_hook_hash
50
57
  end
51
58
 
52
- def after_test_step(test_step, result)
59
+ def on_after_test_step(event)
60
+ test_step = event.test_step
61
+ result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
53
62
  return if internal_hook?(test_step)
54
63
  add_match_and_result(test_step, result)
55
64
  @any_step_failed = true if result.failed?
56
65
  end
57
66
 
58
- def after_test_case(test_case, result)
67
+ def on_after_test_case(event)
68
+ result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
59
69
  add_failed_around_hook(result) if result.failed? && !@any_step_failed
60
70
  end
61
71
 
62
- def done
72
+ def on_finished_testing(event)
63
73
  @io.write(MultiJson.dump(@feature_hashes, pretty: true))
64
74
  end
65
75
 
@@ -1,4 +1,5 @@
1
1
  require 'builder'
2
+ require 'cucumber/formatter/backtrace_filter'
2
3
  require 'cucumber/formatter/io'
3
4
  require 'cucumber/formatter/interceptor'
4
5
  require 'fileutils'
@@ -16,14 +17,27 @@ module Cucumber
16
17
  end
17
18
  end
18
19
 
19
- def initialize(_runtime, io, options)
20
- @reportdir = ensure_dir(io, "junit")
21
- @options = options
22
- end
23
-
24
- def before_test_case(test_case)
20
+ def initialize(config)
21
+ config.on_event :before_test_case, &method(:on_before_test_case)
22
+ config.on_event :after_test_case, &method(:on_after_test_case)
23
+ config.on_event :after_test_step, &method(:on_after_test_step)
24
+ config.on_event :finished_testing, &method(:on_finished_testing)
25
+ @reportdir = ensure_dir(config.out_stream, "junit")
26
+ @config = config
27
+ @features_data = Hash.new { |h,k| h[k] = {
28
+ feature: nil,
29
+ failures: 0,
30
+ errors: 0,
31
+ tests: 0,
32
+ skipped: 0,
33
+ time: 0,
34
+ builder: Builder::XmlMarkup.new(:indent => 2)
35
+ }}
36
+ end
37
+
38
+ def on_before_test_case(event)
39
+ test_case = event.test_case
25
40
  unless same_feature_as_previous_test_case?(test_case.feature)
26
- end_feature if @current_feature
27
41
  start_feature(test_case.feature)
28
42
  end
29
43
  @failing_step_source = nil
@@ -33,13 +47,15 @@ module Cucumber
33
47
  @interceptederr = Interceptor::Pipe.wrap(:stderr)
34
48
  end
35
49
 
36
- def after_test_step(test_step, result)
50
+ def on_after_test_step(event)
37
51
  return if @failing_step_source
38
52
 
39
- @failing_step_source = test_step.source.last unless result.ok?(@options[:strict])
53
+ @failing_step_source = event.test_step.source.last unless event.result.ok?(@config.strict?)
40
54
  end
41
55
 
42
- def after_test_case(test_case, result)
56
+ def on_after_test_case(event)
57
+ test_case = event.test_case
58
+ result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
43
59
  test_case_name = NameBuilder.new(test_case)
44
60
  scenario = test_case_name.scenario_name
45
61
  scenario_designation = "#{scenario}#{test_case_name.name_suffix}"
@@ -50,43 +66,41 @@ module Cucumber
50
66
  Interceptor::Pipe.unwrap! :stderr
51
67
  end
52
68
 
53
- def done
54
- end_feature if @current_feature
69
+ def on_finished_testing(event)
70
+ @features_data.each { |file, data| end_feature(data) }
55
71
  end
56
72
 
57
73
  private
58
74
 
59
75
  def same_feature_as_previous_test_case?(feature)
60
- @current_feature && @current_feature.file == feature.file && @current_feature.location == feature.location
76
+ @current_feature_data && @current_feature_data[:feature].file == feature.file && @current_feature_data[:feature].location == feature.location
61
77
  end
62
78
 
63
79
  def start_feature(feature)
64
80
  raise UnNamedFeatureError.new(feature.file) if feature.name.empty?
65
- @current_feature = feature
66
- @failures = @errors = @tests = @skipped = 0
67
- @builder = Builder::XmlMarkup.new(:indent => 2)
68
- @time = 0
81
+ @current_feature_data = @features_data[feature.file]
82
+ @current_feature_data[:feature] = feature unless @current_feature_data[:feature]
69
83
  end
70
84
 
71
- def end_feature
85
+ def end_feature(feature_data)
72
86
  @testsuite = Builder::XmlMarkup.new(:indent => 2)
73
87
  @testsuite.instruct!
74
88
  @testsuite.testsuite(
75
- :failures => @failures,
76
- :errors => @errors,
77
- :skipped => @skipped,
78
- :tests => @tests,
79
- :time => "%.6f" % @time,
80
- :name => @current_feature.name ) do
81
- @testsuite << @builder.target!
89
+ :failures => feature_data[:failures],
90
+ :errors => feature_data[:errors],
91
+ :skipped => feature_data[:skipped],
92
+ :tests => feature_data[:tests],
93
+ :time => "%.6f" % feature_data[:time],
94
+ :name => feature_data[:feature].name ) do
95
+ @testsuite << feature_data[:builder].target!
82
96
  end
83
97
 
84
- write_file(feature_result_filename(@current_feature.file), @testsuite.target!)
98
+ write_file(feature_result_filename(feature_data[:feature].file), @testsuite.target!)
85
99
  end
86
100
 
87
101
  def create_output_string(test_case, scenario, result, row_name)
88
102
  output = "#{test_case.keyword}: #{scenario}\n\n"
89
- return output if result.ok?(@options[:strict])
103
+ return output if result.ok?(@config.strict?)
90
104
  if test_case.keyword == "Scenario"
91
105
  output += "#{@failing_step_source.keyword}" unless hook?(@failing_step_source)
92
106
  output += "#{@failing_step_source.name}\n"
@@ -102,31 +116,31 @@ module Cucumber
102
116
 
103
117
  def build_testcase(result, scenario_designation, output)
104
118
  duration = ResultBuilder.new(result).test_case_duration
105
- @time += duration
106
- classname = @current_feature.name
119
+ @current_feature_data[:time] += duration
120
+ classname = @current_feature_data[:feature].name
107
121
  name = scenario_designation
108
122
 
109
- @builder.testcase(:classname => classname, :name => name, :time => "%.6f" % duration) do
110
- if !result.passed? && result.ok?(@options[:strict])
111
- @builder.skipped
112
- @skipped += 1
123
+ @current_feature_data[:builder].testcase(:classname => classname, :name => name, :time => "%.6f" % duration) do
124
+ if !result.passed? && result.ok?(@config.strict?)
125
+ @current_feature_data[:builder].skipped
126
+ @current_feature_data[:skipped] += 1
113
127
  elsif !result.passed?
114
128
  status = result.to_sym
115
129
  exception = get_backtrace_object(result)
116
- @builder.failure(:message => "#{status} #{name}", :type => status) do
117
- @builder.cdata! output
118
- @builder.cdata!(format_exception(exception)) if exception
130
+ @current_feature_data[:builder].failure(:message => "#{status} #{name}", :type => status) do
131
+ @current_feature_data[:builder].cdata! output
132
+ @current_feature_data[:builder].cdata!(format_exception(exception)) if exception
119
133
  end
120
- @failures += 1
134
+ @current_feature_data[:failures] += 1
121
135
  end
122
- @builder.tag!('system-out') do
123
- @builder.cdata! strip_control_chars(@interceptedout.buffer.join)
136
+ @current_feature_data[:builder].tag!('system-out') do
137
+ @current_feature_data[:builder].cdata! strip_control_chars(@interceptedout.buffer.join)
124
138
  end
125
- @builder.tag!('system-err') do
126
- @builder.cdata! strip_control_chars(@interceptederr.buffer.join)
139
+ @current_feature_data[:builder].tag!('system-err') do
140
+ @current_feature_data[:builder].cdata! strip_control_chars(@interceptederr.buffer.join)
127
141
  end
128
142
  end
129
- @tests += 1
143
+ @current_feature_data[:tests] += 1
130
144
  end
131
145
 
132
146
  def get_backtrace_object(result)
@@ -1,5 +1,6 @@
1
1
  require 'gherkin/token_scanner'
2
- require 'gherkin/token_matcher'
2
+ require 'gherkin/parser'
3
+ require 'gherkin/dialect'
3
4
 
4
5
  module Cucumber
5
6
  module Gherkin
@@ -7,17 +8,24 @@ module Cucumber
7
8
  def initialize(builder)
8
9
  @builder = builder
9
10
  end
11
+
10
12
  def parse(text)
11
- scanner = ::Gherkin::TokenScanner.new(text)
12
- matcher = ::Gherkin::TokenMatcher.new
13
- token = scanner.read
14
- until matcher.match_EOF(token) do
15
- if matcher.match_TableRow(token)
16
- @builder.row(token.matched_items.map { |cell_item| cell_item.text })
17
- end
18
- token = scanner.read
13
+ token_scanner = ::Gherkin::TokenScanner.new(feature_header + text)
14
+ parser = ::Gherkin::Parser.new
15
+ gherkin_document = parser.parse(token_scanner)
16
+
17
+ gherkin_document[:feature][:children][0][:steps][0][:argument][:rows].each do |row|
18
+ @builder.row(row[:cells].map { |cell| cell[:value] })
19
19
  end
20
20
  end
21
+
22
+ def feature_header
23
+ dialect = ::Gherkin::Dialect.for('en')
24
+ %(#{dialect.feature_keywords[0]}:
25
+ #{dialect.scenario_keywords[0]}:
26
+ #{dialect.given_keywords[0]} x
27
+ )
28
+ end
21
29
  end
22
30
  end
23
31
  end
@@ -1,7 +1,7 @@
1
1
  require 'gherkin/token_scanner'
2
2
  require 'gherkin/token_matcher'
3
- require 'gherkin/ast_builder'
4
3
  require 'gherkin/parser'
4
+ require 'gherkin/dialect'
5
5
 
6
6
  module Cucumber
7
7
  module Gherkin
@@ -10,29 +10,21 @@ module Cucumber
10
10
  @builder = builder
11
11
  @language = language
12
12
  end
13
- def parse(text)
14
- ast_builder = ::Gherkin::AstBuilder.new
15
- context = ::Gherkin::ParserContext.new(
16
- ::Gherkin::TokenScanner.new(text),
17
- ::Gherkin::TokenMatcher.new(@language),
18
- [],
19
- []
20
- )
21
- parser = ::Gherkin::Parser.new(ast_builder)
22
13
 
23
- parser.start_rule(context, :ScenarioDefinition)
24
- parser.start_rule(context, :Scenario)
25
- scenario = ast_builder.current_node
26
- state = 12
27
- token = nil
28
- begin
29
- token = parser.read_token(context)
30
- state = parser.match_token(state, token, context)
31
- end until(token.eof?)
14
+ def parse(text)
15
+ dialect = ::Gherkin::Dialect.for(@language)
16
+ token_matcher = ::Gherkin::TokenMatcher.new(@language)
17
+ token_scanner = ::Gherkin::TokenScanner.new(feature_header(dialect) + text)
18
+ parser = ::Gherkin::Parser.new
19
+ gherkin_document = parser.parse(token_scanner, token_matcher)
32
20
 
33
- raise CompositeParserException.new(context.errors) if context.errors.any?
21
+ @builder.steps(gherkin_document[:feature][:children][0][:steps])
22
+ end
34
23
 
35
- @builder.steps(ast_builder.get_steps(scenario))
24
+ def feature_header(dialect)
25
+ %(#{dialect.feature_keywords[0]}:
26
+ #{dialect.scenario_keywords[0]}:
27
+ )
36
28
  end
37
29
  end
38
30
  end
@@ -12,6 +12,7 @@ module Cucumber
12
12
  RUBY_BINARY = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
13
13
  RUBY_2_2 = RUBY_VERSION =~ /^2\.2/
14
14
  RUBY_2_1 = RUBY_VERSION =~ /^2\.1/
15
+ RUBY_2_3 = RUBY_VERSION =~ /^2\.3/
15
16
 
16
17
  class << self
17
18
  attr_accessor :use_full_backtrace
@@ -153,7 +153,7 @@ module Cucumber
153
153
  end
154
154
 
155
155
  def cucumber_opts_with_profile #:nodoc:
156
- @profile ? [cucumber_opts, '--profile', @profile] : cucumber_opts
156
+ Array(cucumber_opts).concat Array(@profile).flat_map {|p| ["--profile", p] }
157
157
  end
158
158
 
159
159
  def feature_files #:nodoc:
@@ -12,7 +12,7 @@ module Cucumber
12
12
  end
13
13
 
14
14
  def invoke(pseudo_method, arguments, &block)
15
- @rb_language.current_world.cucumber_instance_exec(false, pseudo_method, *[arguments, block].compact, &@proc)
15
+ @rb_language.current_world.cucumber_instance_exec(false, pseudo_method, *[arguments, block].flatten.compact, &@proc)
16
16
  end
17
17
  end
18
18
  end
@@ -153,7 +153,7 @@ module Cucumber
153
153
  end
154
154
 
155
155
  def append_comment_to(string)
156
- string << " # table is a #{@table.class.to_s}\n"
156
+ string << " # table is a #{Cucumber::MultilineArgument::DataTable.to_s}\n"
157
157
  end
158
158
  end
159
159
 
@@ -1,7 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require 'fileutils'
3
3
  require 'multi_json'
4
- require 'multi_test'
5
4
  require 'cucumber/configuration'
6
5
  require 'cucumber/load_path'
7
6
  require 'cucumber/formatter/duration'
@@ -32,7 +31,7 @@ module Cucumber
32
31
  end
33
32
 
34
33
  class FeatureFolderNotFoundException < FileException
35
- include FixRuby21Bug9285 if Cucumber::RUBY_2_1 || Cucumber::RUBY_2_2
34
+ include FixRuby21Bug9285 if Cucumber::RUBY_2_1 || Cucumber::RUBY_2_2 || Cucumber::RUBY_2_3
36
35
  end
37
36
 
38
37
  require 'cucumber/core'
@@ -195,8 +194,8 @@ module Cucumber
195
194
 
196
195
  def create_formatter(factory, path_or_io, options)
197
196
  if !legacy_formatter?(factory)
198
- out_stream = Cucumber::Formatter::Io.ensure_io(path_or_io)
199
- return factory.new(@configuration.with_options(out_stream: out_stream))
197
+ return factory.new(@configuration) if path_or_io.nil?
198
+ return factory.new(@configuration.with_options(out_stream: path_or_io))
200
199
  end
201
200
  results = Formatter::LegacyApi::Results.new
202
201
  runtime_facade = Formatter::LegacyApi::RuntimeFacade.new(results, @support_code, @configuration)
@@ -226,11 +225,11 @@ module Cucumber
226
225
  name_regexps = @configuration.name_regexps
227
226
  tag_limits = @configuration.tag_limits
228
227
  [].tap do |filters|
229
- filters << Filters::Randomizer.new(@configuration.seed) if @configuration.randomize?
230
228
  filters << Filters::TagLimits.new(tag_limits) if tag_limits.any?
231
229
  filters << Cucumber::Core::Test::TagFilter.new(tag_expressions)
232
230
  filters << Cucumber::Core::Test::NameFilter.new(name_regexps)
233
231
  filters << Cucumber::Core::Test::LocationsFilter.new(filespecs.locations)
232
+ filters << Filters::Randomizer.new(@configuration.seed) if @configuration.randomize?
234
233
  filters << Filters::Quit.new
235
234
  # TODO: can we just use RbLanguages's step definitions directly?
236
235
  step_match_search = StepMatchSearch.new(@support_code.ruby.method(:step_matches), @configuration)
@@ -11,6 +11,8 @@ module Cucumber
11
11
  class ForProgrammingLanguages
12
12
  extend Forwardable
13
13
 
14
+ attr_reader :support_code
15
+
14
16
  def initialize(support_code, user_interface)
15
17
  @support_code, @user_interface = support_code, user_interface
16
18
  end
@@ -14,7 +14,7 @@ module Cucumber
14
14
  private
15
15
  def after_step_hooks(test_step)
16
16
  @hooks.map do |hook|
17
- action = ->(*args) { hook.invoke('AfterStep', args) }
17
+ action = ->(*args) { hook.invoke('AfterStep', [args, test_step]) }
18
18
  Hooks.after_step_hook(test_step.source, hook.location, &action)
19
19
  end
20
20
  end
@@ -2,7 +2,7 @@ require 'cucumber/multiline_argument'
2
2
 
3
3
  module Cucumber
4
4
 
5
- # Represents the match found between a Test Step and it's activation
5
+ # Represents the match found between a Test Step and its activation
6
6
  class StepMatch #:nodoc:
7
7
  attr_reader :step_definition, :step_arguments
8
8
 
@@ -1 +1 @@
1
- 2.3.3
1
+ 2.4.0
@@ -423,6 +423,13 @@ END_OF_MESSAGE
423
423
  expect(config.snippet_type).to eq :regexp
424
424
  end
425
425
  end
426
+
427
+ describe "#retry_attempts" do
428
+ it "returns the specified number of retries" do
429
+ config.parse!(['--retry=3'])
430
+ expect(config.retry_attempts).to eql 3
431
+ end
432
+ end
426
433
  end
427
434
  end
428
435
  end