cucumber-core 0.2.0 → 1.0.0.beta.1

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/cucumber-core.gemspec +1 -1
  4. data/lib/cucumber/core.rb +6 -6
  5. data/lib/cucumber/core/ast/data_table.rb +8 -0
  6. data/lib/cucumber/core/ast/describes_itself.rb +1 -1
  7. data/lib/cucumber/core/ast/examples_table.rb +8 -2
  8. data/lib/cucumber/core/ast/feature.rb +2 -3
  9. data/lib/cucumber/core/ast/location.rb +11 -6
  10. data/lib/cucumber/core/ast/names.rb +10 -1
  11. data/lib/cucumber/core/ast/outline_step.rb +6 -3
  12. data/lib/cucumber/core/ast/scenario.rb +0 -1
  13. data/lib/cucumber/core/ast/scenario_outline.rb +2 -3
  14. data/lib/cucumber/core/ast/step.rb +2 -2
  15. data/lib/cucumber/core/gherkin/ast_builder.rb +4 -1
  16. data/lib/cucumber/core/test/case.rb +10 -6
  17. data/lib/cucumber/core/test/filters/debug_filter.rb +28 -0
  18. data/lib/cucumber/core/test/filters/tag_filter.rb +1 -1
  19. data/lib/cucumber/core/test/hooks.rb +76 -0
  20. data/lib/cucumber/core/test/mapper.rb +101 -19
  21. data/lib/cucumber/core/test/mapping.rb +15 -4
  22. data/lib/cucumber/core/test/result.rb +39 -27
  23. data/lib/cucumber/core/test/runner.rb +76 -81
  24. data/lib/cucumber/core/test/step.rb +10 -18
  25. data/lib/cucumber/core/version.rb +1 -1
  26. data/spec/cucumber/core/ast/data_table_spec.rb +12 -0
  27. data/spec/cucumber/core/ast/location_spec.rb +8 -1
  28. data/spec/cucumber/core/ast/outline_step_spec.rb +11 -4
  29. data/spec/cucumber/core/ast/step_spec.rb +2 -2
  30. data/spec/cucumber/core/compiler_spec.rb +6 -6
  31. data/spec/cucumber/core/gherkin/parser_spec.rb +31 -18
  32. data/spec/cucumber/core/test/case_spec.rb +24 -24
  33. data/spec/cucumber/core/test/hooks_spec.rb +30 -0
  34. data/spec/cucumber/core/test/mapper_spec.rb +115 -1
  35. data/spec/cucumber/core/test/mapping_spec.rb +22 -6
  36. data/spec/cucumber/core/test/result_spec.rb +0 -8
  37. data/spec/cucumber/core/test/runner_spec.rb +31 -97
  38. data/spec/cucumber/core/test/step_spec.rb +24 -16
  39. data/spec/cucumber/core_spec.rb +109 -16
  40. data/spec/report_api_spy.rb +24 -0
  41. metadata +32 -28
  42. data/lib/cucumber/core/test/hook_compiler.rb +0 -109
  43. data/spec/cucumber/core/test/hook_compiler_spec.rb +0 -78
@@ -1,55 +1,137 @@
1
1
  require 'cucumber/initializer'
2
+ require 'cucumber/core/test/hooks'
3
+
2
4
  module Cucumber
3
5
  module Core
4
6
  module Test
5
7
  class Mapper
6
- include Cucumber.initializer(:mappings, :runner)
8
+ include Cucumber.initializer(:mapping_definition, :receiver)
7
9
 
8
10
  def test_case(test_case, &descend)
9
- mapper = CaseMapper.new(mappings)
11
+ hook_factory = HookFactory.new(test_case.source)
12
+ mapper = CaseMapper.new(mapping_definition)
13
+ test_case.describe_to mapping_definition, CaseMapper::DSL.new(mapper, hook_factory)
10
14
  descend.call(mapper)
11
- test_case.with_steps(mapper.test_steps).describe_to(runner)
15
+ test_case.
16
+ with_steps(mapper.before_hooks + mapper.test_steps + mapper.after_hooks).
17
+ with_around_hooks(mapper.around_hooks).
18
+ describe_to(receiver)
12
19
  self
13
20
  end
14
21
 
15
22
  def done
16
- runner.done
23
+ receiver.done
17
24
  self
18
25
  end
19
26
 
27
+ private
28
+
20
29
  class CaseMapper
21
- include Cucumber.initializer(:mappings)
30
+ include Cucumber.initializer(:mapping_definition)
22
31
 
23
- attr_reader :test_steps
32
+ def test_step(test_step)
33
+ hook_factory = HookFactory.new(test_step.source)
34
+ mapper = StepMapper.new(test_step)
35
+ test_step.describe_to mapping_definition, StepMapper::DSL.new(mapper, hook_factory)
36
+ test_steps.push(*[mapper.test_step] + mapper.after_step_hooks)
37
+ self
38
+ end
24
39
 
