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