cucumber-core 1.2.0 → 1.3.0

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