cucumber-core 1.2.0 → 1.3.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.
@@ -1,5 +1,8 @@
1
+ require 'gherkin3/parser'
2
+ require 'gherkin3/token_scanner'
3
+ require 'gherkin3/errors'
1
4
  require 'cucumber/core/gherkin/ast_builder'
2
- require 'gherkin/parser/parser'
5
+ require 'cucumber/core/ast'
3
6
 
4
7
  module Cucumber
5
8
  module Core
@@ -15,13 +18,18 @@ module Cucumber
15
18
  end
16
19
 
17
20
  def document(document)
18
- builder = AstBuilder.new(document.uri)
19
- parser = ::Gherkin::Parser::Parser.new(builder, true, "root", false)
21
+ parser = ::Gherkin3::Parser.new
22
+ scanner = ::Gherkin3::TokenScanner.new(document.body)
23
+ core_builder = AstBuilder.new(document.uri)
24
+
25
+ if document.body.strip.empty?
26
+ return receiver.feature Ast::NullFeature.new
27
+ end
20
28
 
21
29
  begin
22
- parser.parse(document.body, document.uri, 0)
23
- builder.language = parser.i18n_language
24
- receiver.feature builder.result
30
+ result = parser.parse(scanner)
31
+
32
+ receiver.feature core_builder.feature(result)
25
33
  rescue *PARSER_ERRORS => e
26
34
  raise Core::Gherkin::ParseError.new("#{document.uri}: #{e.message}")
27
35
  end
@@ -34,16 +42,8 @@ module Cucumber
34
42
 
35
43
  private
36
44
 
37
- PARSER_ERRORS = if Cucumber::JRUBY
38
- [
39
- ::Java::GherkinLexer::LexingError
40
- ]
41
- else
42
- [
43
- ::Gherkin::Lexer::LexingError,
44
- ::Gherkin::Parser::ParseError,
45
- ]
46
- end
45
+ PARSER_ERRORS = ::Gherkin3::ParserError
46
+
47
47
  end
48
48
  end
49
49
  end
@@ -0,0 +1,64 @@
1
+ module Cucumber
2
+ module Core
3
+ module Gherkin
4
+ class TagExpression
5
+
6
+ attr_reader :limits
7
+
8
+ def initialize(tag_expressions)
9
+ @ands = []
10
+ @limits = {}
11
+ tag_expressions.each do |expr|
12
+ add(expr.strip.split(/\s*,\s*/))
13
+ end
14
+ end
15
+
16
+ def empty?
17
+ @ands.empty?
18
+ end
19
+
20
+ def evaluate(tags)
21
+ return true if @ands.flatten.empty?
22
+ vars = Hash[*tags.map{|tag| [tag.name, true]}.flatten]
23
+ raise "No vars" if vars.nil? # Useless statement to prevent ruby warnings about unused var
24
+ !!Kernel.eval(ruby_expression)
25
+ end
26
+
27
+ private
28
+
29
+ def add(tags_with_negation_and_limits)
30
+ negatives, positives = tags_with_negation_and_limits.partition{|tag| tag =~ /^~/}
31
+ @ands << (store_and_extract_limits(negatives, true) + store_and_extract_limits(positives, false))
32
+ end
33
+
34
+ def store_and_extract_limits(tags_with_negation_and_limits, negated)
35
+ tags_with_negation = []
36
+ tags_with_negation_and_limits.each do |tag_with_negation_and_limit|
37
+ tag_with_negation, limit = tag_with_negation_and_limit.split(':')
38
+ tags_with_negation << tag_with_negation
39
+ if limit
40
+ tag_without_negation = negated ? tag_with_negation[1..-1] : tag_with_negation
41
+ if @limits[tag_without_negation] && @limits[tag_without_negation] != limit.to_i
42
+ raise "Inconsistent tag limits for #{tag_without_negation}: #{@limits[tag_without_negation]} and #{limit.to_i}"
43
+ end
44
+ @limits[tag_without_negation] = limit.to_i
45
+ end
46
+ end
47
+ tags_with_negation
48
+ end
49
+
50
+ def ruby_expression
51
+ "(" + @ands.map do |ors|
52
+ ors.map do |tag|
53
+ if tag =~ /^~(.*)/
54
+ "!vars['#{$1}']"
55
+ else
56
+ "vars['#{tag}']"
57
+ end
58
+ end.join("||")
59
+ end.join(")&&(") + ")"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,4 +1,5 @@
1
1
  require 'cucumber/core/test/result'
