cucumber 0.4.5.rc2 → 0.5.0

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 (34) hide show
  1. data/.gitignore +1 -0
  2. data/History.txt +11 -2
  3. data/Rakefile +12 -11
  4. data/VERSION.yml +2 -3
  5. data/cucumber.gemspec +39 -31
  6. data/cucumber.yml +1 -1
  7. data/features/language_help.feature +2 -2
  8. data/features/wire_protocol.feature +167 -48
  9. data/features/wire_protocol_table_diffing.feature +95 -0
  10. data/lib/cucumber/cli/configuration.rb +7 -1
  11. data/lib/cucumber/cli/main.rb +6 -2
  12. data/lib/cucumber/cli/options.rb +9 -9
  13. data/lib/cucumber/feature_file.rb +1 -1
  14. data/lib/cucumber/formatter/color_io.rb +4 -4
  15. data/lib/cucumber/formatter/html.rb +4 -3
  16. data/lib/cucumber/formatter/tag_cloud.rb +1 -0
  17. data/lib/cucumber/formatter/unicode.rb +28 -23
  18. data/lib/cucumber/languages.yml +1 -1
  19. data/lib/cucumber/step_argument.rb +3 -3
  20. data/lib/cucumber/step_match.rb +2 -2
  21. data/lib/cucumber/wire_support/connection.rb +4 -0
  22. data/lib/cucumber/wire_support/request_handler.rb +1 -1
  23. data/lib/cucumber/wire_support/wire_exception.rb +23 -1
  24. data/lib/cucumber/wire_support/wire_language.rb +4 -1
  25. data/lib/cucumber/wire_support/wire_packet.rb +0 -4
  26. data/lib/cucumber/wire_support/wire_protocol.rb +51 -7
  27. data/lib/cucumber/wire_support/wire_step_definition.rb +14 -9
  28. data/spec/cucumber/cli/options_spec.rb +4 -8
  29. data/spec/cucumber/formatter/color_io_spec.rb +7 -5
  30. data/spec/cucumber/rb_support/regexp_argument_matcher_spec.rb +2 -2
  31. data/spec/cucumber/step_match_spec.rb +6 -1
  32. data/spec/cucumber/wire_support/wire_exception_spec.rb +44 -0
  33. data/spec/cucumber/wire_support/wire_step_definition_spec.rb +20 -0
  34. metadata +30 -15
@@ -0,0 +1,95 @@
1
+ @wire
2
+ Feature: Wire protocol table diffing
3
+ In order to use the amazing functionality in the Cucumber table object
4
+ As a wire server
5
+ I want to be able to ask for a table diff during a step definition invocation
6
+
7
+ Background:
8
+ Given a standard Cucumber project directory structure
9
+ And a file named "features/wired.feature" with:
10
+ """
11
+ Scenario: Wired
12
+ Given we're all wired
13
+
14
+ """
15
+ And a file named "features/step_definitions/some_remote_place.wire" with:
16
+ """
17
+ host: localhost
18
+ port: 54321
19
+
20
+ """
21
+
22
+ Scenario: Invoke a step definition tries to diff the table and fails
23
+ Given there is a wire server running on port 54321 which understands the following protocol:
24
+ | request | response |
25
+ | ["step_matches",{"name_to_match":"we're all wired"}] | ["step_matches",[{"id":"1", "args":[]}]] |
26
+ | ["begin_scenario",null] | ["success",null] |
27
+ | ["invoke",{"id":"1","args":[]}] | ["diff",[[["a","b"],["c","d"]],[["x","y"],["z","z"]]]] |
28
+ | ["diff_failed",null] | ["step_failed",{"message":"Not same", "exception":"DifferentException", "backtrace":["a.cs:12","b.cs:34"]}] |
29
+ | ["end_scenario",null] | ["success",null] |
30
+ When I run cucumber -f progress --backtrace
31
+ And it should fail with
32
+ """
33
+ F
34
+
35
+ (::) failed steps (::)
36
+
37
+ Not same (DifferentException from localhost:54321)
38
+ a.cs:12
39
+ b.cs:34
40
+ features/wired.feature:2:in `Given we're all wired'
41
+
42
+ Failing Scenarios:
43
+ cucumber features/wired.feature:1 # Scenario: Wired
44
+
45
+ 1 scenario (1 failed)
46
+ 1 step (1 failed)
47
+
48
+ """
49
+
50
+ Scenario: Invoke a step definition tries to diff the table and passes
51
+ Given there is a wire server running on port 54321 which understands the following protocol:
52
+ | request | response |
53
+ | ["step_matches",{"name_to_match":"we're all wired"}] | ["step_matches",[{"id":"1", "args":[]}]] |
54
+ | ["begin_scenario",null] | ["success",null] |
55
+ | ["invoke",{"id":"1","args":[]}] | ["diff",[[["a"],["b"]],[["a"],["b"]]]] |
56
+ | ["diff_ok",null] | ["success",null] |
57
+ | ["end_scenario",null] | ["success",null] |
58
+ When I run cucumber -f progress
59
+ And it should pass with
60
+ """
61
+ .
62
+
63
+ 1 scenario (1 passed)
64
+ 1 step (1 passed)
65
+
66
+ """
67
+
68
+ Scenario: Invoke a step definition which successfully diffs a table but then fails
69
+ Given there is a wire server running on port 54321 which understands the following protocol:
70
+ | request | response |
71
+ | ["step_matches",{"name_to_match":"we're all wired"}] | ["step_matches",[{"id":"1", "args":[]}]] |
72
+ | ["begin_scenario",null] | ["success",null] |
73
+ | ["invoke",{"id":"1","args":[]}] | ["diff",[[["a"],["b"]],[["a"],["b"]]]] |
74
+ | ["diff_ok",null] | ["step_failed",{"message":"I wanted things to be different for us"}] |
75
+ | ["end_scenario",null] | ["success",null] |
76
+ When I run cucumber -f progress
77
+ And it should fail with
78
+ """
79
+ F
80
+
81
+ (::) failed steps (::)
82
+
83
+ I wanted things to be different for us (Cucumber::WireSupport::WireException)
84
+ features/wired.feature:2:in `Given we're all wired'
85
+
86
+ Failing Scenarios:
87
+ cucumber features/wired.feature:1 # Scenario: Wired
88
+
89
+ 1 scenario (1 failed)
90
+ 1 step (1 failed)
91
+
92
+ """
93
+
94
+
95
+
@@ -127,7 +127,13 @@ module Cucumber
127
127
  private