25
- def initialize(*)
26
- super
27
- @test_steps = []
40
+ def test_steps
41
+ @test_steps ||= []
28
42
  end
29
43
 
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
44
+ def around_hooks
45
+ @around_hooks ||= []
46
+ end
47
+
48
+ def before_hooks
49
+ @before_hooks ||= []
50
+ end
51
+
52
+ def after_hooks
53
+ @after_hooks ||= []
34
54
  end
35
55
 
56
+ # Passed to users in the mappings to add hooks to a scenario
57
+ class DSL
58
+ include Cucumber.initializer(:mapper, :hook_factory)
59
+
60
+ # Run this block of code before the scenario
61
+ def before(&block)
62
+ mapper.before_hooks << hook_factory.before(block)
63
+ self
64
+ end
65
+
66
+ # Run this block of code after the scenario
67
+ def after(&block)
68
+ mapper.after_hooks << hook_factory.after(block)
69
+ self
70
+ end
71
+
72
+ # Run this block of code around the scenario, with a yield in the block executing the scenario
73
+ def around(&block)
74
+ mapper.around_hooks << Hooks::AroundHook.new(&block)
75
+ self
76
+ end
77
+
78
+ end
36
79
  end
37
80
 
38
81
  class StepMapper
39
82
  include Cucumber.initializer(:test_step)
40
83
 
41
- attr_reader :mapped_test_step
84
+ attr_accessor :test_step
42
85
 
43
- def initialize(*)
44
- super
45
- @mapped_test_step = test_step
86
+ def after_step_hooks
87
+ @after_step_hooks ||= []
46
88
  end
47
89
 
48
- def map(&block)
49
- @mapped_test_step = test_step.map(&block)
90
+ # Passed to users in the mappings to define and add hooks to a step
91
+ class DSL
92
+ include Cucumber.initializer(:mapper, :hook_factory)
93
+
94
+ # Define the step with a block of code to be executed
95
+ def map(&block)
96
+ mapper.test_step = mapper.test_step.with_mapping(&block)
97
+ self
98
+ end
99
+
100
+ # Define a block of code to be run after the step
101
+ def after(&block)
102
+ mapper.after_step_hooks << hook_factory.after_step(block)
103
+ self
104
+ end
105
+
106
+ end
107
+ end
108
+
109
+ class HookFactory
110
+ include Cucumber.initializer(:source)
111
+
112
+ def after(block)
113
+ build_hook_step(block, Hooks::AfterHook, Test::UnskippableMapping)
114
+ end
115
+
116
+ def before(block)
117
+ build_hook_step(block, Hooks::BeforeHook, Test::UnskippableMapping)
118
+ end
119
+
120
+ def after_step(block)
121
+ build_hook_step(block, Hooks::AfterStepHook, Test::Mapping)
122
+ end
123
+
124
+ private
125
+
126
+ def build_hook_step(block, hook_type, mapping_type)
127
+ mapping = mapping_type.new(&block)
128
+ hook = hook_type.new(mapping.location)
129
+ Step.new(source + [hook], mapping)
50
130
  end
131
+
51
132
  end
52
133
 
134
+
53
135
  end
54
136
  end
55
137
  end
@@ -1,6 +1,7 @@
1
1
  require 'cucumber/core/test/result'
2
2
  require 'cucumber/core/test/timer'
3
3
  require 'cucumber/core/test/result'
4
+ require 'cucumber/core/ast/location'
4
5
 
5
6
  module Cucumber
6
7
  module Core
@@ -21,12 +22,20 @@ module Cucumber
21
22
  @timer.start
22
23
  @block.call
23
24
  passed
24
- rescue Result::Pending => exception
25
- pending(exception)
25
+ rescue Result::Raisable => exception
26
+ exception.with_duration(@timer.duration)
26
27
  rescue Exception => exception
27
28
  failed(exception)
28
29
  end
29
30
 
31
+ def location
32
+ Ast::Location.new(*@block.source_location)
33
+ end
34
+
35
+ def inspect
36
+ "<#{self.class}: #{location}>"
37
+ end
38
+
30
39
  private
31
40
 
32
41
  def passed
@@ -40,9 +49,11 @@ module Cucumber
40
49
  def skipped
41
50
  Result::Skipped.new
42
51
  end
52
+ end
43
53
 
44
- def pending(exception)
45
- exception.with_duration(@timer.duration)
54
+ class UnskippableMapping < Mapping
55
+ def skip
56
+ execute
46
57
  end
47
58
  end
48
59
 
@@ -5,6 +5,9 @@ module Cucumber
5
5
  module Core
6
6
  module Test
7
7
  module Result
8
+
9
+ # Defines predicate methods on a result class with only the given one
10
+ # returning true
8
11
  def self.status_queries(status)
