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,20 @@
|
|
1
|
+
require 'cucumber/initializer'
|
2
|
+
|
3
|
+
module Cucumber
|
4
|
+
module Core
|
5
|
+
module Gherkin
|
6
|
+
class Document
|
7
|
+
include Cucumber.initializer(:uri, :body)
|
8
|
+
attr_reader :uri, :body
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
body
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
to_s == other.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'cucumber/core/gherkin/ast_builder'
|
2
|
+
require 'gherkin/parser/parser'
|
3
|
+
|
4
|
+
module Cucumber
|
5
|
+
module Core
|
6
|
+
module Gherkin
|
7
|
+
ParseError = Class.new(StandardError)
|
8
|
+
|
9
|
+
class Parser
|
10
|
+
include Cucumber.initializer(:receiver)
|
11
|
+
|
12
|
+
def document(document)
|
13
|
+
builder = AstBuilder.new(document.uri)
|
14
|
+
parser = ::Gherkin::Parser::Parser.new(builder, true, "root", false)
|
15
|
+
|
16
|
+
begin
|
17
|
+
parser.parse(document.body, document.uri, 0)
|
18
|
+
builder.language = parser.i18n_language
|
19
|
+
receiver.feature builder.result
|
20
|
+
rescue *PARSER_ERRORS => e
|
21
|
+
raise Core::Gherkin::ParseError.new("#{document.uri}: #{e.message}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def done
|
26
|
+
receiver.done
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
PARSER_ERRORS = if Cucumber::JRUBY
|
33
|
+
[
|
34
|
+
::Java::GherkinLexer::LexingError
|
35
|
+
]
|
36
|
+
else
|
37
|
+
[
|
38
|
+
::Gherkin::Lexer::LexingError,
|
39
|
+
::Gherkin::Parser::ParseError,
|
40
|
+
]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'cucumber/core/gherkin/writer/helpers'
|
2
|
+
require 'cucumber/core/gherkin/document'
|
3
|
+
|
4
|
+
module Cucumber
|
5
|
+
module Core
|
6
|
+
module Gherkin
|
7
|
+
|
8
|
+
module Writer
|
9
|
+
NEW_LINE = ''
|
10
|
+
def gherkin(uri = nil, &source)
|
11
|
+
uri ||= 'features/test.feature'
|
12
|
+
builder = Gherkin.new(uri, &source)
|
13
|
+
builder.build
|
14
|
+
end
|
15
|
+
|
16
|
+
class Gherkin
|
17
|
+
def initialize(uri, &source)
|
18
|
+
@uri, @source = uri, source
|
19
|
+
end
|
20
|
+
|
21
|
+
def comment(line)
|
22
|
+
comment_lines << "# #{line}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def comment_lines
|
26
|
+
@comment_lines ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def feature(*args, &source)
|
30
|
+
@feature = Feature.new(comment_lines, *args).tap do |builder|
|
31
|
+
builder.instance_exec(&source) if source
|
32
|
+
end
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def build
|
37
|
+
instance_exec(&@source)
|
38
|
+
Document.new(@uri, @feature.build.join("\n"))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Feature
|
43
|
+
include HasElements
|
44
|
+
include HasOptionsInitializer
|
45
|
+
include HasDescription
|
46
|
+
include Indentation.level(0)
|
47
|
+
|
48
|
+
default_keyword 'Feature'
|
49
|
+
|
50
|
+
elements :background, :scenario, :scenario_outline
|
51
|
+
|
52
|
+
def build(source = [])
|
53
|
+
elements.inject(source + statements) { |acc, el| el.build(acc) + [NEW_LINE] }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def language
|
58
|
+
options[:language]
|
59
|
+
end
|
60
|
+
|
61
|
+
def statements
|
62
|
+
prepare_statements language_statement,
|
63
|
+
comments_statement,
|
64
|
+
tag_statement,
|
65
|
+
name_statement,
|
66
|
+
description_statement,
|
67
|
+
NEW_LINE
|
68
|
+
end
|
69
|
+
|
70
|
+
def language_statement
|
71
|
+
"# language: #{language}" if language
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Background
|
76
|
+
include HasElements
|
77
|
+
include HasOptionsInitializer
|
78
|
+
include HasDescription
|
79
|
+
include Indentation.level 2
|
80
|
+
|
81
|
+
default_keyword 'Background'
|
82
|
+
|
83
|
+
elements :step
|
84
|
+
|
85
|
+
private
|
86
|
+
def statements
|
87
|
+
prepare_statements comments_statement, tag_statement, name_statement, description_statement
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Scenario
|
92
|
+
include HasElements
|
93
|
+
include HasOptionsInitializer
|
94
|
+
include HasDescription
|
95
|
+
include Indentation.level 2
|
96
|
+
|
97
|
+
default_keyword 'Scenario'
|
98
|
+
|
99
|
+
elements :step
|
100
|
+
|
101
|
+
private
|
102
|
+
def statements
|
103
|
+
prepare_statements comments_statement,
|
104
|
+
tag_statement,
|
105
|
+
name_statement,
|
106
|
+
description_statement
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class ScenarioOutline
|
111
|
+
include HasElements
|
112
|
+
include HasOptionsInitializer
|
113
|
+
include HasDescription
|
114
|
+
include Indentation.level 2
|
115
|
+
|
116
|
+
default_keyword 'Scenario Outline'
|
117
|
+
|
118
|
+
elements :step, :examples
|
119
|
+
|
120
|
+
private
|
121
|
+
def statements
|
122
|
+
prepare_statements comments_statement, tag_statement, name_statement, description_statement
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Step
|
127
|
+
include HasElements
|
128
|
+
include HasOptionsInitializer
|
129
|
+
include Indentation.level 4
|
130
|
+
|
131
|
+
default_keyword 'Given'
|
132
|
+
|
133
|
+
elements :table
|
134
|
+
|
135
|
+
def doc_string(string, content_type='')
|
136
|
+
elements << DocString.new(string, content_type)
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def statements
|
141
|
+
prepare_statements comments_statement, name_statement
|
142
|
+
end
|
143
|
+
|
144
|
+
def name_statement
|
145
|
+
"#{keyword} #{name}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Table
|
150
|
+
include Indentation.level(6)
|
151
|
+
include HasRows
|
152
|
+
|
153
|
+
def initialize(*)
|
154
|
+
end
|
155
|
+
|
156
|
+
def build(source)
|
157
|
+
source + statements
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
def statements
|
162
|
+
row_statements
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class DocString
|
167
|
+
include Indentation.level(6)
|
168
|
+
|
169
|
+
attr_reader :strings, :content_type
|
170
|
+
private :strings, :content_type
|
171
|
+
|
172
|
+
def initialize(string, content_type)
|
173
|
+
@strings = string.split("\n").map(&:strip)
|
174
|
+
@content_type = content_type
|
175
|
+
end
|
176
|
+
|
177
|
+
def build(source)
|
178
|
+
source + statements
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
def statements
|
183
|
+
prepare_statements doc_string_statement
|
184
|
+
end
|
185
|
+
|
186
|
+
def doc_string_statement
|
187
|
+
[
|
188
|
+
%["""#{content_type}],
|
189
|
+
strings,
|
190
|
+
'"""'
|
191
|
+
]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
class Examples
|
196
|
+
include HasOptionsInitializer
|
197
|
+
include HasRows
|
198
|
+
include HasDescription
|
199
|
+
include Indentation.level(4)
|
200
|
+
|
201
|
+
default_keyword 'Examples'
|
202
|
+
|
203
|
+
def build(source)
|
204
|
+
source + statements
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
def statements
|
209
|
+
prepare_statements NEW_LINE,
|
210
|
+
comments_statement,
|
211
|
+
tag_statement,
|
212
|
+
name_statement,
|
213
|
+
description_statement,
|
214
|
+
row_statements(2)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module Cucumber
|
2
|
+
module Core
|
3
|
+
module Gherkin
|
4
|
+
module Writer
|
5
|
+
|
6
|
+
module HasOptionsInitializer
|
7
|
+
def self.included(base)
|
8
|
+
base.extend HasDefaultKeyword
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :name, :options
|
12
|
+
private :name, :options
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
@comments = args.shift if args.first.is_a?(Array)
|
16
|
+
@comments ||= []
|
17
|
+
@options = args.pop if args.last.is_a?(Hash)
|
18
|
+
@options ||= {}
|
19
|
+
@name = args.first
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def comments_statement
|
25
|
+
@comments
|
26
|
+
end
|
27
|
+
|
28
|
+
def keyword
|
29
|
+
options.fetch(:keyword) { self.class.keyword }
|
30
|
+
end
|
31
|
+
|
32
|
+
def name_statement
|
33
|
+
"#{keyword}: #{name}".strip
|
34
|
+
end
|
35
|
+
|
36
|
+
def tag_statement
|
37
|
+
tags
|
38
|
+
end
|
39
|
+
|
40
|
+
def tags
|
41
|
+
options[:tags]
|
42
|
+
end
|
43
|
+
|
44
|
+
module HasDefaultKeyword
|
45
|
+
def default_keyword(keyword)
|
46
|
+
@keyword = keyword
|
47
|
+
end
|
48
|
+
|
49
|
+
def keyword
|
50
|
+
@keyword
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module AcceptsComments
|
56
|
+
def comment(line)
|
57
|
+
comment_lines << "# #{line}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def comment_lines
|
61
|
+
@comment_lines ||= []
|
62
|
+
end
|
63
|
+
|
64
|
+
def slurp_comments
|
65
|
+
# TODO: I can't think of another way to handle this?
|
66
|
+
# When we use the comments, we need to reset the collection
|
67
|
+
# for the next element...
|
68
|
+
slurped_comments = comment_lines.dup
|
69
|
+
@comment_lines = nil
|
70
|
+
slurped_comments
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module HasElements
|
75
|
+
include AcceptsComments
|
76
|
+
|
77
|
+
def self.included(base)
|
78
|
+
base.extend HasElementBuilders
|
79
|
+
end
|
80
|
+
|
81
|
+
def build(source = [])
|
82
|
+
elements.inject(source + statements) { |acc, el| el.build(acc) }
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def elements
|
87
|
+
@elements ||= []
|
88
|
+
end
|
89
|
+
|
90
|
+
module HasElementBuilders
|
91
|
+
def elements(*names)
|
92
|
+
names.each { |name| element(name) }
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
def element(name)
|
97
|
+
define_method name do |*args, &source|
|
98
|
+
factory_name = String(name).split("_").map(&:capitalize).join
|
99
|
+
factory = Writer.const_get(factory_name)
|
100
|
+
factory.new(slurp_comments, *args).tap do |builder|
|
101
|
+
builder.instance_exec(&source) if source
|
102
|
+
elements << builder
|
103
|
+
end
|
104
|
+
self
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
module Indentation
|
111
|
+
def self.level(number)
|
112
|
+
Module.new do
|
113
|
+
define_method :indent do |string, amount=nil|
|
114
|
+
amount ||= number
|
115
|
+
return string if string.nil? || string.empty?
|
116
|
+
(' ' * amount) + string
|
117
|
+
end
|
118
|
+
|
119
|
+
define_method :indent_level do
|
120
|
+
number
|
121
|
+
end
|
122
|
+
|
123
|
+
define_method :prepare_statements do |*statements|
|
124
|
+
statements.flatten.compact.map { |s| indent(s) }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
module HasDescription
|
131
|
+
private
|
132
|
+
def description
|
133
|
+
options.fetch(:description) { '' }.split("\n").map(&:strip)
|
134
|
+
end
|
135
|
+
|
136
|
+
def description_statement
|
137
|
+
description.map { |s| indent(s,2) } unless description.empty?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module HasRows
|
142
|
+
def row(*cells)
|
143
|
+
rows << cells
|
144
|
+
end
|
145
|
+
|
146
|
+
def rows
|
147
|
+
@rows ||= []
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def row_statements(indent=nil)
|
153
|
+
rows.map { |row| indent(table_row(row), indent) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def table_row(row)
|
157
|
+
padded = pad(row)
|
158
|
+
"| #{padded.join(' | ')} |"
|
159
|
+
end
|
160
|
+
|
161
|
+
def pad(row)
|
162
|
+
row.map.with_index { |text, position| justify_cell(text, position) }
|
163
|
+
end
|
164
|
+
|
165
|
+
def column_length(column)
|
166
|
+
lengths = rows.transpose.map { |r| r.map(&:length).max }
|
167
|
+
lengths[column]
|
168
|
+
end
|
169
|
+
|
170
|
+
def justify_cell(cell, position)
|
171
|
+
cell.ljust(column_length(position))
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|