cucumber 2.0.0.rc.4 → 2.0.0.rc.5

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +74 -52
  3. data/examples/i18n/ht/Rakefile +6 -0
  4. data/examples/i18n/ht/features/adisyon.feature +17 -0
  5. data/examples/i18n/ht/features/divizyon.feature +10 -0
  6. data/examples/i18n/ht/features/step_definitions/kalkilatris_steps.rb +25 -0
  7. data/examples/i18n/ht/lib/kalkilatris.rb +14 -0
  8. data/features/docs/cli/dry_run.feature +43 -0
  9. data/features/docs/cli/strict_mode.feature +24 -1
  10. data/features/docs/defining_steps/nested_steps.feature +49 -0
  11. data/features/docs/exception_in_after_step_hook.feature +1 -1
  12. data/features/docs/exception_in_around_hook.feature +80 -0
  13. data/features/docs/formatters/json_formatter.feature +65 -1
  14. data/features/docs/formatters/junit_formatter.feature +40 -0
  15. data/features/docs/{wire_protocol_erb.feature → wire_protocol/erb_configuration.feature} +2 -2
  16. data/features/docs/wire_protocol/handle_unexpected_response.feature +30 -0
  17. data/features/docs/wire_protocol/invoke_message.feature +216 -0
  18. data/features/docs/wire_protocol/readme.md +26 -0
  19. data/features/docs/wire_protocol/snippets_message.feature +51 -0
  20. data/features/docs/wire_protocol/step_matches_message.feature +81 -0
  21. data/features/docs/{wire_protocol_table_diffing.feature → wire_protocol/table_diffing.feature} +1 -0
  22. data/features/docs/{wire_protocol_tags.feature → wire_protocol/tags.feature} +1 -0
  23. data/features/docs/{wire_protocol_timeouts.feature → wire_protocol/timeouts.feature} +1 -0
  24. data/features/docs/work_in_progress.feature +1 -1
  25. data/features/docs/writing_support_code/around_hooks.feature +31 -0
  26. data/features/docs/writing_support_code/before_hook.feature +7 -3
  27. data/features/docs/writing_support_code/tagged_hooks.feature +44 -6
  28. data/features/lib/step_definitions/wire_steps.rb +18 -1
  29. data/features/lib/support/fake_wire_server.rb +10 -7
  30. data/lib/cucumber.rb +1 -3
  31. data/lib/cucumber/cli/options.rb +7 -0
  32. data/lib/cucumber/encoding.rb +5 -0
  33. data/lib/cucumber/errors.rb +8 -0
  34. data/lib/cucumber/filters/prepare_world.rb +13 -5
  35. data/lib/cucumber/formatter/console.rb +1 -1
  36. data/lib/cucumber/formatter/gherkin_formatter_adapter.rb +9 -6
  37. data/lib/cucumber/formatter/junit.rb +95 -0
  38. data/lib/cucumber/formatter/legacy_api/adapter.rb +39 -3
  39. data/lib/cucumber/platform.rb +1 -1
  40. data/lib/cucumber/project_initializer.rb +43 -0
  41. data/lib/cucumber/rb_support/rb_step_definition.rb +11 -2
  42. data/lib/cucumber/rb_support/rb_world.rb +2 -2
  43. data/lib/cucumber/rb_support/snippet.rb +1 -1
  44. data/lib/cucumber/rspec/doubles.rb +1 -1
  45. data/lib/cucumber/running_test_case.rb +115 -0
  46. data/lib/cucumber/runtime.rb +5 -1
  47. data/lib/cucumber/runtime/for_programming_languages.rb +2 -2
  48. data/lib/cucumber/runtime/support_code.rb +18 -8
  49. data/spec/cucumber/formatter/junit_spec.rb +212 -156
  50. data/spec/cucumber/formatter/legacy_api/adapter_spec.rb +89 -1
  51. data/spec/cucumber/formatter/pretty_spec.rb +13 -0
  52. data/spec/cucumber/project_initializer_spec.rb +87 -0
  53. data/spec/cucumber/rb_support/rb_step_definition_spec.rb +21 -3
  54. data/spec/cucumber/rb_support/snippet_spec.rb +6 -6
  55. data/spec/cucumber/running_test_case_spec.rb +83 -0
  56. data/spec/spec_helper.rb +1 -4
  57. metadata +35 -18
  58. data/bin/cuke +0 -60
  59. data/features/docs/report_called_undefined_steps.feature +0 -57
  60. data/features/docs/wire_protocol.feature +0 -337
  61. data/lib/cucumber/ast/facade.rb +0 -117