9
12
  Module.new do
10
13
  [:passed, :failed, :undefined, :unknown, :skipped, :pending].each do |possible_status|
@@ -15,7 +18,8 @@ module Cucumber
15
18
  end
16
19
  end
17
20
 
18
- Unknown = Class.new do
21
+ # Null object for results. Represents the state where we haven't run anything yet
22
+ class Unknown
19
23
  include Result.status_queries :unknown
20
24
 
21
25
  def describe_to(visitor, *args)
@@ -24,7 +28,7 @@ module Cucumber
24
28
  end
25
29
 
26
30
  class Passed
27
- include Result.status_queries :passed
31
+ include Result.status_queries(:passed)
28
32
  include Cucumber.initializer(:duration)
29
33
  attr_reader :duration
30
34
 
@@ -45,7 +49,7 @@ module Cucumber
45
49
  end
46
50
 
47
51
  class Failed
48
- include Result.status_queries :failed
52
+ include Result.status_queries(:failed)
49
53
  include Cucumber.initializer(:duration, :exception)
50
54
  attr_reader :duration, :exception
51
55
 
@@ -72,52 +76,54 @@ module Cucumber
72
76
 
73
77
  end
74
78
 
75
- Undefined = Class.new do
76
- include Result.status_queries :undefined
77
- include Cucumber.initializer(:duration)
78
- attr_reader :duration
79
+ # Base class for exceptions that can be raised in a step defintion causing
80
+ # the step to have that result.
81
+ class Raisable < StandardError
82
+ attr_reader :message, :duration
79
83
 
80
- def initialize(duration = 0)
81
- super
84
+ def initialize(message = "", duration = :unknown, backtrace = nil)
85
+ @message, @duration = message, duration
86
+ super(message)
87
+ set_backtrace(backtrace) if backtrace
88
+ end
89
+
90
+ def with_duration(new_duration)
91
+ self.class.new(message, new_duration, backtrace)
82
92
  end
93
+ end
94
+
95
+ class Undefined < Raisable
96
+ include Result.status_queries :undefined
83
97
 
84
98
  def describe_to(visitor, *args)
85
99
  visitor.undefined(*args)
100
+ visitor.duration(duration, *args) unless duration == :unknown
86
101
  self
87
102
  end
88
103
 
89
104
  def to_s
90
- ""
105
+ "?"
91
106
  end
92
107
 
93
- def with_duration(new_duration)
94
- self.class.new(new_duration)
95
- end
96
108
  end
97
109
 
98
- Skipped = Class.new do
110
+ class Skipped < Raisable
99
111
  include Result.status_queries :skipped
100
112
 
101
113
  def describe_to(visitor, *args)
102
114
  visitor.skipped(*args)
115
+ visitor.duration(duration, *args) unless duration == :unknown
103
116
  self
104
117
  end
105
118
 
106
119
  def to_s
107
120
  "-"
108
121
  end
122
+
109
123
  end
110
124
 
111
- class Pending < StandardError
125
+ class Pending < Raisable
112
126
  include Result.status_queries :pending
113
- attr_reader :message, :duration
114
-
115
- def initialize(message, duration = :unknown, backtrace=nil)
116
- raise ArgumentError unless message
117
- @message, @duration = message, duration
118
- super(message)
119
- set_backtrace(backtrace) if backtrace
120
- end
121
127
 
122
128
  def describe_to(visitor, *args)
123
129
  visitor.pending(self, *args)
@@ -128,12 +134,18 @@ module Cucumber
128
134
  def to_s
129
135
  "P"
130
136
  end
131
-
132
- def with_duration(new_duration)
133
- self.class.new(message, new_duration, backtrace)
134
- end
135
137
  end
136
138
 
139
+ #
140
+ # An object that responds to the description protocol from the results
141
+ # and collects summary information.
142
+ #
143
+ # e.g.
144
+ # summary = Result::Summary.new
145
+ # Result::Passed.new(0).describe_to(summary)
146
+ # puts summary.total_passed
147
+ # => 1
148
+ #
137
149
  class Summary
138
150
  attr_reader :total_failed,
139
151
  :total_passed,
@@ -5,116 +5,111 @@ module Cucumber
5
5
  module Core
6
6
  module Test
7
7
  class Runner
8
- module StepRunner
9
- class Default
10
- def initialize
11
- @timer = Timer.new.start
12
- end
13
-
14
- def execute(test_step)
15
- status.execute(test_step, self)
16
- end
8
+ class StepRunner
9
+ def initialize
10
+ @timer = Timer.new.start
11
+ @status = Status::Unknown.new(Result::Unknown.new)
12
+ end
17
13
 
18
- def result
19
- status.result(@timer.duration)
20
- end
14
+ def execute(test_step)
15
+ status.execute(test_step, self)
16
+ end
21
17
 
