cucumber 0.4.5.rc2 → 0.5.0

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