cucumber 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +19 -0
- data/Manifest.txt +5 -1
- data/examples/self_test/features/tons_of_cukes.feature +52 -0
- data/examples/sinatra/features/support/env.rb +6 -2
- data/examples/tickets/features/248.feature +11 -0
- data/examples/tickets/features/step_definitons/248_steps.rb +15 -0
- data/features/cucumber_cli.feature +1 -1
- data/features/custom_formatter.feature +2 -2
- data/features/usage.feature +108 -0
- data/lib/autotest/cucumber_mixin.rb +1 -1
- data/lib/cucumber/ast/feature.rb +1 -1
- data/lib/cucumber/ast/features.rb +6 -0
- data/lib/cucumber/ast/step.rb +2 -13
- data/lib/cucumber/ast/step_invocation.rb +11 -3
- data/lib/cucumber/ast/table.rb +4 -0
- data/lib/cucumber/cli/configuration.rb +11 -14
- data/lib/cucumber/cli/main.rb +14 -21
- data/lib/cucumber/formatter.rb +1 -1
- data/lib/cucumber/formatter/html.rb +47 -8
- data/lib/cucumber/formatter/pretty.rb +1 -2
- data/lib/cucumber/formatter/rerun.rb +8 -0
- data/lib/cucumber/formatter/usage.rb +69 -0
- data/lib/cucumber/jbehave.rb +1 -0
- data/lib/cucumber/rails/world.rb +22 -21
- data/lib/cucumber/step_definition.rb +65 -54
- data/lib/cucumber/step_match.rb +10 -2
- data/lib/cucumber/step_mother.rb +1 -8
- data/lib/cucumber/version.rb +1 -1
- data/rails_generators/cucumber/templates/env.rb +2 -0
- data/rails_generators/feature/templates/steps.erb +1 -1
- data/spec/cucumber/ast/feature_spec.rb +2 -1
- data/spec/cucumber/ast/step_collection_spec.rb +8 -0
- data/spec/cucumber/cli/configuration_spec.rb +18 -6
- data/spec/cucumber/cli/main_spec.rb +1 -13
- data/spec/cucumber/parser/feature_parser_spec.rb +15 -15
- data/spec/cucumber/step_definition_spec.rb +21 -9
- metadata +7 -3
- data/gem_tasks/jar.rake +0 -67
data/lib/cucumber/cli/main.rb
CHANGED
@@ -32,13 +32,6 @@ module Cucumber
|
|
32
32
|
step_mother.options = configuration.options
|
33
33
|
|
34
34
|
require_files
|
35
|
-
|
36
|
-
if(configuration.print_step_definitions?)
|
37
|
-
step_mother.print_step_definitions(@out_stream)
|
38
|
-
Kernel.exit(0)
|
39
|
-
return # In specs, exit is stubbed
|
40
|
-
end
|
41
|
-
|
42
35
|
enable_diffing
|
43
36
|
|
44
37
|
features = load_plain_text_features
|
@@ -53,7 +46,18 @@ module Cucumber
|
|
53
46
|
Kernel.exit(failure ? 1 : 0)
|
54
47
|
end
|
55
48
|
|
56
|
-
|
49
|
+
def load_plain_text_features
|
50
|
+
features = Ast::Features.new
|
51
|
+
parser = Parser::FeatureParser.new
|
52
|
+
|
53
|
+
verbose_log("Features:")
|
54
|
+
configuration.feature_files.each do |f|
|
55
|
+
features.add_feature(parser.parse_file(f))
|
56
|
+
verbose_log(" * #{f}")
|
57
|
+
end
|
58
|
+
verbose_log("\n"*2)
|
59
|
+
features
|
60
|
+
end
|
57
61
|
|
58
62
|
def configuration
|
59
63
|
return @configuration if @configuration
|
@@ -62,6 +66,8 @@ module Cucumber
|
|
62
66
|
@configuration.parse!(@args)
|
63
67
|
@configuration
|
64
68
|
end
|
69
|
+
|
70
|
+
private
|
65
71
|
|
66
72
|
def require_files
|
67
73
|
verbose_log("Ruby files required:")
|
@@ -77,19 +83,6 @@ module Cucumber
|
|
77
83
|
verbose_log("\n")
|
78
84
|
end
|
79
85
|
|
80
|
-
def load_plain_text_features
|
81
|
-
features = Ast::Features.new
|
82
|
-
parser = Parser::FeatureParser.new
|
83
|
-
|
84
|
-
verbose_log("Features:")
|
85
|
-
configuration.feature_files.each do |f|
|
86
|
-
features.add_feature(parser.parse_file(f))
|
87
|
-
verbose_log(" * #{f}")
|
88
|
-
end
|
89
|
-
verbose_log("\n"*2)
|
90
|
-
features
|
91
|
-
end
|
92
|
-
|
93
86
|
def verbose_log(string)
|
94
87
|
@out_stream.puts(string) if configuration.verbose?
|
95
88
|
end
|
data/lib/cucumber/formatter.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
%w{color_io pretty progress profile rerun html}.each{|n| require "cucumber/formatter/#{n}"}
|
1
|
+
%w{color_io pretty progress profile rerun html usage}.each{|n| require "cucumber/formatter/#{n}"}
|
@@ -83,37 +83,73 @@ module Cucumber
|
|
83
83
|
@builder.h4("#{keyword} #{name}")
|
84
84
|
end
|
85
85
|
|
86
|
-
def visit_steps(
|
86
|
+
def visit_steps(steps)
|
87
87
|
@builder.ol do
|
88
88
|
super
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
def visit_step(step)
|
93
|
+
@step_id = step.dom_id
|
94
|
+
@builder.li(:id => @step_id) do
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
92
99
|
def visit_step_name(keyword, step_match, status, source_indent, background)
|
93
|
-
|
94
|
-
@
|
100
|
+
@step_matches ||= []
|
101
|
+
@skip_step = @step_matches.index(step_match)
|
102
|
+
@step_matches << step_match
|
103
|
+
|
104
|
+
unless @skip_step
|
105
|
+
step_name = step_match.format_args(lambda{|param| "<span>#{param}</span>"})
|
106
|
+
@builder.div(:class => status) do |div|
|
107
|
+
div << "#{keyword} #{step_name}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def visit_exception(exception, status)
|
113
|
+
@builder.pre(format_exception(exception), :class => status)
|
95
114
|
end
|
96
115
|
|
97
116
|
def visit_multiline_arg(multiline_arg)
|
117
|
+
return if @skip_step
|
98
118
|
if Ast::Table === multiline_arg
|
99
119
|
@builder.table do
|
100
120
|
super
|
101
121
|
end
|
102
122
|
else
|
103
|
-
|
104
|
-
|
105
|
-
|
123
|
+
super
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def visit_py_string(string, status)
|
128
|
+
@builder.pre(:class => status) do |pre|
|
129
|
+
pre << string
|
106
130
|
end
|
107
131
|
end
|
108
132
|
|
109
133
|
def visit_table_row(table_row)
|
110
|
-
@
|
134
|
+
@row_id = table_row.dom_id
|
135
|
+
@col_index = 0
|
136
|
+
@builder.tr(:id => @row_id) do
|
111
137
|
super
|
112
138
|
end
|
139
|
+
if table_row.exception
|
140
|
+
@builder.tr do
|
141
|
+
@builder.td(:colspan => @col_index.to_s, :class => 'failed') do
|
142
|
+
@builder.pre do |pre|
|
143
|
+
pre << format_exception(table_row.exception)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
113
148
|
end
|
114
149
|
|
115
150
|
def visit_table_cell_value(value, width, status)
|
116
|
-
@builder.td(value, :class => status)
|
151
|
+
@builder.td(value, :class => status, :id => "#{@row_id}_#{@col_index}")
|
152
|
+
@col_index += 1
|
117
153
|
end
|
118
154
|
|
119
155
|
def announce(announcement)
|
@@ -128,6 +164,9 @@ module Cucumber
|
|
128
164
|
end
|
129
165
|
end
|
130
166
|
|
167
|
+
def format_exception(exception)
|
168
|
+
(["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n")
|
169
|
+
end
|
131
170
|
end
|
132
171
|
end
|
133
172
|
end
|
@@ -121,16 +121,15 @@ module Cucumber
|
|
121
121
|
|
122
122
|
def visit_step_name(keyword, step_match, status, source_indent, background)
|
123
123
|
@step_matches ||= []
|
124
|
-
|
125
124
|
non_failed_background_step_outside_background = !@in_background && background && (status != :failed)
|
126
125
|
@skip_step = @step_matches.index(step_match) || non_failed_background_step_outside_background
|
126
|
+
@step_matches << step_match
|
127
127
|
|
128
128
|
unless(@skip_step)
|
129
129
|
source_indent = nil unless @options[:source]
|
130
130
|
formatted_step_name = format_step(keyword, step_match, status, source_indent)
|
131
131
|
@io.puts(" " + formatted_step_name)
|
132
132
|
end
|
133
|
-
@step_matches << step_match
|
134
133
|
end
|
135
134
|
|
136
135
|
def visit_multiline_arg(multiline_arg)
|
@@ -1,5 +1,13 @@
|
|
1
1
|
module Cucumber
|
2
2
|
module Formatter
|
3
|
+
# This formatter keeps track of all failing features and print out their location.
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# features/foo.feature:34 features/bar.feature:11:76:81
|
7
|
+
#
|
8
|
+
# This formatter is used by AutoTest - it will use the output to decide what
|
9
|
+
# to run the next time, simply passing the output string on the command line.
|
10
|
+
#
|
3
11
|
class Rerun < Ast::Visitor
|
4
12
|
def initialize(step_mother, io, options)
|
5
13
|
super(step_mother)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'cucumber/formatter/progress'
|
2
|
+
|
3
|
+
module Cucumber
|
4
|
+
module Formatter
|
5
|
+
class Usage < Ast::Visitor
|
6
|
+
include Console
|
7
|
+
|
8
|
+
def initialize(step_mother, io, options)
|
9
|
+
super(step_mother)
|
10
|
+
@io = io
|
11
|
+
@options = options
|
12
|
+
@step_definitions = Hash.new { |h,step_definition| h[step_definition] = [] }
|
13
|
+
@locations = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_features(features)
|
17
|
+
super
|
18
|
+
print_summary
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit_step(step)
|
22
|
+
@step = step
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_step_name(keyword, step_match, status, source_indent, background)
|
27
|
+
if step_match.step_definition
|
28
|
+
location = @step.file_colon_line
|
29
|
+
return if @locations.index(location)
|
30
|
+
@locations << location
|
31
|
+
|
32
|
+
description = format_step(keyword, step_match, status, nil)
|
33
|
+
length = (keyword + step_match.format_args).jlength
|
34
|
+
@step_definitions[step_match.step_definition] << [step_match, description, length, location]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_summary
|
39
|
+
sorted_defs = @step_definitions.keys.sort_by{|step_definition| step_definition.backtrace_line}
|
40
|
+
|
41
|
+
sorted_defs.each do |step_definition|
|
42
|
+
step_matches_and_descriptions = @step_definitions[step_definition].sort_by do |step_match_and_description|
|
43
|
+
step_match = step_match_and_description[0]
|
44
|
+
step_match.step_definition.regexp.inspect
|
45
|
+
end
|
46
|
+
|
47
|
+
step_matches = step_matches_and_descriptions.map{|step_match_and_description| step_match_and_description[0]}
|
48
|
+
|
49
|
+
lengths = step_matches_and_descriptions.map do |step_match_and_description|
|
50
|
+
step_match_and_description[2]
|
51
|
+
end
|
52
|
+
lengths << step_definition.text_length
|
53
|
+
max_length = lengths.max
|
54
|
+
|
55
|
+
@io.print step_definition.regexp.inspect
|
56
|
+
@io.puts format_string(" # #{step_definition.file_colon_line}".indent(max_length - step_definition.text_length), :comment)
|
57
|
+
step_matches_and_descriptions.each do |step_match_and_description|
|
58
|
+
step_match = step_match_and_description[0]
|
59
|
+
description = step_match_and_description[1]
|
60
|
+
length = step_match_and_description[2]
|
61
|
+
file_colon_line = step_match_and_description[3]
|
62
|
+
@io.print " #{description}"
|
63
|
+
@io.puts format_string(" # #{file_colon_line}".indent(max_length - length), :comment)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/cucumber/jbehave.rb
CHANGED
data/lib/cucumber/rails/world.rb
CHANGED
@@ -9,22 +9,10 @@ else
|
|
9
9
|
end
|
10
10
|
require 'test/unit/testresult'
|
11
11
|
|
12
|
-
# These allow exceptions to come through as opposed to being caught and having non-helpful responses returned.
|
13
|
-
ActionController::Base.class_eval do
|
14
|
-
def rescue_action(exception)
|
15
|
-
raise exception
|
16
|
-
end
|
17
|
-
end
|
18
|
-
ActionController::Dispatcher.class_eval do
|
19
|
-
def self.failsafe_response(output, status, exception = nil)
|
20
|
-
raise exception
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
12
|
# So that Test::Unit doesn't launch at the end - makes it think it has already been run.
|
25
13
|
Test::Unit.run = true if Test::Unit.respond_to?(:run=)
|
26
14
|
|
27
|
-
$
|
15
|
+
$__cucumber_toplevel = self
|
28
16
|
|
29
17
|
module Cucumber #:nodoc:
|
30
18
|
module Rails
|
@@ -44,20 +32,21 @@ module Cucumber #:nodoc:
|
|
44
32
|
def self.use_transactional_fixtures
|
45
33
|
World.use_transactional_fixtures = true
|
46
34
|
if defined?(ActiveRecord::Base)
|
47
|
-
$
|
48
|
-
|
49
|
-
|
35
|
+
$__cucumber_toplevel.Before do
|
36
|
+
@__cucumber_ar_connection = ActiveRecord::Base.connection
|
37
|
+
if @__cucumber_ar_connection.respond_to?(:increment_open_transactions)
|
38
|
+
@__cucumber_ar_connection.increment_open_transactions
|
50
39
|
else
|
51
40
|
ActiveRecord::Base.__send__(:increment_open_transactions)
|
52
41
|
end
|
53
|
-
|
42
|
+
@__cucumber_ar_connection.begin_db_transaction
|
54
43
|
ActionMailer::Base.deliveries = [] if defined?(ActionMailer::Base)
|
55
44
|
end
|
56
45
|
|
57
|
-
$
|
58
|
-
|
59
|
-
if
|
60
|
-
|
46
|
+
$__cucumber_toplevel.After do
|
47
|
+
@__cucumber_ar_connection.rollback_db_transaction
|
48
|
+
if @__cucumber_ar_connection.respond_to?(:decrement_open_transactions)
|
49
|
+
@__cucumber_ar_connection.decrement_open_transactions
|
61
50
|
else
|
62
51
|
ActiveRecord::Base.__send__(:decrement_open_transactions)
|
63
52
|
end
|
@@ -65,6 +54,18 @@ module Cucumber #:nodoc:
|
|
65
54
|
end
|
66
55
|
end
|
67
56
|
|
57
|
+
def self.bypass_rescue
|
58
|
+
ActionController::Base.class_eval do
|
59
|
+
def rescue_action(exception)
|
60
|
+
raise exception
|
61
|
+
end
|
62
|
+
end
|
63
|
+
ActionController::Dispatcher.class_eval do
|
64
|
+
def self.failsafe_response(output, status, exception = nil)
|
65
|
+
raise exception
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
@@ -3,6 +3,50 @@ require 'cucumber/core_ext/string'
|
|
3
3
|
require 'cucumber/core_ext/proc'
|
4
4
|
|
5
5
|
module Cucumber
|
6
|
+
module StepDefinitionMethods
|
7
|
+
def step_match(name_to_match, name_to_report)
|
8
|
+
if(match = name_to_match.match(regexp))
|
9
|
+
StepMatch.new(self, name_to_match, name_to_report, match.captures)
|
10
|
+
else
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Formats the matched arguments of the associated Step. This method
|
16
|
+
# is usually called from visitors, which render output.
|
17
|
+
#
|
18
|
+
# The +format+ can either be a String or a Proc.
|
19
|
+
#
|
20
|
+
# If it is a String it should be a format string according to
|
21
|
+
# <tt>Kernel#sprinf</tt>, for example:
|
22
|
+
#
|
23
|
+
# '<span class="param">%s</span></tt>'
|
24
|
+
#
|
25
|
+
# If it is a Proc, it should take one argument and return the formatted
|
26
|
+
# argument, for example:
|
27
|
+
#
|
28
|
+
# lambda { |param| "[#{param}]" }
|
29
|
+
#
|
30
|
+
def format_args(step_name, format)
|
31
|
+
step_name.gzub(regexp, format)
|
32
|
+
end
|
33
|
+
|
34
|
+
def match(step_name)
|
35
|
+
case step_name
|
36
|
+
when String then regexp.match(step_name)
|
37
|
+
when Regexp then regexp == step_name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def backtrace_line
|
42
|
+
"#{file_colon_line}:in `#{regexp.inspect}'"
|
43
|
+
end
|
44
|
+
|
45
|
+
def text_length
|
46
|
+
regexp.inspect.jlength
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
6
50
|
# A Step Definition holds a Regexp and a Proc, and is created
|
7
51
|
# by calling <tt>Given</tt>, <tt>When</tt> or <tt>Then</tt>
|
8
52
|
# in the <tt>step_definitions</tt> ruby files - for example:
|
@@ -14,7 +58,22 @@ module Cucumber
|
|
14
58
|
class StepDefinition
|
15
59
|
def self.snippet_text(step_keyword, step_name)
|
16
60
|
escaped = Regexp.escape(step_name).gsub('\ ', ' ').gsub('/', '\/')
|
17
|
-
|
61
|
+
param_pattern = /"([^\"]*)"/
|
62
|
+
|
63
|
+
match = escaped.match(param_pattern)
|
64
|
+
if match
|
65
|
+
n = 0
|
66
|
+
block_args = match.captures.map do |a|
|
67
|
+
n += 1
|
68
|
+
"arg#{n}"
|
69
|
+
end
|
70
|
+
block_arg_string = " |#{block_args.join(", ")}|"
|
71
|
+
else
|
72
|
+
block_arg_string = ""
|
73
|
+
end
|
74
|
+
|
75
|
+
escaped = escaped.gsub(param_pattern, '"([^\\"]*)"')
|
76
|
+
"#{step_keyword} /^#{escaped}$/ do#{block_arg_string}\n pending\nend"
|
18
77
|
end
|
19
78
|
|
20
79
|
class MissingProc < StandardError
|
@@ -23,7 +82,7 @@ module Cucumber
|
|
23
82
|
end
|
24
83
|
end
|
25
84
|
|
26
|
-
|
85
|
+
include StepDefinitionMethods
|
27
86
|
|
28
87
|
def initialize(pattern, &proc)
|
29
88
|
raise MissingProc if proc.nil?
|
@@ -34,70 +93,22 @@ module Cucumber
|
|
34
93
|
@regexp, @proc = pattern, proc
|
35
94
|
end
|
36
95
|
|
37
|
-
def
|
38
|
-
|
39
|
-
StepMatch.new(self, name_to_match, name_to_report, match.captures)
|
40
|
-
else
|
41
|
-
nil
|
42
|
-
end
|
96
|
+
def regexp
|
97
|
+
@regexp
|
43
98
|
end
|
44
99
|
|
45
|
-
def invoke(world, args
|
100
|
+
def invoke(world, args)
|
46
101
|
args = args.map{|arg| Ast::PyString === arg ? arg.to_s : arg}
|
47
102
|
begin
|
48
|
-
world.cucumber_instance_exec(true,
|
103
|
+
world.cucumber_instance_exec(true, regexp.inspect, *args, &@proc)
|
49
104
|
rescue Cucumber::ArityMismatchError => e
|
50
105
|
e.backtrace.unshift(self.backtrace_line)
|
51
106
|
raise e
|
52
107
|
end
|
53
108
|
end
|
54
109
|
|
55
|
-
#:stopdoc:
|
56
|
-
|
57
|
-
def match(step_name)
|
58
|
-
case step_name
|
59
|
-
when String then @regexp.match(step_name)
|
60
|
-
when Regexp then @regexp == step_name
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Formats the matched arguments of the associated Step. This method
|
65
|
-
# is usually called from visitors, which render output.
|
66
|
-
#
|
67
|
-
# The +format+ either be a String or a Proc.
|
68
|
-
#
|
69
|
-
# If it is a String it should be a format string according to
|
70
|
-
# <tt>Kernel#sprinf</tt>, for example:
|
71
|
-
#
|
72
|
-
# '<span class="param">%s</span></tt>'
|
73
|
-
#
|
74
|
-
# If it is a Proc, it should take one argument and return the formatted
|
75
|
-
# argument, for example:
|
76
|
-
#
|
77
|
-
# lambda { |param| "[#{param}]" }
|
78
|
-
#
|
79
|
-
def format_args(step_name, format)
|
80
|
-
step_name.gzub(@regexp, format)
|
81
|
-
end
|
82
|
-
|
83
|
-
def matched_args(step_name)
|
84
|
-
step_name.match(@regexp).captures
|
85
|
-
end
|
86
|
-
|
87
|
-
def backtrace_line
|
88
|
-
"#{file_colon_line}:in `#{@regexp.inspect}'"
|
89
|
-
end
|
90
|
-
|
91
110
|
def file_colon_line
|
92
111
|
@proc.file_colon_line
|
93
112
|
end
|
94
|
-
|
95
|
-
def text_length
|
96
|
-
@regexp.inspect.jlength
|
97
|
-
end
|
98
|
-
|
99
|
-
def to_s(indent = 0)
|
100
|
-
@regexp.inspect + (' # ').indent(indent) + file_colon_line
|
101
|
-
end
|
102
113
|
end
|
103
114
|
end
|