22
- def failed(step_result)
23
- @status = Failing.new(step_result)
24
- self
25
- end
18
+ def result
19
+ status.result(@timer.duration)
20
+ end
26
21
 
27
- def passed(step_result)
28
- @status = Passing.new
29
- self
30
- end
22
+ def failed(step_result)
23
+ @status = Status::Failing.new(step_result)
24
+ self
25
+ end
31
26
 
32
- def pending(message, step_result)
33
- @status = Pending.new(step_result)
34
- self
35
- end
27
+ def passed(step_result)
28
+ @status = Status::Passing.new(step_result)
29
+ self
30
+ end
36
31
 
37
- def undefined(step_result)
38
- failed(step_result)
39
- self
40
- end
32
+ def pending(message, step_result)
33
+ @status = Status::Pending.new(step_result)
34
+ self
35
+ end
41
36
 
42
- def exception(step_exception, step_result)
43
- self
44
- end
37
+ def skipped(step_result)
38
+ @status = Status::Skipping.new(step_result)
39
+ self
40
+ end
45
41
 
46
- def duration(step_duration, step_result)
47
- self
48
- end
42
+ def undefined(step_result)
43
+ failed(step_result)
44
+ self
45
+ end
49
46
 
50
- private
47
+ def exception(step_exception, step_result)
48
+ self
49
+ end
51
50
 
52
- def status
53
- @status ||= Unknown.new
54
- end
51
+ def duration(step_duration, step_result)
52
+ self
55
53
  end
56
54
 
57
- class DryRun
58
- def execute(test_step)
59
- step_result = test_step.skip
60
- @case_result = Result::Undefined.new if step_result.undefined?
61
- step_result
62
- end
55
+ attr_reader :status
56
+ private :status
63
57
 
64
- def result
65
- @case_result ||= Result::Skipped.new
66
- end
67
- end
58
+ module Status
59
+ class Base
60
+ include Cucumber.initializer(:step_result)
61
+
62
+ def execute(test_step, monitor)
63
+ result = test_step.execute
64
+ result.describe_to(monitor, result)
65
+ end
68
66
 
69
- class Unknown
70
- def execute(test_step, monitor)
71
- result = test_step.execute
72
- result.describe_to(monitor, result)
67
+ def result
68
+ raise NoMethodError, "Override me"
69
+ end
73
70
  end
74
71
 
75
- def result(duration)
76
- Result::Unknown.new
72
+ class Unknown < Base
73
+ def result(duration)
74
+ Result::Unknown.new
75
+ end
77
76
  end
78
- end
79
77
 
80
- class Passing < Unknown
81
- def result(duration)
82
- Result::Passed.new(duration)
78
+ class Passing < Base
79
+ def result(duration)
80
+ Result::Passed.new(duration)
81
+ end
83
82
  end
84
- end
85
83
 
86
- Failing = Struct.new(:step_result) do
87
- def execute(test_step, monitor)
88
- test_step.skip
84
+ class Failing < Base
85
+ def execute(test_step, monitor)
86
+ test_step.skip
87
+ end
88
+
89
+ def result(duration)
90
+ step_result.with_duration(duration)
91
+ end
89
92
  end
90
93
 
91
- def result(duration)
92
- step_result.with_duration(duration)
94
+ Pending = Class.new(Failing)
95
+
96
+ class Skipping < Failing
97
+ def result(duration)
98
+ step_result.with_duration(duration)
99
+ end
93
100
  end
94
101
  end
95
-
96
- Pending = Class.new(Failing)
97
102
  end
98
103
 
99
- STEP_RUNNER_STRATEGY = {
100
- default: StepRunner::Default,
101
- dry_run: StepRunner::DryRun
102
- }
103
-
104
- attr_reader :report, :step_runner_class
105
- private :report, :step_runner_class
106
- def initialize(report, run_options = {})
104
+ attr_reader :report
105
+ private :report
106
+ def initialize(report)
107
107
  @report = report
108
-
109
- run_mode = run_options.fetch(:run_mode) { :default }
110
- @step_runner_class = STEP_RUNNER_STRATEGY.fetch(run_mode) do
111
- raise ArgumentError, "No strategy for run mode: #{run_mode.inspect}"
112
- end
113
108
  end
114
109
 
115
110
  def test_case(test_case, &descend)
116
111
  report.before_test_case(test_case)
117
- descend.call
112
+ descend.call(self)
118
113
  report.after_test_case(test_case, current_case_result)
119
114
  @current_step_runner = nil
120
115
  end
@@ -141,7 +136,7 @@ module Cucumber
141
136
  end
142
137
 
143
138
  def current_step_runner
144
- @current_step_runner ||= step_runner_class.new
139
+ @current_step_runner ||= StepRunner.new
145
140
  end
146
141
  end
147
142
  end