@@ -0,0 +1,5 @@
1
+ # See https://github.com/cucumber/cucumber/issues/693
2
+ if defined? Encoding
3
+ Encoding.default_external = 'utf-8'
4
+ Encoding.default_internal = 'utf-8'
5
+ end
@@ -20,6 +20,14 @@ module Cucumber
20
20
  end
21
21
  end
22
22
 
23
+ # Raised when there is no matching StepDefinition for a step called
24
+ # from within another step definition.
25
+ class UndefinedDynamicStep < StandardError
26
+ def initialize(step_name)
27
+ super %(Undefined dynamic step: "#{step_name}")
28
+ end
29
+ end
30
+
23
31
  # Raised when a StepDefinition's block invokes World#pending
24
32
  class Pending < Core::Test::Result::Pending
25
33
  end
@@ -1,5 +1,5 @@
1
1
  require 'cucumber/core/filter'
2
- require 'cucumber/ast/facade'
2
+ require 'cucumber/running_test_case'
3
3
  require 'cucumber/hooks'
4
4
 
5
5
  module Cucumber
@@ -17,17 +17,25 @@ module Cucumber
17
17
  end
18
18
 
19
19
  def test_case
20
- init_scenario = Cucumber::Hooks.before_hook(@original_test_case.source) do
20
+ init_scenario = Cucumber::Hooks.around_hook(@original_test_case.source) do |continue|
21
21
  @runtime.begin_scenario(scenario)
22
+ continue.call
23
+ @runtime.end_scenario(scenario)
22
24
  end
23
- steps = [init_scenario] + @original_test_case.test_steps
24
- @original_test_case.with_steps(steps)
25
+ around_hooks = [init_scenario] + @original_test_case.around_hooks
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
30
+ steps = [default_hook] + @original_test_case.test_steps
31
+
32
+ @original_test_case.with_around_hooks(around_hooks).with_steps(steps)
25
33
  end
26
34
 
27
35
  private
28
36
 
29
37
  def scenario
30
- @scenario ||= Ast::Facade.new(test_case).build_scenario
38
+ @scenario ||= RunningTestCase.new(test_case)
31
39
  end
32
40
  end
33
41
 
@@ -118,7 +118,7 @@ module Cucumber
118
118
  end
119
119
 
120
120
  def print_exception(e, status, indent)
121
- message = "#{e.message} (#{e.class})"
121
+ message = "#{e.message} (#{e.class})".force_encoding("UTF-8")
122
122
  if ENV['CUCUMBER_TRUNCATE_OUTPUT']
123
123
  message = linebreaks(message, ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
124
124
  end
@@ -21,12 +21,10 @@ module Cucumber
21
21
 
22
22
  def before_background(background)
23
23
  @outline = false
24
- @before_steps = true
25
24
  @gf.background(background.gherkin_statement)
26
25
  end
27
26
 
28
27
  def before_feature_element(feature_element)
29
- @before_steps = true
30
28
  case(feature_element)
31
29
  when Core::Ast::Scenario
32
30
  @outline = false
@@ -44,6 +42,10 @@ module Cucumber
44
42
  end
45
43
  end
46
44
 
45
+ def before_test_case(test_case)
46
+ @delay_output = true
47
+ end
48
+
47
49
  def scenario_name(keyword, name, file_colon_line, source_indent)
48
50
  if @outline and @options[:expand]
49
51
  return if not @in_instantiated_scenario
@@ -70,10 +72,11 @@ module Cucumber
70
72
  unless @outline and @options[:expand]
71
73
  @gf.step(step.gherkin_statement)
72
74
  pass_delayed_output
73
- @before_steps = false
75
+ @delay_output = false
74
76
  else
75
77
  if @in_instantiated_scenario
76
78
  @current_step_hash = to_hash(step.gherkin_statement)
79
+ @delay_output = true
77
80
  end
78
81
  end
79
82
  if @print_empty_match
@@ -120,7 +123,7 @@ module Cucumber
120
123
  @current_step_hash['rows'],
121
124
  @current_step_hash['doc_string']))
122
125
  pass_delayed_output
123
- @before_steps = false
126
+ @delay_output = false
124
127
  @gf.match(@current_match)
125
128
  @gf.result(@current_result)
126
129
  end
@@ -165,7 +168,7 @@ module Cucumber
165
168
  if defined?(JRUBY_VERSION)
166
169
  data = data.to_java_bytes
167
170
  end
168
- unless @before_steps
171
+ unless @delay_output
169
172
  @gf.embedding(mime_type, data)
