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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.travis.yml +16 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +20 -0
  8. data/README.md +9 -0
  9. data/Rakefile +24 -0
  10. data/cucumber-core.gemspec +32 -0
  11. data/lib/cucumber/core.rb +37 -0
  12. data/lib/cucumber/core/ast.rb +13 -0
  13. data/lib/cucumber/core/ast/background.rb +33 -0
  14. data/lib/cucumber/core/ast/comment.rb +17 -0
  15. data/lib/cucumber/core/ast/data_table.rb +326 -0
  16. data/lib/cucumber/core/ast/describes_itself.rb +16 -0
  17. data/lib/cucumber/core/ast/doc_string.rb +83 -0
  18. data/lib/cucumber/core/ast/empty_background.rb +12 -0
  19. data/lib/cucumber/core/ast/examples_table.rb +95 -0
  20. data/lib/cucumber/core/ast/feature.rb +62 -0
  21. data/lib/cucumber/core/ast/location.rb +140 -0
  22. data/lib/cucumber/core/ast/multiline_argument.rb +33 -0
  23. data/lib/cucumber/core/ast/names.rb +19 -0
  24. data/lib/cucumber/core/ast/outline_step.rb +51 -0
  25. data/lib/cucumber/core/ast/scenario.rb +43 -0
  26. data/lib/cucumber/core/ast/scenario_outline.rb +44 -0
  27. data/lib/cucumber/core/ast/step.rb +38 -0
  28. data/lib/cucumber/core/ast/tag.rb +14 -0
  29. data/lib/cucumber/core/compiler.rb +136 -0
  30. data/lib/cucumber/core/gherkin/ast_builder.rb +315 -0
  31. data/lib/cucumber/core/gherkin/document.rb +20 -0
  32. data/lib/cucumber/core/gherkin/parser.rb +45 -0
  33. data/lib/cucumber/core/gherkin/writer.rb +220 -0
  34. data/lib/cucumber/core/gherkin/writer/helpers.rb +178 -0
  35. data/lib/cucumber/core/platform.rb +30 -0
  36. data/lib/cucumber/core/test/case.rb +143 -0
  37. data/lib/cucumber/core/test/filters.rb +48 -0
  38. data/lib/cucumber/core/test/filters/tag_filter.rb +110 -0
  39. data/lib/cucumber/core/test/hook_compiler.rb +109 -0
  40. data/lib/cucumber/core/test/mapper.rb +56 -0
  41. data/lib/cucumber/core/test/mapping.rb +67 -0
  42. data/lib/cucumber/core/test/result.rb +191 -0
  43. data/lib/cucumber/core/test/runner.rb +149 -0
  44. data/lib/cucumber/core/test/step.rb +69 -0
  45. data/lib/cucumber/core/test/timer.rb +31 -0
  46. data/lib/cucumber/core/version.rb +9 -0
  47. data/lib/cucumber/initializer.rb +18 -0
  48. data/spec/capture_warnings.rb +68 -0
  49. data/spec/coverage.rb +10 -0
  50. data/spec/cucumber/core/ast/data_table_spec.rb +139 -0
  51. data/spec/cucumber/core/ast/doc_string_spec.rb +77 -0
  52. data/spec/cucumber/core/ast/examples_table_spec.rb +87 -0
  53. data/spec/cucumber/core/ast/location_spec.rb +105 -0
  54. data/spec/cucumber/core/ast/outline_step_spec.rb +77 -0
  55. data/spec/cucumber/core/ast/step_spec.rb +44 -0
  56. data/spec/cucumber/core/compiler_spec.rb +249 -0
  57. data/spec/cucumber/core/gherkin/parser_spec.rb +182 -0
  58. data/spec/cucumber/core/gherkin/writer_spec.rb +332 -0
  59. data/spec/cucumber/core/test/case_spec.rb +416 -0
  60. data/spec/cucumber/core/test/hook_compiler_spec.rb +78 -0
  61. data/spec/cucumber/core/test/mapper_spec.rb +68 -0
  62. data/spec/cucumber/core/test/mapping_spec.rb +103 -0
  63. data/spec/cucumber/core/test/result_spec.rb +178 -0
  64. data/spec/cucumber/core/test/runner_spec.rb +265 -0
  65. data/spec/cucumber/core/test/step_spec.rb +58 -0
  66. data/spec/cucumber/core/test/timer_spec.rb +13 -0
  67. data/spec/cucumber/core_spec.rb +419 -0
  68. data/spec/cucumber/initializer_spec.rb +49 -0
  69. 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