cucumber 0.8.7 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +24 -0
- data/Gemfile +5 -0
- data/History.txt +16 -3
- data/Rakefile +4 -50
- data/cucumber.gemspec +36 -600
- data/features/cucumber_cli.feature +1 -1
- data/features/json_formatter.feature +1 -1
- data/features/junit_formatter.feature +10 -6
- data/features/post_configuration_hook.feature +15 -2
- data/features/step_definitions/cucumber_steps.rb +5 -1
- data/features/step_definitions/wire_steps.rb +1 -0
- data/features/support/env.rb +2 -5
- data/features/wire_protocol.feature +1 -1
- data/lib/cucumber.rb +8 -0
- data/lib/cucumber/ast/outline_table.rb +4 -4
- data/lib/cucumber/ast/step_invocation.rb +14 -13
- data/lib/cucumber/ast/table.rb +2 -1
- data/lib/cucumber/ast/tree_walker.rb +3 -3
- data/lib/cucumber/cli/configuration.rb +32 -7
- data/lib/cucumber/cli/main.rb +26 -30
- data/lib/cucumber/cli/options.rb +1 -3
- data/lib/cucumber/cli/profile_loader.rb +2 -0
- data/lib/cucumber/configuration.rb +37 -0
- data/lib/cucumber/errors.rb +40 -0
- data/lib/cucumber/feature_file.rb +5 -12
- data/lib/cucumber/formatter/junit.rb +2 -2
- data/lib/cucumber/formatter/tag_cloud.rb +1 -1
- data/lib/cucumber/js_support/js_dsl.js +4 -4
- data/lib/cucumber/js_support/js_language.rb +9 -5
- data/lib/cucumber/language_support.rb +2 -2
- data/lib/cucumber/parser/gherkin_builder.rb +19 -19
- data/lib/cucumber/platform.rb +3 -4
- data/lib/cucumber/rake/task.rb +1 -7
- data/lib/cucumber/rb_support/rb_dsl.rb +1 -0
- data/lib/cucumber/rb_support/rb_language.rb +1 -0
- data/lib/cucumber/rspec/doubles.rb +3 -3
- data/lib/cucumber/runtime.rb +192 -0
- data/lib/cucumber/runtime/features_loader.rb +62 -0
- data/lib/cucumber/runtime/results.rb +46 -0
- data/lib/cucumber/runtime/support_code.rb +174 -0
- data/lib/cucumber/runtime/user_interface.rb +80 -0
- data/lib/cucumber/step_mother.rb +6 -427
- data/lib/cucumber/wire_support/configuration.rb +2 -0
- data/lib/cucumber/wire_support/wire_language.rb +1 -8
- data/spec/cucumber/ast/background_spec.rb +3 -3
- data/spec/cucumber/ast/feature_spec.rb +2 -2
- data/spec/cucumber/ast/scenario_outline_spec.rb +1 -1
- data/spec/cucumber/ast/scenario_spec.rb +1 -2
- data/spec/cucumber/ast/tree_walker_spec.rb +1 -1
- data/spec/cucumber/cli/configuration_spec.rb +31 -5
- data/spec/cucumber/cli/drb_client_spec.rb +1 -1
- data/spec/cucumber/cli/main_spec.rb +8 -37
- data/spec/cucumber/cli/options_spec.rb +20 -0
- data/spec/cucumber/formatter/spec_helper.rb +5 -7
- data/spec/cucumber/rb_support/rb_language_spec.rb +2 -2
- data/spec/cucumber/rb_support/rb_step_definition_spec.rb +1 -1
- data/spec/cucumber/runtime_spec.rb +294 -0
- data/spec/cucumber/step_match_spec.rb +10 -8
- data/spec/cucumber/world/pending_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -21
- metadata +215 -84
- data/Caliper.yml +0 -4
- data/VERSION.yml +0 -5
- data/spec/cucumber/step_mother_spec.rb +0 -302
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'cucumber/errors'
|
2
|
+
|
3
|
+
module Cucumber
|
4
|
+
class Runtime
|
5
|
+
|
6
|
+
class FeaturesLoader
|
7
|
+
include Formatter::Duration
|
8
|
+
|
9
|
+
def initialize(feature_files, filters, tag_expression)
|
10
|
+
@feature_files, @filters, @tag_expression = feature_files, filters, tag_expression
|
11
|
+
end
|
12
|
+
|
13
|
+
def features
|
14
|
+
load unless @features
|
15
|
+
@features
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def load
|
21
|
+
features = Ast::Features.new
|
22
|
+
|
23
|
+
tag_counts = {}
|
24
|
+
start = Time.new
|
25
|
+
log.debug("Features:\n")
|
26
|
+
@feature_files.each do |f|
|
27
|
+
feature_file = FeatureFile.new(f)
|
28
|
+
feature = feature_file.parse(@filters, tag_counts)
|
29
|
+
if feature
|
30
|
+
features.add_feature(feature)
|
31
|
+
log.debug(" * #{f}\n")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
duration = Time.now - start
|
35
|
+
log.debug("Parsing feature files took #{format_duration(duration)}\n\n")
|
36
|
+
|
37
|
+
check_tag_limits(tag_counts)
|
38
|
+
|
39
|
+
@features = features
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_tag_limits(tag_counts)
|
43
|
+
error_messages = []
|
44
|
+
@tag_expression.limits.each do |tag_name, tag_limit|
|
45
|
+
tag_locations = (tag_counts[tag_name] || [])
|
46
|
+
tag_count = tag_locations.length
|
47
|
+
if tag_count > tag_limit
|
48
|
+
error = "#{tag_name} occurred #{tag_count} times, but the limit was set to #{tag_limit}\n " +
|
49
|
+
tag_locations.join("\n ")
|
50
|
+
error_messages << error
|
51
|
+
end
|
52
|
+
end
|
53
|
+
raise TagExcess.new(error_messages) if error_messages.any?
|
54
|
+
end
|
55
|
+
|
56
|
+
def log
|
57
|
+
Cucumber.logger
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Cucumber
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
class Results
|
5
|
+
def initialize(configuration)
|
6
|
+
@configuration = configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
def step_visited(step) #:nodoc:
|
10
|
+
steps << step unless steps.index(step)
|
11
|
+
end
|
12
|
+
|
13
|
+
def scenario_visited(scenario) #:nodoc:
|
14
|
+
scenarios << scenario unless scenarios.index(scenario)
|
15
|
+
end
|
16
|
+
|
17
|
+
def steps(status = nil) #:nodoc:
|
18
|
+
@steps ||= []
|
19
|
+
if(status)
|
20
|
+
@steps.select{|step| step.status == status}
|
21
|
+
else
|
22
|
+
@steps
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def scenarios(status = nil) #:nodoc:
|
27
|
+
@scenarios ||= []
|
28
|
+
if(status)
|
29
|
+
@scenarios.select{|scenario| scenario.status == status}
|
30
|
+
else
|
31
|
+
@scenarios
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def failure?
|
36
|
+
if @configuration.wip?
|
37
|
+
scenarios(:passed).any?
|
38
|
+
else
|
39
|
+
scenarios(:failed).any? ||
|
40
|
+
(@configuration.strict? && (steps(:undefined).any? || steps(:pending).any?))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'cucumber/constantize'
|
2
|
+
|
3
|
+
module Cucumber
|
4
|
+
class Runtime
|
5
|
+
|
6
|
+
class SupportCode
|
7
|
+
class StepInvoker
|
8
|
+
include Gherkin::Rubify
|
9
|
+
|
10
|
+
def initialize(support_code)
|
11
|
+
@support_code = support_code
|
12
|
+
end
|
13
|
+
|
14
|
+
def uri(uri)
|
15
|
+
end
|
16
|
+
|
17
|
+
def step(step)
|
18
|
+
cucumber_multiline_arg = case(rubify(step.multiline_arg))
|
19
|
+
when Gherkin::Formatter::Model::PyString
|
20
|
+
step.multiline_arg.value
|
21
|
+
when Array
|
22
|
+
Ast::Table.new(step.multiline_arg.map{|row| row.cells})
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
@support_code.invoke(step.name, cucumber_multiline_arg)
|
27
|
+
end
|
28
|
+
|
29
|
+
def eof
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
include Constantize
|
34
|
+
|
35
|
+
def initialize(step_mother, in_guess_mode)
|
36
|
+
@step_mother = step_mother
|
37
|
+
@guess_step_matches = in_guess_mode
|
38
|
+
@unsupported_programming_languages = []
|
39
|
+
@programming_languages = []
|
40
|
+
@language_map = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def invoke_steps(steps_text, i18n, file_colon_line)
|
44
|
+
file, line = file_colon_line.split(':')
|
45
|
+
parser = Gherkin::Parser::Parser.new(StepInvoker.new(self), true, 'steps')
|
46
|
+
parser.parse(steps_text, file, line.to_i)
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_programming_language!(ext)
|
50
|
+
return @language_map[ext] if @language_map[ext]
|
51
|
+
programming_language_class = constantize("Cucumber::#{ext.capitalize}Support::#{ext.capitalize}Language")
|
52
|
+
programming_language = programming_language_class.new(@step_mother)
|
53
|
+
@programming_languages << programming_language
|
54
|
+
@language_map[ext] = programming_language
|
55
|
+
programming_language
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_files!(files)
|
59
|
+
log.debug("Code:\n")
|
60
|
+
files.each do |file|
|
61
|
+
load_file(file)
|
62
|
+
end
|
63
|
+
log.debug("\n")
|
64
|
+
end
|
65
|
+
|
66
|
+
def unmatched_step_definitions
|
67
|
+
@programming_languages.map do |programming_language|
|
68
|
+
programming_language.unmatched_step_definitions
|
69
|
+
end.flatten
|
70
|
+
end
|
71
|
+
|
72
|
+
def snippet_text(step_keyword, step_name, multiline_arg_class) #:nodoc:
|
73
|
+
load_programming_language!('rb') if unknown_programming_language?
|
74
|
+
@programming_languages.map do |programming_language|
|
75
|
+
programming_language.snippet_text(step_keyword, step_name, multiline_arg_class)
|
76
|
+
end.join("\n")
|
77
|
+
end
|
78
|
+
|
79
|
+
def unknown_programming_language?
|
80
|
+
@programming_languages.empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
def fire_hook(name, *args)
|
84
|
+
@programming_languages.each do |programming_language|
|
85
|
+
programming_language.send(name, *args)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def around(scenario, block)
|
90
|
+
@programming_languages.reverse.inject(block) do |blk, programming_language|
|
91
|
+
proc do
|
92
|
+
programming_language.around(scenario) do
|
93
|
+
blk.call(scenario)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end.call
|
97
|
+
end
|
98
|
+
|
99
|
+
def step_match(step_name, name_to_report=nil) #:nodoc:
|
100
|
+
matches = matches(step_name, name_to_report)
|
101
|
+
raise Undefined.new(step_name) if matches.empty?
|
102
|
+
matches = best_matches(step_name, matches) if matches.size > 1 && guess_step_matches?
|
103
|
+
raise Ambiguous.new(step_name, matches, guess_step_matches?) if matches.size > 1
|
104
|
+
matches[0]
|
105
|
+
end
|
106
|
+
|
107
|
+
def invoke(step_name, multiline_argument=nil)
|
108
|
+
begin
|
109
|
+
step_match(step_name).invoke(multiline_argument)
|
110
|
+
rescue Exception => e
|
111
|
+
e.nested! if Undefined === e
|
112
|
+
raise e
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def guess_step_matches?
|
119
|
+
@guess_step_matches
|
120
|
+
end
|
121
|
+
|
122
|
+
def matches(step_name, name_to_report)
|
123
|
+
@programming_languages.map do |programming_language|
|
124
|
+
programming_language.step_matches(step_name, name_to_report).to_a
|
125
|
+
end.flatten
|
126
|
+
end
|
127
|
+
|
128
|
+
def best_matches(step_name, step_matches) #:nodoc:
|
129
|
+
no_groups = step_matches.select {|step_match| step_match.args.length == 0}
|
130
|
+
max_arg_length = step_matches.map {|step_match| step_match.args.length }.max
|
131
|
+
top_groups = step_matches.select {|step_match| step_match.args.length == max_arg_length }
|
132
|
+
|
133
|
+
if no_groups.any?
|
134
|
+
longest_regexp_length = no_groups.map {|step_match| step_match.text_length }.max
|
135
|
+
no_groups.select {|step_match| step_match.text_length == longest_regexp_length }
|
136
|
+
elsif top_groups.any?
|
137
|
+
shortest_capture_length = top_groups.map {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } }.min
|
138
|
+
top_groups.select {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } == shortest_capture_length }
|
139
|
+
else
|
140
|
+
top_groups
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def load_file(file)
|
145
|
+
if programming_language = programming_language_for(file)
|
146
|
+
log.debug(" * #{file}\n")
|
147
|
+
programming_language.load_code_file(file)
|
148
|
+
else
|
149
|
+
log.debug(" * #{file} [NOT SUPPORTED]\n")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def log
|
154
|
+
Cucumber.logger
|
155
|
+
end
|
156
|
+
|
157
|
+
def programming_language_for(step_def_file) #:nodoc:
|
158
|
+
if ext = File.extname(step_def_file)[1..-1]
|
159
|
+
return nil if @unsupported_programming_languages.index(ext)
|
160
|
+
begin
|
161
|
+
load_programming_language!(ext)
|
162
|
+
rescue LoadError => e
|
163
|
+
log.debug("Failed to load '#{ext}' programming language for file #{step_def_file}: #{e.message}\n")
|
164
|
+
@unsupported_programming_languages << ext
|
165
|
+
nil
|
166
|
+
end
|
167
|
+
else
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Cucumber
|
4
|
+
class Runtime
|
5
|
+
|
6
|
+
module UserInterface
|
7
|
+
attr_writer :visitor
|
8
|
+
|
9
|
+
# Output +announcement+ alongside the formatted output.
|
10
|
+
# This is an alternative to using Kernel#puts - it will display
|
11
|
+
# nicer, and in all outputs (in case you use several formatters)
|
12
|
+
#
|
13
|
+
def announce(msg)
|
14
|
+
msg.respond_to?(:join) ? @visitor.announce(msg.join("\n")) : @visitor.announce(msg.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Suspends execution and prompts +question+ to the console (STDOUT).
|
18
|
+
# An operator (manual tester) can then enter a line of text and hit
|
19
|
+
# <ENTER>. The entered text is returned, and both +question+ and
|
20
|
+
# the result is added to the output using #announce.
|
21
|
+
#
|
22
|
+
# If you want a beep to happen (to grab the manual tester's attention),
|
23
|
+
# just prepend ASCII character 7 to the question:
|
24
|
+
#
|
25
|
+
# ask("#{7.chr}How many cukes are in the external system?")
|
26
|
+
#
|
27
|
+
# If that doesn't issue a beep, you can shell out to something else
|
28
|
+
# that makes a sound before invoking #ask.
|
29
|
+
#
|
30
|
+
def ask(question, timeout_seconds)
|
31
|
+
STDOUT.puts(question)
|
32
|
+
STDOUT.flush
|
33
|
+
announce(question)
|
34
|
+
|
35
|
+
if(Cucumber::JRUBY)
|
36
|
+
answer = jruby_gets(timeout_seconds)
|
37
|
+
else
|
38
|
+
answer = mri_gets(timeout_seconds)
|
39
|
+
end
|
40
|
+
|
41
|
+
if(answer)
|
42
|
+
announce(answer)
|
43
|
+
answer
|
44
|
+
else
|
45
|
+
raise("Waited for input for #{timeout_seconds} seconds, then timed out.")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Embed +file+ of MIME type +mime_type+ into the output. This may or may
|
50
|
+
# not be ignored, depending on what kind of formatter(s) are active.
|
51
|
+
#
|
52
|
+
def embed(file, mime_type)
|
53
|
+
@visitor.embed(file, mime_type)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def mri_gets(timeout_seconds)
|
59
|
+
begin
|
60
|
+
Timeout.timeout(timeout_seconds) do
|
61
|
+
STDIN.gets
|
62
|
+
end
|
63
|
+
rescue Timeout::Error => e
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def jruby_gets(timeout_seconds)
|
69
|
+
answer = nil
|
70
|
+
t = java.lang.Thread.new do
|
71
|
+
answer = STDIN.gets
|
72
|
+
end
|
73
|
+
t.start
|
74
|
+
t.join(timeout_seconds * 1000)
|
75
|
+
answer
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
data/lib/cucumber/step_mother.rb
CHANGED
@@ -1,431 +1,10 @@
|
|
1
|
-
require 'cucumber/
|
2
|
-
require 'cucumber/core_ext/instance_exec'
|
3
|
-
require 'cucumber/language_support/language_methods'
|
4
|
-
require 'cucumber/formatter/duration'
|
5
|
-
require 'cucumber/cli/options'
|
6
|
-
require 'timeout'
|
1
|
+
require 'cucumber/runtime'
|
7
2
|
|
8
3
|
module Cucumber
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def initialize(step_name)
|
14
|
-
super %{Undefined step: "#{step_name}"}
|
15
|
-
@step_name = step_name
|
16
|
-
end
|
17
|
-
|
18
|
-
def nested!
|
19
|
-
@nested = true
|
20
|
-
end
|
21
|
-
|
22
|
-
def nested?
|
23
|
-
@nested
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Raised when a StepDefinition's block invokes World#pending
|
28
|
-
class Pending < StandardError
|
29
|
-
end
|
30
|
-
|
31
|
-
# Raised when a step matches 2 or more StepDefinitions
|
32
|
-
class Ambiguous < StandardError
|
33
|
-
def initialize(step_name, step_definitions, used_guess)
|
34
|
-
message = "Ambiguous match of \"#{step_name}\":\n\n"
|
35
|
-
message << step_definitions.map{|sd| sd.backtrace_line}.join("\n")
|
36
|
-
message << "\n\n"
|
37
|
-
message << "You can run again with --guess to make Cucumber be more smart about it\n" unless used_guess
|
38
|
-
super(message)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
class TagExcess < StandardError
|
43
|
-
def initialize(messages)
|
44
|
-
super(messages.join("\n"))
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# This is the meaty part of Cucumber that ties everything together.
|
49
|
-
class StepMother
|
50
|
-
include Constantize
|
51
|
-
include Formatter::Duration
|
52
|
-
attr_writer :options, :visitor, :log
|
53
|
-
|
54
|
-
def initialize
|
55
|
-
@unsupported_programming_languages = []
|
56
|
-
@programming_languages = []
|
57
|
-
@language_map = {}
|
58
|
-
@current_scenario = nil
|
59
|
-
end
|
60
|
-
|
61
|
-
def load_plain_text_features(feature_files)
|
62
|
-
features = Ast::Features.new
|
63
|
-
|
64
|
-
tag_counts = {}
|
65
|
-
start = Time.new
|
66
|
-
log.debug("Features:\n")
|
67
|
-
feature_files.each do |f|
|
68
|
-
feature_file = FeatureFile.new(f)
|
69
|
-
feature = feature_file.parse(options, tag_counts)
|
70
|
-
if feature
|
71
|
-
features.add_feature(feature)
|
72
|
-
log.debug(" * #{f}\n")
|
73
|
-
end
|
74
|
-
end
|
75
|
-
duration = Time.now - start
|
76
|
-
log.debug("Parsing feature files took #{format_duration(duration)}\n\n")
|
77
|
-
|
78
|
-
check_tag_limits(tag_counts)
|
79
|
-
|
80
|
-
features
|
81
|
-
end
|
82
|
-
|
83
|
-
def check_tag_limits(tag_counts)
|
84
|
-
error_messages = []
|
85
|
-
options[:tag_expression].limits.each do |tag_name, tag_limit|
|
86
|
-
tag_locations = (tag_counts[tag_name] || [])
|
87
|
-
tag_count = tag_locations.length
|
88
|
-
if tag_count > tag_limit
|
89
|
-
error = "#{tag_name} occurred #{tag_count} times, but the limit was set to #{tag_limit}\n " +
|
90
|
-
tag_locations.join("\n ")
|
91
|
-
error_messages << error
|
92
|
-
end
|
93
|
-
end
|
94
|
-
raise TagExcess.new(error_messages) if error_messages.any?
|
95
|
-
end
|
96
|
-
|
97
|
-
def load_code_files(step_def_files)
|
98
|
-
log.debug("Code:\n")
|
99
|
-
step_def_files.each do |step_def_file|
|
100
|
-
load_code_file(step_def_file)
|
101
|
-
end
|
102
|
-
log.debug("\n")
|
103
|
-
end
|
104
|
-
|
105
|
-
def load_code_file(step_def_file)
|
106
|
-
if programming_language = programming_language_for(step_def_file)
|
107
|
-
log.debug(" * #{step_def_file}\n")
|
108
|
-
programming_language.load_code_file(step_def_file)
|
109
|
-
else
|
110
|
-
log.debug(" * #{step_def_file} [NOT SUPPORTED]\n")
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# Loads and registers programming language implementation.
|
115
|
-
# Instances are cached, so calling with the same argument
|
116
|
-
# twice will return the same instance.
|
117
|
-
#
|
118
|
-
def load_programming_language(ext)
|
119
|
-
return @language_map[ext] if @language_map[ext]
|
120
|
-
programming_language_class = constantize("Cucumber::#{ext.capitalize}Support::#{ext.capitalize}Language")
|
121
|
-
programming_language = programming_language_class.new(self)
|
122
|
-
@programming_languages << programming_language
|
123
|
-
@language_map[ext] = programming_language
|
124
|
-
programming_language
|
125
|
-
end
|
126
|
-
|
127
|
-
# Returns the options passed on the command line.
|
128
|
-
def options
|
129
|
-
@options ||= Cli::Options.new
|
130
|
-
end
|
131
|
-
|
132
|
-
def step_visited(step) #:nodoc:
|
133
|
-
steps << step unless steps.index(step)
|
134
|
-
end
|
135
|
-
|
136
|
-
def steps(status = nil) #:nodoc:
|
137
|
-
@steps ||= []
|
138
|
-
if(status)
|
139
|
-
@steps.select{|step| step.status == status}
|
140
|
-
else
|
141
|
-
@steps
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Output +announcement+ alongside the formatted output.
|
146
|
-
# This is an alternative to using Kernel#puts - it will display
|
147
|
-
# nicer, and in all outputs (in case you use several formatters)
|
148
|
-
#
|
149
|
-
def announce(msg)
|
150
|
-
msg.respond_to?(:join) ? @visitor.announce(msg.join("\n")) : @visitor.announce(msg.to_s)
|
151
|
-
end
|
152
|
-
|
153
|
-
# Suspends execution and prompts +question+ to the console (STDOUT).
|
154
|
-
# An operator (manual tester) can then enter a line of text and hit
|
155
|
-
# <ENTER>. The entered text is returned, and both +question+ and
|
156
|
-
# the result is added to the output using #announce.
|
157
|
-
#
|
158
|
-
# If you want a beep to happen (to grab the manual tester's attention),
|
159
|
-
# just prepend ASCII character 7 to the question:
|
160
|
-
#
|
161
|
-
# ask("#{7.chr}How many cukes are in the external system?")
|
162
|
-
#
|
163
|
-
# If that doesn't issue a beep, you can shell out to something else
|
164
|
-
# that makes a sound before invoking #ask.
|
165
|
-
#
|
166
|
-
def ask(question, timeout_seconds)
|
167
|
-
STDOUT.puts(question)
|
168
|
-
STDOUT.flush
|
169
|
-
announce(question)
|
170
|
-
|
171
|
-
if(Cucumber::JRUBY)
|
172
|
-
answer = jruby_gets(timeout_seconds)
|
173
|
-
else
|
174
|
-
answer = mri_gets(timeout_seconds)
|
175
|
-
end
|
176
|
-
|
177
|
-
if(answer)
|
178
|
-
announce(answer)
|
179
|
-
answer
|
180
|
-
else
|
181
|
-
raise("Waited for input for #{timeout_seconds} seconds, then timed out.")
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Embed +file+ of MIME type +mime_type+ into the output. This may or may
|
186
|
-
# not be ignored, depending on what kind of formatter(s) are active.
|
187
|
-
#
|
188
|
-
def embed(file, mime_type)
|
189
|
-
@visitor.embed(file, mime_type)
|
190
|
-
end
|
191
|
-
|
192
|
-
def scenarios(status = nil) #:nodoc:
|
193
|
-
@scenarios ||= []
|
194
|
-
if(status)
|
195
|
-
@scenarios.select{|scenario| scenario.status == status}
|
196
|
-
else
|
197
|
-
@scenarios
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def invoke(step_name, multiline_argument=nil)
|
202
|
-
begin
|
203
|
-
step_match(step_name).invoke(multiline_argument)
|
204
|
-
rescue Exception => e
|
205
|
-
e.nested! if Undefined === e
|
206
|
-
raise e
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
# Invokes a series of steps +steps_text+. Example:
|
211
|
-
#
|
212
|
-
# invoke(%Q{
|
213
|
-
# Given I have 8 cukes in my belly
|
214
|
-
# Then I should not be thirsty
|
215
|
-
# })
|
216
|
-
def invoke_steps(steps_text, i18n, file_colon_line)
|
217
|
-
file, line = file_colon_line.split(':')
|
218
|
-
parser = Gherkin::Parser::Parser.new(StepInvoker.new(self), true, 'steps')
|
219
|
-
parser.parse(steps_text, file, line.to_i)
|
220
|
-
end
|
221
|
-
|
222
|
-
class StepInvoker
|
223
|
-
def initialize(step_mother)
|
224
|
-
@step_mother = step_mother
|
225
|
-
end
|
226
|
-
|
227
|
-
def step(statement, multiline_arg, result)
|
228
|
-
cucumber_multiline_arg = case(multiline_arg)
|
229
|
-
when Gherkin::Formatter::Model::PyString
|
230
|
-
multiline_arg.value
|
231
|
-
when Array
|
232
|
-
Ast::Table.new(multiline_arg.map{|row| row.cells})
|
233
|
-
else
|
234
|
-
nil
|
235
|
-
end
|
236
|
-
@step_mother.invoke(*[statement.name, cucumber_multiline_arg].compact)
|
237
|
-
end
|
238
|
-
|
239
|
-
def eof
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
# Returns a Cucumber::Ast::Table for +text_or_table+, which can either
|
244
|
-
# be a String:
|
245
|
-
#
|
246
|
-
# table(%{
|
247
|
-
# | account | description | amount |
|
248
|
-
# | INT-100 | Taxi | 114 |
|
249
|
-
# | CUC-101 | Peeler | 22 |
|
250
|
-
# })
|
251
|
-
#
|
252
|
-
# or a 2D Array:
|
253
|
-
#
|
254
|
-
# table([
|
255
|
-
# %w{ account description amount },
|
256
|
-
# %w{ INT-100 Taxi 114 },
|
257
|
-
# %w{ CUC-101 Peeler 22 }
|
258
|
-
# ])
|
259
|
-
#
|
260
|
-
def table(text_or_table, file=nil, line_offset=0)
|
261
|
-
if Array === text_or_table
|
262
|
-
Ast::Table.new(text_or_table)
|
263
|
-
else
|
264
|
-
Ast::Table.parse(text_or_table, file, line_offset)
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
# Returns a regular String for +string_with_triple_quotes+. Example:
|
269
|
-
#
|
270
|
-
# """
|
271
|
-
# hello
|
272
|
-
# world
|
273
|
-
# """
|
274
|
-
#
|
275
|
-
# Is retured as: " hello\nworld"
|
276
|
-
#
|
277
|
-
def py_string(string_with_triple_quotes, file=nil, line_offset=0)
|
278
|
-
Ast::PyString.parse(string_with_triple_quotes)
|
279
|
-
end
|
280
|
-
|
281
|
-
def step_match(step_name, name_to_report=nil) #:nodoc:
|
282
|
-
matches = @programming_languages.map do |programming_language|
|
283
|
-
programming_language.step_matches(step_name, name_to_report).to_a
|
284
|
-
end.flatten
|
285
|
-
raise Undefined.new(step_name) if matches.empty?
|
286
|
-
matches = best_matches(step_name, matches) if matches.size > 1 && options[:guess]
|
287
|
-
raise Ambiguous.new(step_name, matches, options[:guess]) if matches.size > 1
|
288
|
-
matches[0]
|
289
|
-
end
|
290
|
-
|
291
|
-
def best_matches(step_name, step_matches) #:nodoc:
|
292
|
-
no_groups = step_matches.select {|step_match| step_match.args.length == 0}
|
293
|
-
max_arg_length = step_matches.map {|step_match| step_match.args.length }.max
|
294
|
-
top_groups = step_matches.select {|step_match| step_match.args.length == max_arg_length }
|
295
|
-
|
296
|
-
if no_groups.any?
|
297
|
-
longest_regexp_length = no_groups.map {|step_match| step_match.text_length }.max
|
298
|
-
no_groups.select {|step_match| step_match.text_length == longest_regexp_length }
|
299
|
-
elsif top_groups.any?
|
300
|
-
shortest_capture_length = top_groups.map {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } }.min
|
301
|
-
top_groups.select {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } == shortest_capture_length }
|
302
|
-
else
|
303
|
-
top_groups
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
def unmatched_step_definitions
|
308
|
-
@programming_languages.map do |programming_language|
|
309
|
-
programming_language.unmatched_step_definitions
|
310
|
-
end.flatten
|
311
|
-
end
|
312
|
-
|
313
|
-
def snippet_text(step_keyword, step_name, multiline_arg_class) #:nodoc:
|
314
|
-
load_programming_language('rb') if unknown_programming_language?
|
315
|
-
@programming_languages.map do |programming_language|
|
316
|
-
programming_language.snippet_text(step_keyword, step_name, multiline_arg_class)
|
317
|
-
end.join("\n")
|
318
|
-
end
|
319
|
-
|
320
|
-
def unknown_programming_language?
|
321
|
-
@programming_languages.empty?
|
322
|
-
end
|
323
|
-
|
324
|
-
def with_hooks(scenario, skip_hooks=false)
|
325
|
-
around(scenario, skip_hooks) do
|
326
|
-
before_and_after(scenario, skip_hooks) do
|
327
|
-
yield scenario
|
328
|
-
end
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
def around(scenario, skip_hooks=false, &block) #:nodoc:
|
333
|
-
unless skip_hooks
|
334
|
-
@programming_languages.reverse.inject(block) do |blk, programming_language|
|
335
|
-
proc do
|
336
|
-
programming_language.around(scenario) do
|
337
|
-
blk.call(scenario)
|
338
|
-
end
|
339
|
-
end
|
340
|
-
end.call
|
341
|
-
else
|
342
|
-
yield
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
def before_and_after(scenario, skip_hooks=false) #:nodoc:
|
347
|
-
before(scenario) unless skip_hooks
|
348
|
-
yield scenario
|
349
|
-
after(scenario) unless skip_hooks
|
350
|
-
scenario_visited(scenario)
|
351
|
-
end
|
352
|
-
|
353
|
-
def before(scenario) #:nodoc:
|
354
|
-
return if options[:dry_run] || @current_scenario
|
355
|
-
@current_scenario = scenario
|
356
|
-
@programming_languages.each do |programming_language|
|
357
|
-
programming_language.before(scenario)
|
358
|
-
end
|
359
|
-
end
|
360
|
-
|
361
|
-
def after(scenario) #:nodoc:
|
362
|
-
@current_scenario = nil
|
363
|
-
return if options[:dry_run]
|
364
|
-
@programming_languages.each do |programming_language|
|
365
|
-
programming_language.after(scenario)
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
def after_step #:nodoc:
|
370
|
-
return if options[:dry_run]
|
371
|
-
@programming_languages.each do |programming_language|
|
372
|
-
programming_language.execute_after_step(@current_scenario)
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
def after_configuration(configuration) #:nodoc
|
377
|
-
@programming_languages.each do |programming_language|
|
378
|
-
programming_language.after_configuration(configuration)
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
private
|
383
|
-
|
384
|
-
def programming_language_for(step_def_file) #:nodoc:
|
385
|
-
if ext = File.extname(step_def_file)[1..-1]
|
386
|
-
return nil if @unsupported_programming_languages.index(ext)
|
387
|
-
begin
|
388
|
-
load_programming_language(ext)
|
389
|
-
rescue LoadError => e
|
390
|
-
log.debug("Failed to load '#{ext}' programming language for file #{step_def_file}: #{e.message}\n")
|
391
|
-
@unsupported_programming_languages << ext
|
392
|
-
nil
|
393
|
-
end
|
394
|
-
else
|
395
|
-
nil
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
def max_step_definition_length #:nodoc:
|
400
|
-
@max_step_definition_length ||= step_definitions.map{|step_definition| step_definition.text_length}.max
|
401
|
-
end
|
402
|
-
|
403
|
-
def scenario_visited(scenario) #:nodoc:
|
404
|
-
scenarios << scenario unless scenarios.index(scenario)
|
405
|
-
end
|
406
|
-
|
407
|
-
def log
|
408
|
-
@log ||= Logger.new(STDOUT)
|
409
|
-
end
|
410
|
-
|
411
|
-
def mri_gets(timeout_seconds)
|
412
|
-
begin
|
413
|
-
Timeout.timeout(timeout_seconds) do
|
414
|
-
STDIN.gets
|
415
|
-
end
|
416
|
-
rescue Timeout::Error => e
|
417
|
-
nil
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
def jruby_gets(timeout_seconds)
|
422
|
-
answer = nil
|
423
|
-
t = java.lang.Thread.new do
|
424
|
-
answer = STDIN.gets
|
425
|
-
end
|
426
|
-
t.start
|
427
|
-
t.join(timeout_seconds * 1000)
|
428
|
-
answer
|
4
|
+
class StepMother < Runtime
|
5
|
+
def initialize(*args)
|
6
|
+
warn("StepMother has been deprecated and will be gently put to sleep before the next release. Please use Runtime instead. #{caller[0]}")
|
7
|
+
super
|
429
8
|
end
|
430
9
|
end
|
431
|
-
end
|
10
|
+
end
|