cucumber-core 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|