170
173
  else
171
174
  @delayed_embeddings.push [mime_type, data]
@@ -173,7 +176,7 @@ module Cucumber
173
176
  end
174
177
 
175
178
  def puts(message)
176
- unless @before_steps
179
+ unless @delay_output
177
180
  @gf.write(message)
178
181
  else
179
182
  @delayed_messages.push message
@@ -129,6 +129,34 @@ module Cucumber
129
129
  @header_row = false if @header_row
130
130
  end
131
131
 
132
+ def before_test_case(test_case)
133
+ if @options[:expand] and test_case.keyword == "Scenario Outline"
134
+ @exception = nil
135
+ end
136
+ end
137
+
138
+ def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
139
+ if @options[:expand] and @in_examples
140
+ if not @exception and exception
141
+ @exception = exception
142
+ end
143
+ end
144
+ end
145
+
146
+ def after_test_case(test_case, result)
147
+ if @options[:expand] and test_case.keyword == "Scenario Outline"
148
+ test_case_name = NameBuilder.new(test_case)
149
+ @scenario = test_case_name.outline_name
150
+ @output = "#{test_case.keyword}: #{@scenario}\n\n"
151
+ if result.failed?
152
+ @output += "Example row: #{test_case_name.row_name}\n"
153
+ @output += "\nMessage:\n"
154
+ end
155
+ test_case_result = ResultBuilder.new(result)
156
+ build_testcase(test_case_result.test_case_duration, test_case_result.status, @exception, test_case_name.name_suffix)
157
+ end
158
+ end
159
+
132
160
  private
133
161
 
134
162
  def build_testcase(duration, status, exception = nil, suffix = "")
@@ -176,5 +204,72 @@ module Cucumber
176
204
  end
177
205
 
178
206
  end
207
+
208
+ class NameBuilder
209
+ attr_reader :outline_name, :name_suffix, :row_name
210
+
211
+ def initialize(test_case)
212
+ test_case.describe_source_to self
213
+ end
214
+
215
+ def feature(*)
216
+ self
217
+ end
218
+
219
+ def scenario(*)
220
+ self
221
+ end
222
+
223
+ def scenario_outline(outline)
224
+ @outline_name = outline.name
225
+ self
226
+ end
227
+
228
+ def examples_table(*)
229
+ self
230
+ end
231
+
232
+ def examples_table_row(row)
233
+ @row_name = '| ' + row.values.join(' | ') + ' |'
234
+ @name_suffix = " (outline example : #{@row_name})"
235
+ self
236
+ end
237
+ end
238
+
239
+ class ResultBuilder
240
+ attr_reader :status, :test_case_duration
241
+ def initialize(result)
242
+ @test_case_duration = 0
243
+ result.describe_to(self)
244
+ end
245
+
246
+ def passed
247
+ @status = :passed
248
+ end
249
+
250
+ def failed
251
+ @status = :failed
252
+ end
253
+
254
+ def undefined
255
+ @status = :undefined
256
+ end
257
+
258
+ def skipped
259
+ @status = :skipped
260
+ end
261
+
262
+ def pending(*)
263
+ @status = :pending
264
+ end
265
+
266
+ def exception(*)
267
+ end
268
+
269
+ def duration(duration, *)
270
+ duration.tap { |duration| @test_case_duration = duration.nanoseconds / 10 ** 9.0 }
271
+ end
272
+ end
273
+
179
274
  end
180
275
  end
@@ -126,6 +126,30 @@ module Cucumber
126
126
  end
127
127
  end
128
128
 
129
+ module TestCaseSource
130
+ def self.for(test_case, result)
131
+ collector = Collector.new
132
+ test_case.describe_source_to collector, result
133
+ collector.result.freeze
134
+ end
135
+
136
+ class Collector
137
+ attr_reader :result
138
+
139
+ def initialize
140
+ @result = CaseSource.new
141
+ end
142
+
143
+ def method_missing(name, node, test_case_result, *args)
144
+ result.send "#{name}=", node
145
+ end
146
+ end
147
+
148
+ require 'ostruct'
149
+ class CaseSource < OpenStruct
150
+ end
151
+ end
152
+
129
153
  module TestStepSource
130
154
  def self.for(test_step, result)
131
155
  collector = Collector.new
@@ -194,6 +218,7 @@ module Cucumber
194
218
 
195
219
  def before_test_case(test_case)
196
220
  @before_hook_results = Ast::HookResultCollection.new
221
+ @test_step_results = []
197
222
  end
198
223
 
199
224
  def before_test_step(test_step)
