cucumber 0.8.6 → 0.8.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -1
- data/Caliper.yml +4 -0
- data/History.txt +1557 -0
- data/LICENSE +1 -1
- data/README.rdoc +26 -0
- data/Rakefile +51 -5
- data/VERSION.yml +5 -0
- data/bin/cucumber +1 -7
- data/cucumber.gemspec +77 -3
- data/examples/i18n/ar/features/step_definitons/calculator_steps.rb +1 -1
- data/examples/i18n/he/features/step_definitons/calculator_steps.rb +1 -1
- data/examples/i18n/ro/features/step_definitons/calculator_steps.rb +7 -4
- data/examples/i18n/ro/features/suma.feature +11 -0
- data/examples/i18n/ru/features/division.feature +2 -2
- data/examples/i18n/tr/features/step_definitons/hesap_makinesi_adimlari.rb +3 -3
- data/examples/sinatra/features/support/env.rb +5 -2
- data/examples/v8/features/fibonacci.feature +1 -1
- data/examples/watir/features/step_definitions/search_steps.rb +1 -1
- data/features/announce.feature +164 -0
- data/features/around_hooks.feature +232 -0
- data/features/background.feature +95 -284
- data/features/bug_371.feature +32 -0
- data/features/bug_464.feature +16 -0
- data/features/bug_475.feature +42 -0
- data/features/bug_585_tab_indentation.feature +22 -0
- data/features/bug_600.feature +67 -0
- data/features/call_steps_from_stepdefs.feature +154 -0
- data/features/cucumber_cli.feature +591 -0
- data/features/cucumber_cli_outlines.feature +117 -0
- data/features/custom_formatter.feature +73 -3
- data/features/default_snippets.feature +42 -0
- data/features/diffing.feature +25 -0
- data/features/drb_server_integration.feature +174 -0
- data/features/exception_in_after_block.feature +127 -0
- data/features/exception_in_after_step_block.feature +104 -0
- data/features/exception_in_before_block.feature +98 -0
- data/features/exclude_files.feature +20 -0
- data/features/expand.feature +60 -0
- data/features/html_formatter.feature +8 -0
- data/features/html_formatter/a.html +582 -0
- data/features/json_formatter.feature +245 -160
- data/features/junit_formatter.feature +88 -0
- data/features/language_from_header.feature +30 -0
- data/features/language_help.feature +78 -0
- data/features/listener_debugger_formatter.feature +42 -0
- data/features/multiline_names.feature +44 -0
- data/features/negative_tagged_hooks.feature +60 -0
- data/features/post_configuration_hook.feature +37 -0
- data/features/profiles.feature +126 -0
- data/features/rake_task.feature +152 -0
- data/features/report_called_undefined_steps.feature +34 -0
- data/features/rerun_formatter.feature +45 -0
- data/features/simplest.feature +11 -0
- data/features/snippet.feature +23 -0
- data/features/snippets_when_using_star_keyword.feature +36 -0
- data/features/step_definitions/cucumber_steps.rb +153 -7
- data/features/step_definitions/extra_steps.rb +2 -0
- data/features/step_definitions/simplest_steps.rb +3 -0
- data/features/step_definitions/wire_steps.rb +32 -0
- data/features/support/env.rb +140 -18
- data/features/support/env.rb.simplest +7 -0
- data/features/support/fake_wire_server.rb +77 -0
- data/features/table_diffing.feature +45 -0
- data/features/table_mapping.feature +34 -0
- data/features/tag_logic.feature +258 -0
- data/features/transform.feature +245 -0
- data/features/unicode_table.feature +35 -0
- data/features/usage_and_stepdefs_formatter.feature +169 -0
- data/features/wire_protocol.feature +332 -0
- data/features/wire_protocol_table_diffing.feature +119 -0
- data/features/wire_protocol_tags.feature +87 -0
- data/features/wire_protocol_timeouts.feature +63 -0
- data/features/work_in_progress.feature +156 -0
- data/fixtures/json/features/pystring.feature +8 -0
- data/fixtures/junit/features/pending.feature +1 -3
- data/fixtures/self_test/features/background/background_tagged_before_on_outline.feature +12 -0
- data/fixtures/self_test/features/background/background_with_name.feature +7 -0
- data/fixtures/self_test/features/background/failing_background.feature +12 -0
- data/fixtures/self_test/features/background/failing_background_after_success.feature +11 -0
- data/fixtures/self_test/features/background/multiline_args_background.feature +32 -0
- data/fixtures/self_test/features/background/passing_background.feature +10 -0
- data/fixtures/self_test/features/background/pending_background.feature +10 -0
- data/fixtures/self_test/features/background/scenario_outline_failing_background.feature +16 -0
- data/fixtures/self_test/features/background/scenario_outline_passing_background.feature +16 -0
- data/fixtures/self_test/features/support/env.rb +0 -8
- data/fixtures/tickets/features.html +1 -1
- data/gem_tasks/examples.rake +1 -1
- data/gem_tasks/features.rake +14 -0
- data/gem_tasks/sdoc.rake +12 -0
- data/lib/cucumber.rb +0 -12
- data/lib/cucumber/ast.rb +1 -1
- data/lib/cucumber/ast/background.rb +5 -21
- data/lib/cucumber/ast/examples.rb +4 -12
- data/lib/cucumber/ast/feature.rb +5 -13
- data/lib/cucumber/ast/feature_element.rb +4 -9
- data/lib/cucumber/ast/outline_table.rb +4 -4
- data/lib/cucumber/ast/py_string.rb +80 -0
- data/lib/cucumber/ast/scenario.rb +5 -7
- data/lib/cucumber/ast/scenario_outline.rb +15 -23
- data/lib/cucumber/ast/step.rb +0 -5
- data/lib/cucumber/ast/step_invocation.rb +15 -21
- data/lib/cucumber/ast/table.rb +8 -14
- data/lib/cucumber/ast/tree_walker.rb +48 -10
- data/lib/cucumber/cli/configuration.rb +8 -33
- data/lib/cucumber/cli/main.rb +35 -20
- data/lib/cucumber/cli/options.rb +7 -8
- data/lib/cucumber/cli/profile_loader.rb +0 -2
- data/lib/cucumber/core_ext/proc.rb +1 -2
- data/lib/cucumber/feature_file.rb +15 -47
- data/lib/cucumber/formatter/ansicolor.rb +5 -3
- data/lib/cucumber/formatter/color_io.rb +23 -0
- data/lib/cucumber/formatter/console.rb +23 -27
- data/lib/cucumber/formatter/cucumber.css +17 -34
- data/lib/cucumber/formatter/cucumber.sass +182 -173
- data/lib/cucumber/formatter/html.rb +11 -46
- data/lib/cucumber/formatter/io.rb +4 -2
- data/lib/cucumber/formatter/json.rb +152 -15
- data/lib/cucumber/formatter/json_pretty.rb +6 -5
- data/lib/cucumber/formatter/junit.rb +22 -28
- data/lib/cucumber/formatter/pdf.rb +6 -6
- data/lib/cucumber/formatter/pretty.rb +5 -5
- data/lib/cucumber/formatter/rerun.rb +11 -22
- data/lib/cucumber/formatter/tag_cloud.rb +35 -0
- data/lib/cucumber/formatter/unicode.rb +20 -41
- data/lib/cucumber/js_support/js_dsl.js +4 -4
- data/lib/cucumber/js_support/js_language.rb +5 -9
- data/lib/cucumber/js_support/js_snippets.rb +2 -2
- data/lib/cucumber/language_support.rb +2 -2
- data/lib/cucumber/parser/gherkin_builder.rb +30 -35
- data/lib/cucumber/platform.rb +8 -8
- data/lib/cucumber/py_support/py_language.rb +2 -2
- data/lib/cucumber/rake/task.rb +31 -74
- data/lib/cucumber/rb_support/rb_dsl.rb +0 -1
- data/lib/cucumber/rb_support/rb_language.rb +8 -10
- data/lib/cucumber/rb_support/rb_step_definition.rb +0 -8
- data/lib/cucumber/rb_support/rb_transform.rb +0 -17
- data/lib/cucumber/rb_support/rb_world.rb +18 -26
- data/lib/cucumber/rspec/doubles.rb +3 -3
- data/lib/cucumber/step_match.rb +2 -6
- data/lib/cucumber/step_mother.rb +427 -6
- data/lib/cucumber/wire_support/configuration.rb +1 -4
- data/lib/cucumber/wire_support/wire_language.rb +10 -3
- data/spec/cucumber/ast/background_spec.rb +6 -68
- data/spec/cucumber/ast/feature_factory.rb +4 -5
- data/spec/cucumber/ast/feature_spec.rb +4 -4
- data/spec/cucumber/ast/outline_table_spec.rb +1 -1
- data/spec/cucumber/ast/py_string_spec.rb +40 -0
- data/spec/cucumber/ast/scenario_outline_spec.rb +11 -15
- data/spec/cucumber/ast/scenario_spec.rb +4 -4
- data/spec/cucumber/ast/step_spec.rb +3 -3
- data/spec/cucumber/ast/table_spec.rb +2 -38
- data/spec/cucumber/ast/tree_walker_spec.rb +2 -2
- data/spec/cucumber/broadcaster_spec.rb +1 -1
- data/spec/cucumber/cli/configuration_spec.rb +6 -32
- data/spec/cucumber/cli/drb_client_spec.rb +3 -2
- data/spec/cucumber/cli/main_spec.rb +43 -43
- data/spec/cucumber/cli/options_spec.rb +1 -28
- data/spec/cucumber/cli/profile_loader_spec.rb +1 -1
- data/spec/cucumber/core_ext/proc_spec.rb +1 -1
- data/spec/cucumber/formatter/ansicolor_spec.rb +1 -1
- data/spec/cucumber/formatter/color_io_spec.rb +29 -0
- data/spec/cucumber/formatter/duration_spec.rb +1 -1
- data/spec/cucumber/formatter/html_spec.rb +5 -3
- data/spec/cucumber/formatter/junit_spec.rb +2 -16
- data/spec/cucumber/formatter/progress_spec.rb +1 -1
- data/spec/cucumber/formatter/spec_helper.rb +12 -11
- data/spec/cucumber/rb_support/rb_language_spec.rb +28 -241
- data/spec/cucumber/rb_support/rb_step_definition_spec.rb +28 -33
- data/spec/cucumber/rb_support/regexp_argument_matcher_spec.rb +1 -1
- data/spec/cucumber/step_match_spec.rb +9 -11
- data/spec/cucumber/step_mother_spec.rb +302 -0
- data/spec/cucumber/wire_support/configuration_spec.rb +1 -1
- data/spec/cucumber/wire_support/connection_spec.rb +1 -1
- data/spec/cucumber/wire_support/wire_exception_spec.rb +1 -1
- data/spec/cucumber/wire_support/wire_language_spec.rb +1 -1
- data/spec/cucumber/wire_support/wire_packet_spec.rb +1 -1
- data/spec/cucumber/wire_support/wire_step_definition_spec.rb +1 -1
- data/spec/cucumber/world/pending_spec.rb +2 -2
- data/spec/spec_helper.rb +20 -13
- metadata +78 -4
@@ -88,7 +88,6 @@ module Cucumber
|
|
88
88
|
|
89
89
|
# Registers a proc that will run after Cucumber is configured. You can register as
|
90
90
|
# as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
|
91
|
-
# TODO: Deprecate this
|
92
91
|
def AfterConfiguration(&proc)
|
93
92
|
RbDsl.register_rb_hook('after_configuration', [], proc)
|
94
93
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'cucumber/core_ext/instance_exec'
|
2
1
|
require 'cucumber/rb_support/rb_dsl'
|
3
2
|
require 'cucumber/rb_support/rb_world'
|
4
3
|
require 'cucumber/rb_support/rb_step_definition'
|
@@ -22,7 +21,7 @@ module Cucumber
|
|
22
21
|
message << first_proc.backtrace_line('World') << "\n"
|
23
22
|
message << second_proc.backtrace_line('World') << "\n\n"
|
24
23
|
message << "Use Ruby modules instead to extend your worlds. See the Cucumber::RbSupport::RbDsl#World RDoc\n"
|
25
|
-
message << "or http://wiki.github.com/
|
24
|
+
message << "or http://wiki.github.com/aslakhellesoy/cucumber/a-whole-new-world.\n\n"
|
26
25
|
super(message)
|
27
26
|
end
|
28
27
|
end
|
@@ -30,8 +29,7 @@ module Cucumber
|
|
30
29
|
# The Ruby implementation of the programming language API.
|
31
30
|
class RbLanguage
|
32
31
|
include LanguageSupport::LanguageMethods
|
33
|
-
attr_reader :current_world
|
34
|
-
:step_definitions
|
32
|
+
attr_reader :current_world
|
35
33
|
|
36
34
|
Gherkin::I18n.code_keywords.each do |adverb|
|
37
35
|
RbDsl.alias_adverb(adverb)
|
@@ -67,7 +65,7 @@ module Cucumber
|
|
67
65
|
|
68
66
|
# Gets called for each file under features (or whatever is overridden
|
69
67
|
# with --require).
|
70
|
-
def step_definitions_for(rb_file)
|
68
|
+
def step_definitions_for(rb_file)
|
71
69
|
begin
|
72
70
|
require rb_file # This will cause self.add_step_definition and self.add_hook to be called from RbDsl
|
73
71
|
step_definitions
|
@@ -78,7 +76,7 @@ module Cucumber
|
|
78
76
|
@step_definitions = nil
|
79
77
|
end
|
80
78
|
end
|
81
|
-
|
79
|
+
|
82
80
|
def step_matches(name_to_match, name_to_format)
|
83
81
|
@step_definitions.map do |step_definition|
|
84
82
|
if(arguments = step_definition.arguments_from(name_to_match))
|
@@ -91,7 +89,7 @@ module Cucumber
|
|
91
89
|
|
92
90
|
ARGUMENT_PATTERNS = ['"([^"]*)"', '(\d+)']
|
93
91
|
|
94
|
-
def snippet_text(
|
92
|
+
def snippet_text(step_keyword, step_name, multiline_arg_class)
|
95
93
|
snippet_pattern = Regexp.escape(step_name).gsub('\ ', ' ').gsub('/', '\/')
|
96
94
|
arg_count = 0
|
97
95
|
ARGUMENT_PATTERNS.each do |pattern|
|
@@ -107,7 +105,7 @@ module Cucumber
|
|
107
105
|
multiline_class_comment = "# #{multiline_arg_class.default_arg_name} is a #{multiline_arg_class.to_s}\n "
|
108
106
|
end
|
109
107
|
|
110
|
-
"#{
|
108
|
+
"#{Gherkin::I18n.code_keyword_for(step_keyword)} /^#{snippet_pattern}$/ do#{block_arg_string}\n #{multiline_class_comment}pending # express the regexp above with the code you wish you had\nend"
|
111
109
|
end
|
112
110
|
|
113
111
|
def begin_rb_scenario(scenario)
|
@@ -140,9 +138,9 @@ module Cucumber
|
|
140
138
|
end
|
141
139
|
|
142
140
|
def load_code_file(code_file)
|
143
|
-
|
141
|
+
require File.expand_path(code_file) # This will cause self.add_step_definition, self.add_hook, and self.add_transform to be called from RbDsl
|
144
142
|
end
|
145
|
-
|
143
|
+
|
146
144
|
protected
|
147
145
|
|
148
146
|
def begin_scenario(scenario)
|
@@ -38,14 +38,6 @@ module Cucumber
|
|
38
38
|
@regexp.inspect
|
39
39
|
end
|
40
40
|
|
41
|
-
def to_hash
|
42
|
-
flags = ''
|
43
|
-
flags += 'm' if (@regexp.options & Regexp::MULTILINE) != 0
|
44
|
-
flags += 'i' if (@regexp.options & Regexp::IGNORECASE) != 0
|
45
|
-
flags += 'x' if (@regexp.options & Regexp::EXTENDED) != 0
|
46
|
-
{'source' => @regexp.source, 'flags' => flags}
|
47
|
-
end
|
48
|
-
|
49
41
|
def ==(step_definition)
|
50
42
|
regexp_source == step_definition.regexp_source
|
51
43
|
end
|
@@ -32,23 +32,6 @@ module Cucumber
|
|
32
32
|
@rb_language.current_world.cucumber_instance_exec(true, @regexp.inspect, *args, &@proc)
|
33
33
|
end
|
34
34
|
end
|
35
|
-
|
36
|
-
def to_s
|
37
|
-
strip_captures(strip_anchors(@regexp.source))
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def strip_captures(regexp_source)
|
43
|
-
regexp_source.
|
44
|
-
gsub(/(\()/, '').
|
45
|
-
gsub(/(\))/, '')
|
46
|
-
end
|
47
|
-
|
48
|
-
def strip_anchors(regexp_source)
|
49
|
-
regexp_source.
|
50
|
-
gsub(/(^\^|\$$)/, '')
|
51
|
-
end
|
52
35
|
end
|
53
36
|
end
|
54
37
|
end
|
@@ -1,11 +1,7 @@
|
|
1
|
-
require 'gherkin/formatter/ansi_escapes'
|
2
|
-
|
3
1
|
module Cucumber
|
4
2
|
module RbSupport
|
5
3
|
# All steps are run in the context of an object that extends this module.
|
6
4
|
module RbWorld
|
7
|
-
include Gherkin::Formatter::AnsiEscapes
|
8
|
-
|
9
5
|
class << self
|
10
6
|
def alias_adverb(adverb)
|
11
7
|
alias_method adverb, :__cucumber_invoke
|
@@ -36,19 +32,14 @@ module Cucumber
|
|
36
32
|
@__cucumber_step_mother.table(text_or_table, file, line_offset)
|
37
33
|
end
|
38
34
|
|
39
|
-
# See StepMother#
|
40
|
-
def
|
41
|
-
@__cucumber_step_mother.
|
42
|
-
end
|
43
|
-
|
44
|
-
def announce(*messages)
|
45
|
-
STDERR.puts failed + "WARNING: #announce is deprecated. Use #puts instead:" + caller[0] + reset
|
46
|
-
puts(*messages)
|
35
|
+
# See StepMother#py_string
|
36
|
+
def py_string(string_with_triple_quotes, file=nil, line_offset=0)
|
37
|
+
@__cucumber_step_mother.py_string(string_with_triple_quotes, file, line_offset)
|
47
38
|
end
|
48
39
|
|
49
|
-
# See StepMother#
|
50
|
-
def
|
51
|
-
@__cucumber_step_mother.
|
40
|
+
# See StepMother#announce
|
41
|
+
def announce(announcement)
|
42
|
+
@__cucumber_step_mother.announce(announcement)
|
52
43
|
end
|
53
44
|
|
54
45
|
# See StepMother#ask
|
@@ -57,8 +48,17 @@ module Cucumber
|
|
57
48
|
end
|
58
49
|
|
59
50
|
# See StepMother#embed
|
60
|
-
def embed(file, mime_type
|
61
|
-
@__cucumber_step_mother.embed(file, mime_type
|
51
|
+
def embed(file, mime_type)
|
52
|
+
@__cucumber_step_mother.embed(file, mime_type)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Prints out the world class, followed by all included modules.
|
56
|
+
def announce_world
|
57
|
+
announce "WORLD:\n #{self.class}"
|
58
|
+
world = self
|
59
|
+
(class << self; self; end).instance_eval do
|
60
|
+
world.announce " #{included_modules.join("\n ")}"
|
61
|
+
end
|
62
62
|
end
|
63
63
|
|
64
64
|
# Mark the matched step as pending.
|
@@ -88,15 +88,7 @@ module Cucumber
|
|
88
88
|
# such errors in World we define it to just return a simple String.
|
89
89
|
#
|
90
90
|
def inspect #:nodoc:
|
91
|
-
|
92
|
-
(class << self; self; end).instance_eval do
|
93
|
-
modules += included_modules
|
94
|
-
end
|
95
|
-
sprintf("#<%s:0x%x>", modules.join('+'), self.object_id)
|
96
|
-
end
|
97
|
-
|
98
|
-
def to_s
|
99
|
-
inspect
|
91
|
+
sprintf("#<%s:0x%x>", self.class, self.object_id)
|
100
92
|
end
|
101
93
|
end
|
102
94
|
end
|
@@ -4,13 +4,13 @@ RSpec.configuration.configure_mock_framework
|
|
4
4
|
World(RSpec::Core::MockFrameworkAdapter)
|
5
5
|
|
6
6
|
Before do
|
7
|
-
|
7
|
+
_setup_mocks
|
8
8
|
end
|
9
9
|
|
10
10
|
After do
|
11
11
|
begin
|
12
|
-
|
12
|
+
_verify_mocks
|
13
13
|
ensure
|
14
|
-
|
14
|
+
_teardown_mocks
|
15
15
|
end
|
16
16
|
end
|
data/lib/cucumber/step_match.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Cucumber
|
2
2
|
class StepMatch #:nodoc:
|
3
|
-
attr_reader :step_definition
|
3
|
+
attr_reader :step_definition
|
4
4
|
|
5
5
|
# Creates a new StepMatch. The +name_to_report+ argument is what's reported, unless it's is,
|
6
6
|
# in which case +name_to_report+ is used instead.
|
@@ -20,7 +20,7 @@ module Cucumber
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def invoke(multiline_arg)
|
23
|
-
multiline_arg = Ast::
|
23
|
+
multiline_arg = Ast::PyString.new(multiline_arg) if String === multiline_arg
|
24
24
|
all_args = args
|
25
25
|
all_args << multiline_arg.to_step_definition_arg if multiline_arg
|
26
26
|
@step_definition.invoke(all_args)
|
@@ -107,9 +107,5 @@ module Cucumber
|
|
107
107
|
def text_length
|
108
108
|
@step.text_length
|
109
109
|
end
|
110
|
-
|
111
|
-
def step_arguments
|
112
|
-
[]
|
113
|
-
end
|
114
110
|
end
|
115
111
|
end
|
data/lib/cucumber/step_mother.rb
CHANGED
@@ -1,10 +1,431 @@
|
|
1
|
-
require 'cucumber/
|
1
|
+
require 'cucumber/constantize'
|
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'
|
2
7
|
|
3
8
|
module Cucumber
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
9
|
+
# Raised when there is no matching StepDefinition for a step.
|
10
|
+
class Undefined < StandardError
|
11
|
+
attr_reader :step_name
|
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
|
8
429
|
end
|
9
430
|
end
|
10
|
-
end
|
431
|
+
end
|