2
+ require 'cucumber/core/gherkin/tag_expression'
2
3
 
3
4
  module Cucumber
4
5
  module Core
@@ -55,9 +56,8 @@ module Cucumber
55
56
  @tags ||= TagCollector.new(self).result
56
57
  end
57
58
 
58
- require 'gherkin/tag_expression'
59
59
  def match_tags?(*expressions)
60
- ::Gherkin::TagExpression.new(expressions.flatten).evaluate(tags.map {|t| ::Gherkin::Formatter::Model::Tag.new(t.name, t.line) })
60
+ Cucumber::Core::Gherkin::TagExpression.new(expressions.flatten).evaluate(tags)
61
61
  end
62
62
 
63
63
  def match_name?(name_regexp)
@@ -4,8 +4,8 @@ module Cucumber
4
4
  module Core
5
5
  module Test
6
6
  class Runner
7
- attr_reader :report, :running_test_case
8
- private :report, :running_test_case
7
+ attr_reader :report, :running_test_case, :running_test_step
8
+ private :report, :running_test_case, :running_test_step
9
9
 
10
10
  def initialize(report)
11
11
  @report = report
@@ -13,6 +13,7 @@ module Cucumber
13
13
 
14
14
  def test_case(test_case, &descend)
15
15
  @running_test_case = RunningTestCase.new
16
+ @running_test_step = nil
16
17
  report.before_test_case(test_case)
17
18
  descend.call(self)
18
19
  report.after_test_case(test_case, running_test_case.result)
@@ -20,14 +21,18 @@ module Cucumber
20
21
  end
21
22
 
22
23
  def test_step(test_step)
24
+ @running_test_step = test_step
23
25
  report.before_test_step test_step
24
26
  step_result = running_test_case.execute(test_step)
25
27
  report.after_test_step test_step, step_result
28
+ @running_test_step = nil
26
29
  self
27
30
  end
28
31
 
29
32
  def around_hook(hook, &continue)
30
- running_test_case.execute(hook, &continue)
33
+ result = running_test_case.execute(hook, &continue)
34
+ report.after_test_step running_test_step, result if running_test_step
35
+ @running_test_step = nil
31
36
  self
32
37
  end
33
38
 
@@ -98,7 +103,7 @@ module Cucumber
98
103
  def execute(test_step, monitor, &continue)
99
104
  result = test_step.execute(monitor.result, &continue)
100
105
  result = result.with_message(%(Undefined step: "#{test_step.name}")) if result.undefined?
101
- result = result.with_appended_backtrace(test_step.source.last) if test_step.respond_to?(:source)
106
+ result = result.with_appended_backtrace(test_step.source.last) if IsStepVisitor.new(test_step).step?
102
107
  result.describe_to(monitor, result)
103
108
  end
104
109
 
@@ -57,6 +57,25 @@ module Cucumber
57
57
  end
58
58
 
59
59
  end
60
+
61
+ class IsStepVisitor
62
+ def initialize(test_step)
63
+ @is_step = false
64
+ test_step.describe_to(self)
65
+ end
66
+
67
+ def step?
68
+ @is_step
69
+ end
70
+
71
+ def test_step(*)
72
+ @is_step = true
73
+ end
74
+
75
+ def method_missing(*)
76
+ self
77
+ end
78
+ end
60
79
  end
61
80
  end
62
81
  end
@@ -2,7 +2,7 @@ module Cucumber
2
2
  module Core
3
3
  class Version
4
4
  def self.to_s
5
- "1.2.0"
5
+ "1.3.0"
6
6
  end
7
7
  end
8
8
  end
@@ -3,7 +3,7 @@ module Cucumber::Core::Ast
3
3
  describe Background do
4
4
  it "has a useful inspect" do
5
5
  location = Location.new("features/a_feature.feature", 3)