@@ -204,13 +229,20 @@ module Cucumber
204
229
  # TODO: stop calling self, and describe source to another object
205
230
  test_step.describe_source_to(self, result)
206
231
  print_step
232
+ @test_step_results << result
207
233
  end
208
234
 
209
- def after_test_case(*args)
235
+ def after_test_case(test_case, test_case_result)
210
236
  if current_test_step_source && current_test_step_source.step_result.nil?
211
237
  switch_step_container
212
238
  end
213
239
 
240
+ if test_case_result.failed? && !any_test_steps_failed?
241
+ # around hook must have failed. Print the error.
242
+ switch_step_container(TestCaseSource.for(test_case, test_case_result))
243
+ LegacyResultBuilder.new(test_case_result).describe_exception_to formatter
244
+ end
245
+
214
246
  # messages and embedding should already have been handled, but just in case...
215
247
  @delayed_messages.each { |message| formatter.puts(message) }
216
248
  @delayed_embeddings.each { |embedding| embedding.send_to_formatter(formatter) }
@@ -276,8 +308,12 @@ module Cucumber
276
308
  attr_reader :before_hook_results
277
309
  private :before_hook_results
278
310
 
279
- def switch_step_container
280
- switch_to_child select_step_container(current_test_step_source), current_test_step_source
311
+ def any_test_steps_failed?
312
+ @test_step_results.any? &:failed?
313
+ end
314
+
315
+ def switch_step_container(source = current_test_step_source)
316
+ switch_to_child select_step_container(source), source
281
317
  end
282
318
 
283
319
  def select_step_container(source)
@@ -4,7 +4,7 @@ require 'rbconfig'
4
4
 
5
5
  module Cucumber
6
6
  unless defined?(Cucumber::VERSION)
7
- VERSION = '2.0.0.rc.4'
7
+ VERSION = '2.0.0.rc.5'
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)
@@ -0,0 +1,43 @@
1
+ module Cucumber
2
+
3
+ # Generates generic file structure for a cucumber project
4
+ class ProjectInitializer
5
+ def run
6
+ create_directory('features')
7
+ create_directory('features/step_definitions')
8
+ create_directory('features/support')
9
+ create_file('features/support/env.rb')
10
+ end
11
+
12
+ private
13
+
14
+ def create_directory(dir_name)
15
+ create_directory_or_file dir_name, true
16
+ end
17
+
18
+ def create_file(file_name)
19
+ create_directory_or_file file_name, false
20
+ end
21
+
22
+ def create_directory_or_file(file_name, directory)
23
+ file_type = if directory
24
+ :mkdir_p
25
+ else
26
+ :touch
27
+ end
28
+
29
+ report_exists(file_name) || return if File.exists?(file_name)
30
+
31
+ report_creating(file_name)
32
+ FileUtils.send file_type, file_name
33
+ end
34
+
35
+ def report_exists(file)
36
+ puts " exist #{file}"
37
+ end
38
+
39
+ def report_creating(file)
40
+ puts " create #{file}"
41
+ end
42
+ end
43
+ end
@@ -44,10 +44,19 @@ module Cucumber
44
44
  raise ArgumentError unless proc_or_sym.is_a?(Symbol)
45
45
  message = proc_or_sym
46
46
  target_proc = parse_target_proc_from(options)
47
- lambda do |*args|
47
+ patch_location_onto lambda { |*args|
48
48
  target = instance_exec(&target_proc)
49
49
  target.send(message, *args)
50
- end
50
+ }
51
+ end
52
+
53
+ def patch_location_onto(block)
54
+ file, line = caller[5].split(':')[0..1]
55
+ location = Core::Ast::Location.new(
56
+ Pathname.new(file).relative_path_from(Pathname.new(Dir.pwd)),
57
+ line)
58
+ block.define_singleton_method(:file_colon_line) { location.to_s }
59
+ block
51
60
  end
52
61
 
53
62
  def parse_target_proc_from(options)
@@ -37,7 +37,7 @@ module Cucumber
37
37
  # @param [String,Cucumber::Ast::DocString,Cucumber::Ast::Table] multiline_argument
38
38
  def step(name, raw_multiline_arg=nil)
39
39
  location = Core::Ast::Location.of_caller
40
- @__cucumber_runtime.invoke(name, MultilineArgument.from(raw_multiline_arg, location))
40
+ @__cucumber_runtime.invoke_dynamic_step(name, MultilineArgument.from(raw_multiline_arg, location))
41
41
  end
42
42
 
43
43
  # Run a snippet of Gherkin
