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

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