cucumber 0.8.7 → 0.9.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.
- 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
|