128
128
 
129
129
  def formatters(step_mother)
130
- return [Formatter::Pretty.new(step_mother, nil, @options)] if @options[:autoformat]
130
+ # TODO: We should remove the autoformat functionality. That
131
+ # can be done with the gherkin CLI.
132
+ if @options[:autoformat]
133
+ require 'cucumber/formatter/pretty'
134
+ return [Formatter::Pretty.new(step_mother, nil, @options)]
135
+ end
136
+
131
137
  @options[:formats].map do |format_and_out|
132
138
  format = format_and_out[0]
133
139
  path_or_io = format_and_out[1]
@@ -5,7 +5,6 @@ require 'logger'
5
5
  require 'cucumber/parser'
6
6
  require 'cucumber/feature_file'
7
7
  require 'cucumber/formatter/color_io'
8
- require 'cucumber/cli/language_help_formatter'
9
8
  require 'cucumber/cli/configuration'
10
9
  require 'cucumber/cli/drb_client'
11
10
  require 'cucumber/ast/tags'
@@ -27,7 +26,12 @@ module Cucumber
27
26
 
28
27
  def initialize(args, out_stream = STDOUT, error_stream = STDERR)
29
28
  @args = args
30
- @out_stream = out_stream == STDOUT ? Formatter::ColorIO.new : out_stream
29
+ if Cucumber::WINDOWS_MRI
30
+ @out_stream = out_stream == STDOUT ? Formatter::ColorIO.new(Kernel, STDOUT) : out_stream
31
+ else
32
+ @out_stream = out_stream
33
+ end
34
+
31
35
  @error_stream = error_stream
32
36
  @configuration = nil
33
37
  end
@@ -1,4 +1,6 @@
1
1
  require 'cucumber/cli/profile_loader'
2
+ require 'cucumber/formatter/ansicolor'
3
+
2
4
  module Cucumber
3
5
  module Cli
4
6
 
@@ -115,17 +117,13 @@ module Cucumber
115
117
  "This option can be specified multiple times.") do |v|
116
118
  @options[:require] << v
117
119
  end
118
- opts.on("-l LANG", "--language LANG (DEPRECATED)",
119
- "Specify language for features (Default: #{@options[:lang]})",
120
- %{Run with "--language help" to see all languages},
121
- %{Run with "--language LANG help" to list keywords for LANG}) do |v|
122
- if v == 'help'
120
+ opts.on("--i18n LANG",
121
+ "List keywords for in a particular language",
122
+ %{Run with "--i18n help" to see all languages}) do |lang|
123
+ if lang == 'help'
123
124
  list_languages_and_exit
