cucumber-core 0.1.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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.md +9 -0
- data/Rakefile +24 -0
- data/cucumber-core.gemspec +32 -0
- data/lib/cucumber/core.rb +37 -0
- data/lib/cucumber/core/ast.rb +13 -0
- data/lib/cucumber/core/ast/background.rb +33 -0
- data/lib/cucumber/core/ast/comment.rb +17 -0
- data/lib/cucumber/core/ast/data_table.rb +326 -0
- data/lib/cucumber/core/ast/describes_itself.rb +16 -0
- data/lib/cucumber/core/ast/doc_string.rb +83 -0
- data/lib/cucumber/core/ast/empty_background.rb +12 -0
- data/lib/cucumber/core/ast/examples_table.rb +95 -0
- data/lib/cucumber/core/ast/feature.rb +62 -0
- data/lib/cucumber/core/ast/location.rb +140 -0
- data/lib/cucumber/core/ast/multiline_argument.rb +33 -0
- data/lib/cucumber/core/ast/names.rb +19 -0
- data/lib/cucumber/core/ast/outline_step.rb +51 -0
- data/lib/cucumber/core/ast/scenario.rb +43 -0
- data/lib/cucumber/core/ast/scenario_outline.rb +44 -0
- data/lib/cucumber/core/ast/step.rb +38 -0
- data/lib/cucumber/core/ast/tag.rb +14 -0
- data/lib/cucumber/core/compiler.rb +136 -0
- data/lib/cucumber/core/gherkin/ast_builder.rb +315 -0
- data/lib/cucumber/core/gherkin/document.rb +20 -0
- data/lib/cucumber/core/gherkin/parser.rb +45 -0
- data/lib/cucumber/core/gherkin/writer.rb +220 -0
- data/lib/cucumber/core/gherkin/writer/helpers.rb +178 -0
- data/lib/cucumber/core/platform.rb +30 -0
- data/lib/cucumber/core/test/case.rb +143 -0
- data/lib/cucumber/core/test/filters.rb +48 -0
- data/lib/cucumber/core/test/filters/tag_filter.rb +110 -0
- data/lib/cucumber/core/test/hook_compiler.rb +109 -0
- data/lib/cucumber/core/test/mapper.rb +56 -0
- data/lib/cucumber/core/test/mapping.rb +67 -0
- data/lib/cucumber/core/test/result.rb +191 -0
- data/lib/cucumber/core/test/runner.rb +149 -0
- data/lib/cucumber/core/test/step.rb +69 -0
- data/lib/cucumber/core/test/timer.rb +31 -0
- data/lib/cucumber/core/version.rb +9 -0
- data/lib/cucumber/initializer.rb +18 -0
- data/spec/capture_warnings.rb +68 -0
- data/spec/coverage.rb +10 -0
- data/spec/cucumber/core/ast/data_table_spec.rb +139 -0
- data/spec/cucumber/core/ast/doc_string_spec.rb +77 -0
- data/spec/cucumber/core/ast/examples_table_spec.rb +87 -0
- data/spec/cucumber/core/ast/location_spec.rb +105 -0
- data/spec/cucumber/core/ast/outline_step_spec.rb +77 -0
- data/spec/cucumber/core/ast/step_spec.rb +44 -0
- data/spec/cucumber/core/compiler_spec.rb +249 -0
- data/spec/cucumber/core/gherkin/parser_spec.rb +182 -0
- data/spec/cucumber/core/gherkin/writer_spec.rb +332 -0
- data/spec/cucumber/core/test/case_spec.rb +416 -0
- data/spec/cucumber/core/test/hook_compiler_spec.rb +78 -0
- data/spec/cucumber/core/test/mapper_spec.rb +68 -0
- data/spec/cucumber/core/test/mapping_spec.rb +103 -0
- data/spec/cucumber/core/test/result_spec.rb +178 -0
- data/spec/cucumber/core/test/runner_spec.rb +265 -0
- data/spec/cucumber/core/test/step_spec.rb +58 -0
- data/spec/cucumber/core/test/timer_spec.rb +13 -0
- data/spec/cucumber/core_spec.rb +419 -0
- data/spec/cucumber/initializer_spec.rb +49 -0
- metadata +221 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
# Detect the platform we're running on so we can tweak behaviour
|
2
|
+
# in various places.
|
3
|
+
require 'rbconfig'
|
4
|
+
|
5
|
+
module Cucumber
|
6
|
+
unless defined?(Cucumber::VERSION)
|
7
|
+
VERSION = '2.0'
|
8
|
+
BINARY = File.expand_path(File.dirname(__FILE__) + '/../../bin/cucumber')
|
9
|
+
LIBDIR = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
10
|
+
JRUBY = defined?(JRUBY_VERSION)
|
11
|
+
IRONRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == "ironruby"
|
12
|
+
WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
|
13
|
+
OS_X = RbConfig::CONFIG['host_os'] =~ /darwin/
|
14
|
+
WINDOWS_MRI = WINDOWS && !JRUBY && !IRONRUBY
|
15
|
+
RAILS = defined?(Rails)
|
16
|
+
RUBY_BINARY = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
|
17
|
+
RUBY_2_0 = RUBY_VERSION =~ /^2\.0/
|
18
|
+
RUBY_1_9 = RUBY_VERSION =~ /^1\.9/
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_accessor :use_full_backtrace
|
22
|
+
|
23
|
+
# @private
|
24
|
+
def file_mode(m, encoding="UTF-8")
|
25
|
+
"#{m}:#{encoding}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
self.use_full_backtrace = false
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'cucumber/initializer'
|
2
|
+
require 'cucumber/core/test/result'
|
3
|
+
|
4
|
+
module Cucumber
|
5
|
+
module Core
|
6
|
+
module Test
|
7
|
+
class Case
|
8
|
+
include Cucumber.initializer(:test_steps, :source, :around_hooks)
|
9
|
+
attr_reader :source
|
10
|
+
|
11
|
+
def initialize(test_steps, source, around_hooks = [])
|
12
|
+
super(test_steps, source, around_hooks)
|
13
|
+
end
|
14
|
+
|
15
|
+
def describe_to(visitor, *args)
|
16
|
+
visitor.test_case(self, *args) do |child_visitor=visitor|
|
17
|
+
compose_around_hooks(child_visitor, *args) do
|
18
|
+
test_steps.each do |test_step|
|
19
|
+
test_step.describe_to(child_visitor, *args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def describe_source_to(visitor, *args)
|
27
|
+
source.each do |node|
|
28
|
+
node.describe_to(visitor, *args)
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_steps(test_steps)
|
34
|
+
self.class.new(test_steps, source, around_hooks)
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_around_hooks(around_hooks)
|
38
|
+
self.class.new(test_steps, source, around_hooks)
|
39
|
+
end
|
40
|
+
|
41
|
+
def name
|
42
|
+
@name ||= NameBuilder.new(self).result
|
43
|
+
end
|
44
|
+
|
45
|
+
def tags
|
46
|
+
@tags ||= TagCollector.new(self).result
|
47
|
+
end
|
48
|
+
|
49
|
+
require 'gherkin/tag_expression'
|
50
|
+
def match_tags?(*expressions)
|
51
|
+
::Gherkin::TagExpression.new(expressions.flatten).evaluate(tags.map {|t| ::Gherkin::Formatter::Model::Tag.new(t.name, t.line) })
|
52
|
+
end
|
53
|
+
|
54
|
+
def match_name?(name_regexp)
|
55
|
+
source.any? { |node| node.respond_to?(:name) && node.name =~ name_regexp }
|
56
|
+
end
|
57
|
+
|
58
|
+
def language
|
59
|
+
feature.language
|
60
|
+
end
|
61
|
+
|
62
|
+
def location
|
63
|
+
source.last.location
|
64
|
+
end
|
65
|
+
|
66
|
+
def match_locations?(queried_locations)
|
67
|
+
return true if source.any? { |s| s.match_locations?(queried_locations) }
|
68
|
+
test_steps.any? { |node| node.match_locations? queried_locations }
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
"<#{self.class}: #{location}>"
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def compose_around_hooks(visitor, *args, &block)
|
78
|
+
around_hooks.reverse.reduce(block) do |continue, hook|
|
79
|
+
-> { hook.describe_to(visitor, *args, &continue) }
|
80
|
+
end.call
|
81
|
+
end
|
82
|
+
|
83
|
+
def feature
|
84
|
+
source.first
|
85
|
+
end
|
86
|
+
|
87
|
+
class NameBuilder
|
88
|
+
attr_reader :result
|
89
|
+
|
90
|
+
def initialize(test_case)
|
91
|
+
test_case.describe_source_to self
|
92
|
+
end
|
93
|
+
|
94
|
+
def feature(*)
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def scenario(scenario)
|
99
|
+
@result = "#{scenario.keyword}: #{scenario.name}"
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def scenario_outline(outline)
|
104
|
+
@result = "#{outline.keyword}: #{outline.name}"
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
def examples_table(table)
|
109
|
+
name = table.name.strip
|
110
|
+
name = table.keyword if name.length == 0
|
111
|
+
@result << ", #{name}"
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
def examples_table_row(row)
|
116
|
+
@result << " (row #{row.number})"
|
117
|
+
self
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class TagCollector
|
122
|
+
attr_reader :result
|
123
|
+
|
124
|
+
def initialize(test_case)
|
125
|
+
@result = []
|
126
|
+
test_case.describe_source_to self
|
127
|
+
end
|
128
|
+
|
129
|
+
[:feature, :scenario, :scenario_outline, :examples_table].each do |node_name|
|
130
|
+
define_method(node_name) do |node|
|
131
|
+
@result += node.tags
|
132
|
+
self
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def examples_table_row(*)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'cucumber/core/test/filters/tag_filter'
|
2
|
+
module Cucumber
|
3
|
+
module Core
|
4
|
+
module Test
|
5
|
+
|
6
|
+
class LocationsFilter
|
7
|
+
def initialize(locations, receiver)
|
8
|
+
@receiver = receiver
|
9
|
+
@locations = locations
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_case(test_case)
|
13
|
+
if test_case.match_locations?(@locations)
|
14
|
+
test_case.describe_to @receiver
|
15
|
+
end
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def done
|
20
|
+
@receiver.done
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class NameFilter
|
26
|
+
include Cucumber.initializer(:name_regexps, :receiver)
|
27
|
+
|
28
|
+
def test_case(test_case)
|
29
|
+
if accept?(test_case)
|
30
|
+
test_case.describe_to(receiver)
|
31
|
+
end
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def done
|
36
|
+
@receiver.done
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def accept?(test_case)
|
43
|
+
name_regexps.empty? || name_regexps.any? { |name_regexp| test_case.match_name?(name_regexp) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Cucumber
|
2
|
+
module Core
|
3
|
+
module Test
|
4
|
+
class TagFilter
|
5
|
+
include Cucumber.initializer(:filter_expressions, :receiver)
|
6
|
+
|
7
|
+
def test_case(test_case)
|
8
|
+
test_cases << test_case
|
9
|
+
if test_case.match_tags?(filter_expressions)
|
10
|
+
test_case.describe_to(receiver)
|
11
|
+
end
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def done
|
16
|
+
tag_limits.enforce(test_cases)
|
17
|
+
receiver.done
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def test_cases
|
23
|
+
@test_cases ||= TestCases.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def tag_limits
|
27
|
+
@tag_limits ||= TagLimits.new(filter_expressions)
|
28
|
+
end
|
29
|
+
|
30
|
+
class TestCases
|
31
|
+
attr_reader :test_cases_by_tag_name
|
32
|
+
private :test_cases_by_tag_name
|
33
|
+
def initialize
|
34
|
+
@test_cases_by_tag_name = Hash.new { [] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def <<(test_case)
|
38
|
+
test_case.tags.each do |tag|
|
39
|
+
test_cases_by_tag_name[tag.name] += [test_case]
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def with_tag_name(tag_name)
|
45
|
+
test_cases_by_tag_name[tag_name]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class TagLimits
|
50
|
+
TAG_MATCHER = /^
|
51
|
+
(?:~)? #The tag negation symbol "~". This is optional and not captured.
|
52
|
+
(?<tag_name>\@[\w\d]+) #Captures the tag name including the "@" symbol.
|
53
|
+
\: #The seperator, ":", between the tag name and the limit.
|
54
|
+
(?<limit>\d+) #Caputres the limit number.
|
55
|
+
$/x
|
56
|
+
|
57
|
+
attr_reader :limit_list
|
58
|
+
private :limit_list
|
59
|
+
def initialize(filter_expressions)
|
60
|
+
@limit_list = Array(filter_expressions).flat_map do |raw_expression|
|
61
|
+
raw_expression.split(/\s*,\s*/)
|
62
|
+
end.map do |filter_expression|
|
63
|
+
TAG_MATCHER.match(filter_expression)
|
64
|
+
end.compact.each_with_object({}) do |matchdata, limit_list|
|
65
|
+
limit_list[matchdata[:tag_name]] = Integer(matchdata[:limit])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def enforce(test_cases)
|
70
|
+
limit_breaches = limit_list.reduce([]) do |breaches, (tag_name, limit)|
|
71
|
+
tag_count = test_cases.with_tag_name(tag_name).count
|
72
|
+
if tag_count > limit
|
73
|
+
tag_locations = test_cases.with_tag_name(tag_name).map(&:location)
|
74
|
+
breaches << TagLimitBreach.new(
|
75
|
+
tag_count,
|
76
|
+
limit,
|
77
|
+
tag_name,
|
78
|
+
tag_locations
|
79
|
+
)
|
80
|
+
end
|
81
|
+
breaches
|
82
|
+
end
|
83
|
+
raise TagExcess.new(limit_breaches) if limit_breaches.any?
|
84
|
+
self
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
TagLimitBreach = Struct.new(
|
89
|
+
:tag_count,
|
90
|
+
:tag_limit,
|
91
|
+
:tag_name,
|
92
|
+
:tag_locations
|
93
|
+
) do
|
94
|
+
|
95
|
+
def message
|
96
|
+
"#{tag_name} occurred #{tag_count} times, but the limit was set to #{tag_limit}\n " +
|
97
|
+
tag_locations.map(&:to_s).join("\n ")
|
98
|
+
end
|
99
|
+
alias :to_s :message
|
100
|
+
end
|
101
|
+
|
102
|
+
class TagExcess < StandardError
|
103
|
+
def initialize(limit_breaches)
|
104
|
+
super(limit_breaches.map(&:to_s).join("\n"))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'cucumber/initializer'
|
2
|
+
|
3
|
+
module Cucumber
|
4
|
+
module Core
|
5
|
+
module Test
|
6
|
+
class HookStep
|
7
|
+
def initialize(source, &block)
|
8
|
+
@mapping = Test::Mapping.new(&block)
|
9
|
+
@source = source
|
10
|
+
end
|
11
|
+
|
12
|
+
def describe_to(visitor, *args)
|
13
|
+
visitor.test_step(self, *args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def describe_source_to(visitor, *args)
|
17
|
+
visitor.hook(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute
|
21
|
+
@mapping.execute
|
22
|
+
end
|
23
|
+
|
24
|
+
def skip
|
25
|
+
execute
|
26
|
+
end
|
27
|
+
|
28
|
+
def map
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def match_locations?(locations)
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
"#{self.class}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class HookCompiler
|
42
|
+
include Cucumber.initializer(:mappings, :receiver)
|
43
|
+
|
44
|
+
def done
|
45
|
+
receiver.done
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_case(test_case, &descend)
|
50
|
+
@before_hooks, @after_hooks, @around_hooks, @steps = [], [], [], []
|
51
|
+
mapper = HookMapper.new(self, test_case.source)
|
52
|
+
test_case.describe_to mappings, mapper
|
53
|
+
descend.call
|
54
|
+
test_case.
|
55
|
+
with_steps(@before_hooks + @steps + @after_hooks).
|
56
|
+
with_around_hooks(@around_hooks).
|
57
|
+
describe_to(receiver)
|
58
|
+
end
|
59
|
+
|
60
|
+
def before_hook(hook)
|
61
|
+
@before_hooks << hook
|
62
|
+
end
|
63
|
+
|
64
|
+
def after_hook(hook)
|
65
|
+
@after_hooks << hook
|
66
|
+
end
|
67
|
+
|
68
|
+
def around_hook(hook)
|
69
|
+
@around_hooks << hook
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_step(step)
|
73
|
+
@steps << step
|
74
|
+
end
|
75
|
+
|
76
|
+
class HookMapper
|
77
|
+
include Cucumber.initializer(:compiler, :source)
|
78
|
+
|
79
|
+
def before(&block)
|
80
|
+
compiler.before_hook HookStep.new(source, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def after(&block)
|
84
|
+
compiler.after_hook HookStep.new(source, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def around(&block)
|
88
|
+
compiler.around_hook AroundHook.new(source, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
class AroundHook
|
92
|
+
def initialize(source, &block)
|
93
|
+
@source = source
|
94
|
+
@block = block
|
95
|
+
end
|
96
|
+
|
97
|
+
def call(continue)
|
98
|
+
@block.call(continue)
|
99
|
+
end
|
100
|
+
|
101
|
+
def describe_to(visitor, *args, &continue)
|
102
|
+
visitor.around_hook(self, *args, &continue)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'cucumber/initializer'
|
2
|
+
module Cucumber
|
3
|
+
module Core
|
4
|
+
module Test
|
5
|
+
class Mapper
|
6
|
+
include Cucumber.initializer(:mappings, :runner)
|
7
|
+
|
8
|
+
def test_case(test_case, &descend)
|
9
|
+
mapper = CaseMapper.new(mappings)
|
10
|
+
descend.call(mapper)
|
11
|
+
test_case.with_steps(mapper.test_steps).describe_to(runner)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def done
|
16
|
+
runner.done
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
class CaseMapper
|
21
|
+
include Cucumber.initializer(:mappings)
|
22
|
+
|
23
|
+
attr_reader :test_steps
|
24
|
+
|
25
|
+
def initialize(*)
|
26
|
+
super
|
27
|
+
@test_steps = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_step(test_step)
|
31
|
+
mapper = StepMapper.new(test_step)
|
32
|
+
test_step.describe_to(mappings, mapper)
|
33
|
+
test_steps << mapper.mapped_test_step
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
class StepMapper
|
39
|
+
include Cucumber.initializer(:test_step)
|
40
|
+
|
41
|
+
attr_reader :mapped_test_step
|
42
|
+
|
43
|
+
def initialize(*)
|
44
|
+
super
|
45
|
+
@mapped_test_step = test_step
|
46
|
+
end
|
47
|
+
|
48
|
+
def map(&block)
|
49
|
+
@mapped_test_step = test_step.map(&block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|