6
- background = Background.new(double, double, location, double, "Background", "the name", double, [])
6
+ background = Background.new(location, double, "Background", "the name", double, [])
7
7
  expect(background.inspect).to eq(%{#<Cucumber::Core::Ast::Background "Background: the name" (#{location})>})
8
8
  end
9
9
  end
@@ -1,6 +1,12 @@
1
1
  require 'cucumber/core/ast/location'
2
2
 
3
3
  module Cucumber::Core::Ast
4
+ RSpec::Matchers.define :be_included_in do |expected|
5
+ match do |actual|
6
+ expected.include? actual
7
+ end
8
+ end
9
+
4
10
  describe Location do
5
11
  let(:line) { 12 }
6
12
  let(:file) { "foo.feature" }
@@ -115,6 +121,45 @@ module Cucumber::Core::Ast
115
121
  end
116
122
  end
117
123
  end
124
+
125
+ describe "created from source location" do
126
+ context "when the location is in the tree below pwd" do
127
+ it "create a relative path from pwd" do
128
+ expect( Location.from_source_location(Dir.pwd + "/path/file.rb", 1).file ).to eq "path/file.rb"
129
+ end
130
+ end
131
+
132
+ context "when the location is in an installed gem" do
133
+ it "create a relative path from the gem directory" do
134
+ expect( Location.from_source_location("/path/gems/gem-name/path/file.rb", 1).file ).to eq "gem-name/path/file.rb"
135
+ end
136
+ end
137
+
138
+ context "when the location is neither below pwd nor in an installed gem" do
139
+ it "use the absolute path to the file" do
140
+ expect( Location.from_source_location("/path/file.rb", 1).file ).to eq "/path/file.rb"
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "created from file-colon-line" do
146
+ it "handles also Windows paths" do
147
+ expect( Location.from_file_colon_line("c:\path\file.rb:123").file ).to eq "c:\path\file.rb"
148
+ end
149
+ end
150
+
151
+ describe "created of caller" do
152
+ it "use the location of the caller" do
153
+ expect( Location.of_caller.to_s ).to be_included_in caller[0]
154
+ end
155
+
156
+ context "when specifying additional caller depth"do
157
+ it "use the location of the n:th caller" do
158
+ expect( Location.of_caller(1).to_s ).to be_included_in caller[1]
159
+ end
160
+ end
161
+ end
162
+
118
163
  end
119
164
  end
120
165
 
@@ -8,8 +8,7 @@ module Cucumber
8
8
  module Core
9
9
  module Ast
10
10
  describe OutlineStep do
11
- let(:outline_step) { OutlineStep.new(node, language, location, comments, keyword, name, multiline_arg) }
12
- let(:node) { double }
11
+ let(:outline_step) { OutlineStep.new(language, location, comments, keyword, name, multiline_arg) }
13
12
  let(:language) { double }
14
13
  let(:location) { double }
15
14
  let(:comments) { double }
@@ -46,7 +45,7 @@ module Cucumber
46
45
  end
47
46
 
48
47
  context "when the step has a DataTable" do
49
- let(:outline_step) { OutlineStep.new(node, language, location, comments, keyword, name, table) }
48
+ let(:outline_step) { OutlineStep.new(language, location, comments, keyword, name, table) }
50
49
  let(:name) { "anything" }
51
50
  let(:table) { DataTable.new([['x', 'y'],['a', 'a <arg>']], Location.new('foo.feature', 23)) }
52
51
 
@@ -64,7 +63,7 @@ module Cucumber
64
63
 
65
64
  context "when the step has a DocString" do
66
65
  let(:location) { double }
67
- let(:outline_step) { OutlineStep.new(node, language, location, comments, keyword, name, doc_string) }
66
+ let(:outline_step) { OutlineStep.new(language, location, comments, keyword, name, doc_string) }
68
67
  let(:doc_string) { DocString.new('a <arg> that needs replacing', '', location) }
69
68
  let(:name) { 'anything' }
70
69
 
@@ -1,14 +1,16 @@
1
1
  require 'cucumber/core/ast/step'
2
- require 'gherkin/i18n'
2
+ require 'cucumber/core/ast/outline_step'
3
+ require 'cucumber/core/ast/empty_multiline_argument'
4
+ require 'gherkin3/dialect'
3
5
 
4
6
  module Cucumber
5
7
  module Core
6
8
  module Ast
7
9
  describe Step do
8
10
  let(:step) do
9
- node, language, location, comments, keyword, name = *double
11
+ language, location, comments, keyword, name = *double
10
12
  multiline_arg = EmptyMultilineArgument.new
11
- Step.new(node, language, location, comments, keyword, name, multiline_arg)
13
+ Step.new(language, location, comments, keyword, name, multiline_arg)
12
14
  end
13
15
 
14
16
  describe "describing itself" do
@@ -27,7 +29,7 @@ module Cucumber
27
29
  end
28
30
 
29
31
  context "with a multiline argument" do
30
- let(:step) { Step.new(double, double, double, double, double, double, multiline_arg) }
32
+ let(:step) { Step.new(double, double, double, double, double, multiline_arg) }
31
33
  let(:multiline_arg) { double }
32
34
 
33
35
  it "tells its multiline argument to describe itself" do
@@ -46,7 +48,7 @@ module Cucumber
46
48
  end
47
49
 
48
50
  describe "backtrace line" do
49
- let(:step) { Step.new(double, double, "path/file.feature:10", double, "Given ", "this step passes", double) }
51
+ let(:step) { Step.new(double, "path/file.feature:10", double, "Given ", "this step passes", double) }
50
52
 
51
53
  it "knows how to form the backtrace line" do
52
54
  expect( step.backtrace_line ).to eq("path/file.feature:10:in `Given this step passes'")
@@ -55,12 +57,12 @@ module Cucumber
55
57
  end
56
58
 
57
59
  describe "actual keyword" do
58
- let(:language) { ::Gherkin::I18n.get('en') }
60
+ let(:language) { ::Gherkin3::Dialect.for('en') }
59
61
 
60
62
  context "for keywords 'given', 'when' and 'then'" do
61
- let(:given_step) { Step.new(double, language, double, double, "Given ", double, double) }
62
- let(:when_step) { Step.new(double, language, double, double, "When ", double, double) }
63
- let(:then_step) { Step.new(double, language, double, double, "Then ", double, double) }
63
+ let(:given_step) { Step.new(language, double, double, "Given ", double, double) }
64
+ let(:when_step) { Step.new(language, double, double, "When ", double, double) }
65
+ let(:then_step) { Step.new(language, double, double, "Then ", double, double) }
64
66
 
65
67
  it "returns the keyword itself" do
66
68
  expect( given_step.actual_keyword(nil) ).to eq("Given ")
@@ -70,9 +72,9 @@ module Cucumber
70
72
  end
71
73
 
72
74
  context "for keyword 'and', 'but', and '*'" do
73
- let(:and_step) { Step.new(double, language, double, double, "And ", double, double) }
74
- let(:but_step) { Step.new(double, language, double, double, "But ", double, double) }
75
- let(:asterisk_step) { Step.new(double, language, double, double, "* ", double, double) }
75
+ let(:and_step) { Step.new(language, double, double, "And ", double, double) }
76
+ let(:but_step) { Step.new(language, double, double, "But ", double, double) }
77
+ let(:asterisk_step) { Step.new(language, double, double, "* ", double, double) }
76
78
 
77
79
  context "when the previous step keyword exist" do
78
80
  it "returns the previous step keyword" do
@@ -93,8 +95,8 @@ module Cucumber
93
95
  end
94
96
 
95
97
  context "for i18n languages" do
96
- let(:language) { ::Gherkin::I18n.get('en-lol') }
97
- let(:and_step) { Step.new(double, language, double, double, "AN ", double, double) }
98
+ let(:language) { ::Gherkin3::Dialect.for('en-lol') }
99
+ let(:and_step) { Step.new(language, double, double, "AN ", double, double) }
98
100
 
99
101
  it "returns the keyword in the correct language" do
100
102
  expect( and_step.actual_keyword(nil) ).to eq("I CAN HAZ ")
@@ -106,10 +108,10 @@ module Cucumber
106
108
  describe ExpandedOutlineStep do
107
109
  let(:outline_step) { double }
108
110
  let(:step) do
109
- node, language, location, keyword, name = *double
110
- comments = []
111
+ language, location, keyword, name = *double
111
112
  multiline_arg = EmptyMultilineArgument.new
112
- ExpandedOutlineStep.new(outline_step, node, language, location, comments, keyword, name, multiline_arg)
113
+ comments = []
114
+ ExpandedOutlineStep.new(outline_step, language, location, comments, keyword, name, multiline_arg)
113
115
  end
114
116
 
115
117
  describe "describing itself" do
@@ -128,7 +130,7 @@ module Cucumber
128
130
  end
129
131
 
130
132
  context "with a multiline argument" do
131
- let(:step) { ExpandedOutlineStep.new(double, double, double, double, double, double, double, multiline_arg) }
133
+ let(:step) { Step.new(double, double, double, double, double, multiline_arg) }
132
134
  let(:multiline_arg) { double }
133
135
 
134
136
  it "tells its multiline argument to describe itself" do
@@ -157,8 +159,8 @@ module Cucumber
157
159
  end
158
160
 
159
161
  describe "backtrace line" do
160
- let(:outline_step) { OutlineStep.new(double, double, "path/file.feature:5", double, "Given ", "this step <state>", double) }
161
- let(:step) { ExpandedOutlineStep.new(outline_step, double, double, "path/file.feature:10", double, "Given ", "this step passes", double) }
162
+ let(:outline_step) { OutlineStep.new(double, "path/file.feature:5", double, "Given ", "this step <state>", double) }
163
+ let(:step) { ExpandedOutlineStep.new(outline_step, double, "path/file.feature:10", double, "Given ", "this step passes", double) }
162
164
 
163
165
  it "includes the outline step in the backtrace line" do
164
166
  expect( step.backtrace_line ).to eq("path/file.feature:10:in `Given this step passes'\n" +
@@ -15,7 +15,7 @@ module Cucumber
15
15
  end
16
16
 
17
17
  context "for invalid gherkin" do
18
- let(:source) { Gherkin::Document.new(path, 'not gherkin') }
18
+ let(:source) { Gherkin::Document.new(path, "\nnot gherkin\n\nFeature: \n") }
19
19
  let(:path) { 'path_to/the.feature' }
20
20
 
21
21
  it "raises an error" do
@@ -116,19 +116,48 @@ module Cucumber
116
116
  end
117
117
  end
118
118
 
119
- context "a Scenario with a Comment" do
119
+ context "a feature file with a comments on different levels" do
120
120
  source do
121
+ comment 'feature comment'
121
122
  feature do
122
- comment 'wow'
123
- scenario
123
+ comment 'scenario comment'
124
+ scenario do
125
+ comment 'step comment'
126
+ step
127
+ end
128
+ comment 'scenario outline comment'
129
+ scenario_outline do
130
+ comment 'outline step comment'
131
+ step
132
+ comment 'examples comment'
133
+ examples do
134
+ row
135
+ row
136
+ end
137
+ end
124
138
  end
125
139
  end
126
140
 
127
- it "parses the comment into the AST" do
141
+ it "the comments are distibuted to down the ast tree from the feature" do
128
142
  visitor = double
129
- allow( visitor ).to receive(:feature).and_yield(visitor)
143
+ expect( visitor ).to receive(:feature) do |feature|
144
+ expect( feature.comments.join ).to eq "# feature comment"
145
+ visitor
146
+ end.and_yield(visitor)
130
147
  expect( visitor ).to receive(:scenario) do |scenario|
131
- expect( scenario.comments.join ).to eq "# wow"
148
+ expect( scenario.comments.join ).to eq " # scenario comment"
149
+ end.and_yield(visitor)
150
+ expect( visitor ).to receive(:step) do |step|
151
+ expect( step.comments.join ).to eq " # step comment"
152
+ end.and_yield(visitor)
153
+ expect( visitor ).to receive(:scenario_outline) do |scenario_outline|
154
+ expect( scenario_outline.comments.join ).to eq " # scenario outline comment"
155
+ end.and_yield(visitor)
156
+ expect( visitor ).to receive(:outline_step) do |outline_step|
157
+ expect( outline_step.comments.join ).to eq " # outline step comment"
158
+ end.and_yield(visitor)
159
+ expect( visitor ).to receive(:examples_table) do |examples_table|
160
+ expect( examples_table.comments.join ).to eq " # examples comment"
132
161
  end
133
162
  feature.describe_to(visitor)
134
163
  end