124
- elsif args==['help']
125
- list_keywords_and_exit(v)
126
125
  else
127
- warn("\nWARNING: --language is deprecated and will be removed in version 0.5.\nSee http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages")
128
- @options[:lang] = v
126
+ list_keywords_and_exit(lang)
129
127
  end
130
128
  end
131
129
  opts.on("-f FORMAT", "--format FORMAT",
@@ -367,11 +365,13 @@ module Cucumber
367
365
  unless Cucumber::LANGUAGES[lang]
368
366
  raise("No language with key #{lang}")
369
367
  end
368
+ require 'cucumber/cli/language_help_formatter'
370
369
  LanguageHelpFormatter.list_keywords(@out_stream, lang)
371
370
  Kernel.exit(0)
372
371
  end
373
372
 
374
373
  def list_languages_and_exit
374
+ require 'cucumber/cli/language_help_formatter'
375
375
  LanguageHelpFormatter.list_languages(@out_stream)
376
376
  Kernel.exit(0)
377
377
  end
@@ -23,7 +23,7 @@ module Cucumber
23
23
  # be filtered.
24
24
  def parse(step_mother, options)
25
25
  filter = Filter.new(@lines, options)
26
- language = Parser::NaturalLanguage.get(step_mother, (lang || options[:lang] || 'en'))
26
+ language = Parser::NaturalLanguage.get(step_mother, (lang || 'en'))
27
27
  language.parse(source, @path, filter)
28
28
  end
29
29
 
@@ -2,15 +2,15 @@ require 'forwardable'
2
2
 
3
3
  module Cucumber
4
4
  module Formatter
5
- # Adapter to make #puts/#print/#flush work with colours on Windows
5
+ # Adapter to make #puts/#print/#flush work with win32console
6
6
  class ColorIO #:nodoc:
7
7
  extend Forwardable
8
8
  def_delegators :@kernel, :puts, :print # win32console colours only work when sent to Kernel
9
9
  def_delegators :@stdout, :flush, :tty?, :write, :close
10
10
 
11
- def initialize
12
- @kernel = Kernel
13
- @stdout = STDOUT
11
+ def initialize(kernel, stdout)
12
+ @kernel = kernel
13
+ @stdout = stdout
14
14
  end
15
15
 
16
16
  # Ensure using << still gets colours in win32console
@@ -315,16 +315,17 @@ module Cucumber
315
315
  backtrace = Array.new
316
316
  @builder.div(:class => 'message') do
317
317
  message = exception.message
318
- if message.include?('Exception caught')
318
+ if defined?(RAILS_ROOT) && message.include?('Exception caught')
319
319
  matches = message.match(/Showing <i>(.+)<\/i>(?:.+)#(\d+)/)
320
320
  backtrace += ["#{RAILS_ROOT}/#{matches[1]}:#{matches[2]}"]
321
321
  message = message.match(/<code>([^(\/)]+)<\//m)[1]
322
322
  end
323
- @builder << "<pre>#{message}</pre>"
323
+ @builder.pre do
324
+ @builder.text!(message)
325
+ end
324
326
  end
325
327
  @builder.div(:class => 'backtrace') do
326
328
  @builder.pre do
327
- # backtrace += (exception.backtrace.size == 1 || exception.backtrace[0].include?('(eval):')) ? ["#{RAILS_ROOT}/#{@step_match.file_colon_line}"] + exception.backtrace : exception.backtrace
328
329
  backtrace = exception.backtrace
329
330
  backtrace.delete_if { |x| x =~ /\/gems\/(cucumber|rspec)/ }
330
331
  @builder << backtrace_line(backtrace.join("\n"))
@@ -1,4 +1,5 @@
1
1
  require 'cucumber/formatter/io'
2
+ require 'cucumber/formatter/pretty'
2
3
 
3
4
  module Cucumber
4
5
  module Formatter
@@ -1,35 +1,40 @@
1
1
  # Require this file if you need Unicode support.
2
2
  require 'cucumber/platform'
3
3
  require 'cucumber/formatter/ansicolor'
4
-
5
4
  $KCODE='u' unless Cucumber::RUBY_1_9
6
5
 
7
- if Cucumber::WINDOWS_MRI && `chcp` =~ /(\d+)/
6
+ if Cucumber::WINDOWS && `cmd /c chcp` =~ /(\d+)/
7
+ require 'iconv'
8
8
  codepage = $1.to_i
9
- codepages = (1251..1252)
9
+ Cucumber::CODEPAGE = "cp#{codepage}"
10
10
 
11
- if codepages.include?(codepage)
12
- Cucumber::CODEPAGE = "cp#{codepage}"
13
-
14
- require 'iconv'
15
- module Kernel #:nodoc:
16
- alias cucumber_print print
17
- def print(*a)
18
- begin
19
- cucumber_print(*Iconv.iconv(Cucumber::CODEPAGE, "UTF-8", *a))
20
- rescue Iconv::IllegalSequence
21
- cucumber_print(*a)
22
- end
23
- end
11
+ module Cucumber
12
+ module WindowsOutput #:nodoc:
13
+ def self.extended(o)
14
+ o.instance_eval do
15
+ alias cucumber_print print
16
+ def print(*a)
17
+ begin
18
+ cucumber_print(*Iconv.iconv(Cucumber::CODEPAGE, "UTF-8", *a.map{|a|a.to_s}))
19
+ rescue Iconv::IllegalSequence
20
+ cucumber_print(*a)
21
+ end
22
+ end
24
23
 
25
- alias cucumber_puts puts
26
- def puts(*a)
27
- begin
28
- cucumber_puts(*Iconv.iconv(Cucumber::CODEPAGE, "UTF-8", *a))
29
- rescue Iconv::IllegalSequence
30
- cucumber_puts(*a)
24
+ alias cucumber_puts puts
25
+ def puts(*a)
26
+ begin
27
+ cucumber_puts(*Iconv.iconv(Cucumber::CODEPAGE, "UTF-8", *a.map{|a|a.to_s}))
28
+ rescue Iconv::IllegalSequence
29
+ cucumber_puts(*a)
30
+ end
31
+ end
31
32
  end
32
33
  end
34
+
35
+ Kernel.extend(self)
36
+ STDOUT.extend(self)
37
+ STDERR.extend(self)
33
38
  end
34
39
  end
35
- end
40
+ end
@@ -333,7 +333,7 @@
333
333
  scenario: Scenario
334
334
  scenario_outline: Abstract Scenario
335
335
  examples: Voorbeelden
336
- given: *|Gegeven
336
+ given: *|Gegeven|Stel
337
337
  when: *|Als
338
338
  then: *|Dan
339
339
  and: *|En
@@ -1,9 +1,9 @@
1
1
  module Cucumber
2
2
  class StepArgument
3
- attr_reader :val, :pos
3
+ attr_reader :val, :byte_offset
4
4
 
5
- def initialize(val, pos)
6
- @val, @pos = val, pos
5
+ def initialize(val, byte_offset)
6
+ @val, @byte_offset = val, byte_offset
7
7
  end
8
8
  end
9
9
  end
@@ -59,7 +59,7 @@ module Cucumber
59
59
  s = string.dup
60
60
  offset = 0
61
61
  step_arguments.each do |step_argument|
62
- next if step_argument.pos.nil?
62
+ next if step_argument.byte_offset.nil?
63
63
  replacement = if block_given?
64
64
  proc.call(step_argument.val)
65
65
  elsif Proc === format
@@ -68,7 +68,7 @@ module Cucumber
68
68
  format % step_argument.val
69
69
  end
70
70
 
71
- s[step_argument.pos + offset, step_argument.val.length] = replacement
71
+ s[step_argument.byte_offset + offset, step_argument.val.length] = replacement
72
72
  offset += replacement.jlength - step_argument.val.jlength
73
73
  end
74
74
  s
@@ -22,6 +22,10 @@ module Cucumber
22
22
  raise "Timed out calling server with message #{message}"
23
23
  end
24
24
  end
25
+
26
+ def exception(params)
27
+ WireException.new(params, @host, @port)
28
+ end
25
29
 
26
30
  private
27
31
 
@@ -12,7 +12,7 @@ module Cucumber
12
12
  end
13
13
 
14
14
  def handle_fail(params)
15
- raise WireException.new(params)
15
+ raise @connection.exception(params)
16
16
  end
17
17
  end
18
18
  end
@@ -2,8 +2,30 @@ module Cucumber
2
2
  module WireSupport
3
3
  # Proxy for an exception that occured at the remote end of the wire
4
4
  class WireException < StandardError
5
- def initialize(args)
5
+ module CanSetName
6
+ attr_writer :exception_name
7
+ def to_s
8
+ @exception_name
9
+ end
10
+ end
11
+
12
+ def initialize(args, host, port)
6
13
  super args['message']
14
+ if args['exception']
15
+ self.class.extend(CanSetName)
16
+ self.class.exception_name = "#{args['exception']} from #{host}:#{port}"
17
+ end
18
+ if args['backtrace']
19
+ @backtrace = if args['backtrace'].is_a?(String)
20
+ args['backtrace'].split("\n") # TODO: change cuke4nuke to pass an array instead of a big string
21
+ else
22
+ args['backtrace']
23
+ end
24
+ end
25
+ end
26
+
27
+ def backtrace
28
+ @backtrace || super
7
29
  end
8
30
  end
9
31
  end
@@ -29,7 +29,10 @@ module Cucumber
29
29
  end
30
30
 
31
31
  def snippet_text(step_keyword, step_name, multiline_arg_class)
32
- "Snippets are not implemented for the wire yet"
32
+ snippets = @connections.map do |remote|
33
+ remote.snippet_text(step_keyword, step_name, multiline_arg_class.to_s)
34
+ end
35
+ snippets.flatten.join("\n")
33
36
  end
34
37
 
35
38
  protected
@@ -22,10 +22,6 @@ module Cucumber
22
22
  [@message, @params].to_json
23
23
  end
24
24
 
25
- def raise_if_bad
26
- raise WireException.new(@params) if @message == 'fail' || @message == 'step_failed'
27
- end
28
-
29
25
  def handle_with(handler)
30
26
  handler.send("handle_#{@message}", @params)
31
27
  end
@@ -9,7 +9,7 @@ module Cucumber
9
9
  make_request(:step_matches, :name_to_match => name_to_match) do
10
10
  def handle_step_matches(params)
11
11
  params.map do |raw_step_match|
12
- step_definition = WireStepDefinition.new(raw_step_match['id'], @connection)
12
+ step_definition = WireStepDefinition.new(@connection, raw_step_match)
13
13
  step_args = raw_step_match['args'].map do |raw_arg|
14
14
  StepArgument.new(raw_arg['val'], raw_arg['pos'])
15
15
  end
@@ -23,12 +23,37 @@ module Cucumber
23
23
  StepMatch.new(step_definition, @name_to_match, @name_to_report, step_args)
24
24
  end
25
25
 
26
+ def snippet_text(step_keyword, step_name, multiline_arg_class_name)
27
+ request_params = { :step_keyword => step_keyword, :step_name => step_name, :multiline_arg_class => multiline_arg_class_name }
28
+
29
+ make_request(:snippet_text, request_params) do
30
+ def handle_snippet_text(text)
31
+ text
32
+ end
33
+ end
34
+ end
35
+
26
36
  def invoke(step_definition_id, args)
27
37
  request_params = { :id => step_definition_id, :args => args }
28
-
38
+
29
39
  make_request(:invoke, request_params) do
30
40
  def handle_success(params)
31
41
  end
42
+
43
+ def handle_pending(message)
44
+ raise Pending, message || "TODO"
45
+ end
46
+
47
+ def handle_diff(tables)
48
+ table1 = Ast::Table.new(tables[0])
49
+ table2 = Ast::Table.new(tables[1])
50
+ begin
51
+ table1.diff!(table2)
52
+ rescue Cucumber::Ast::Table::Different
53
+ @connection.diff_failed
54
+ end
55
+ @connection.diff_ok
56
+ end
32
57
 
33
58
  def handle_step_failed(params)
34
59
  handle_fail(params)
@@ -36,6 +61,28 @@ module Cucumber
36
61
  end
37
62
  end
38
63
 
64
+ def diff_failed
65
+ make_request(:diff_failed) do
66
+ def handle_success(params)
67
+ end
68
+
69
+ def handle_step_failed(params)
70
+ handle_fail(params)
71
+ end
72
+ end
73
+ end
74
+
75
+ def diff_ok
76
+ make_request(:diff_ok) do
77
+ def handle_success(params)
78
+ end
79
+
80
+ def handle_step_failed(params)
81
+ handle_fail(params)
82
+ end
83
+ end
84
+ end
85
+
39
86
  def begin_scenario(scenario)
40
87
  make_request(:begin_scenario) do
41
88
  def handle_success(params)
@@ -52,12 +99,9 @@ module Cucumber
52
99
 
53
100
  private
54
101
 
55
- def handler(request_message, &block)
56
- RequestHandler.new(self, request_message, &block)
57
- end
58
-
59
102
  def make_request(request_message, params = nil, &block)
60
- handler(request_message, &block).execute(params)
103
+ handler = RequestHandler.new(self, request_message, &block)
104
+ handler.execute(params)
61
105
  end
62
106
  end
63
107
  end