@@ -48,7 +48,7 @@ module Cucumber
48
48
  # }
49
49
  # @param [String] steps_text The Gherkin snippet to run
50
50
  def steps(steps_text)
51
- @__cucumber_runtime.invoke_steps(steps_text, @__natural_language, caller[0])
51
+ @__cucumber_runtime.invoke_dynamic_steps(steps_text, @__natural_language, caller[0])
52
52
  end
53
53
 
54
54
  # Parse Gherkin into a {Cucumber::Ast::Table} object.
@@ -2,7 +2,7 @@ module Cucumber
2
2
  module RbSupport
3
3
  module Snippet
4
4
 
5
- ARGUMENT_PATTERNS = ['"(.*?)"', '(\d+)']
5
+ ARGUMENT_PATTERNS = ['"([^"]*)"', '(\d+)']
6
6
 
7
7
  class BaseSnippet
8
8
 
@@ -3,7 +3,7 @@ require 'rspec/mocks'
3
3
  World(RSpec::Mocks::ExampleMethods)
4
4
 
5
5
  Before do
6
- if RSpec::Mocks::Version::STRING.split('.').first.to_i > 2
6
+ if RSpec::Mocks::Version::STRING >= "2.9.9"
7
7
  RSpec::Mocks.setup
8
8
  else
9
9
  RSpec::Mocks.setup(self)
@@ -0,0 +1,115 @@
1
+ require 'delegate'
2
+
3
+ module Cucumber
4
+ # Represents the current status of a running test case.
5
+ #
6
+ # This wraps a `Cucumber::Core::Test::Case` and delegates
7
+ # many methods to that object.
8
+ #
9
+ # We decorete the core object with the current result.
10
+ # In the first Before hook of a scenario, this will be an
11
+ # instance of `Cucumber::Core::Test::Result::Unknown`
12
+ # but as the scenario runs, it will be updated to reflect
13
+ # the passed / failed / undefined / skipped status of
14
+ # the test case.
15
+ #
16
+ # The test case might come from a regular Scenario or
17
+ # a Scenario outline. You can call the `#outline?`
18
+ # predicate to find out. If it's from an outline,
19
+ # you get a couple of extra methods.
20
+ module RunningTestCase
21
+ def self.new(test_case)
22
+ Builder.new(test_case).result
23
+ end
24
+
25
+ class Builder
26
+ def initialize(test_case)
27
+ @test_case = test_case
28
+ test_case.describe_source_to(self)
29
+ end
30
+
31
+ def feature(feature)
32
+ end
33
+
34
+ def scenario(scenario)
35
+ @factory = Scenario
36
+ end
37
+
38
+ def scenario_outline(scenario)
39
+ @factory = ScenarioOutlineExample
40
+ end
41
+
42
+ def examples_table(examples_table)
43
+ end
44
+
45
+ def examples_table_row(row)
46
+ end
47
+
48
+ def result
49
+ @factory.new(@test_case)
50
+ end
51
+ end
52
+ private_constant :Builder
53
+
54
+ class Scenario < SimpleDelegator
55
+ def initialize(test_case, result = Core::Test::Result::Unknown.new)
56
+ @test_case = test_case
57
+ @result = result
58
+ super test_case
59
+ end
60
+
61
+ def accept_hook?(hook)
62
+ hook.tag_expressions.all? { |expression| @test_case.match_tags?(expression) }
63
+ end
64
+
65
+ def failed?
66
+ @result.failed?
67
+ end
68
+
69
+ def passed?
70
+ !failed?
71
+ end
72
+
73
+ def title
74
+ warn("deprecated: call #name instead")
75
+ name
76
+ end
77
+
78
+ def source_tags
79
+ #warn('deprecated: call #tags instead')
80
+ tags
81
+ end
82
+
83
+ def source_tag_names
84
+ tags.map &:name
85
+ end
86
+
87
+ def skip_invoke!
88
+ Cucumber.deprecate(self.class.name, __method__, "Call #skip_this_scenario on the World directly")
89
+ raise Cucumber::Core::Test::Result::Skipped
90
+ end
91
+
92
+ def outline?
93
+ false
94
+ end
95
+
96
+ def with_result(result)
97
+ self.class.new(@test_case, result)
98
+ end
99
+ end
100
+
101
+ class ScenarioOutlineExample < Scenario
102
+ def outline?
103
+ true
104
+ end
105
+
106
+ def scenario_outline
107
+ self
108
+ end
109
+
110
+ def cell_values
111
+ source.last.values
112
+ end
113
+ end
114
+ end
115
+ end