turnip 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/.travis.yml +5 -0
- data/README.md +196 -44
- data/Rakefile +8 -0
- data/examples/alignment_steps.rb +7 -0
- data/examples/autoload_steps.feature +5 -0
- data/examples/autoload_steps.rb +5 -0
- data/examples/dragon_steps.rb +17 -0
- data/examples/evil_steps.rb +7 -0
- data/examples/knight_steps.rb +29 -0
- data/examples/more_steps.rb +7 -0
- data/examples/neutral_steps.rb +7 -0
- data/examples/red_dragon_steps.rb +18 -0
- data/examples/step_calling.feature +14 -0
- data/examples/step_calling_steps.rb +23 -0
- data/examples/steps.rb +0 -22
- data/examples/steps_for_super.feature +16 -0
- data/lib/turnip.rb +12 -12
- data/lib/turnip/builder.rb +20 -4
- data/lib/turnip/config.rb +18 -0
- data/lib/turnip/dsl.rb +19 -13
- data/lib/turnip/feature_file.rb +20 -0
- data/lib/turnip/loader.rb +6 -3
- data/lib/turnip/placeholder.rb +1 -1
- data/lib/turnip/runner_dsl.rb +9 -0
- data/lib/turnip/scenario_context.rb +41 -0
- data/lib/turnip/scenario_runner.rb +35 -0
- data/lib/turnip/step_definition.rb +14 -30
- data/lib/turnip/step_loader.rb +27 -0
- data/lib/turnip/step_module.rb +89 -0
- data/lib/turnip/table.rb +12 -0
- data/lib/turnip/version.rb +1 -1
- data/spec/builder_spec.rb +41 -2
- data/spec/dsl_spec.rb +22 -34
- data/spec/feature_file_spec.rb +18 -0
- data/spec/integration_spec.rb +1 -1
- data/spec/runner_dsl_spec.rb +23 -0
- data/spec/scenario_context_spec.rb +51 -0
- data/spec/scenario_runner_spec.rb +79 -0
- data/spec/spec_helper.rb +1 -3
- data/spec/step_definition_spec.rb +22 -34
- data/spec/step_loader_spec.rb +29 -0
- data/spec/step_module_spec.rb +106 -0
- data/spec/table_spec.rb +8 -1
- data/turnip.gemspec +2 -1
- metadata +51 -7
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: Step-calling steps
|
2
|
+
|
3
|
+
Scenario: when the called step is visible
|
4
|
+
Given a visible step call
|
5
|
+
|
6
|
+
Scenario: when the called step is not visible
|
7
|
+
Given an invisible step call
|
8
|
+
|
9
|
+
Scenario: when the called step is global
|
10
|
+
Given a global step call
|
11
|
+
|
12
|
+
@autoload_steps
|
13
|
+
Scenario: when the called step is included via tag
|
14
|
+
Given an included call
|
@@ -0,0 +1,23 @@
|
|
1
|
+
steps_for :step_calling do
|
2
|
+
step 'visible callee' do
|
3
|
+
@testing = 123
|
4
|
+
end
|
5
|
+
|
6
|
+
step 'a visible step call' do
|
7
|
+
step 'visible callee'
|
8
|
+
@testing.should eq(123)
|
9
|
+
end
|
10
|
+
|
11
|
+
step 'an invisible step call' do
|
12
|
+
step 'this is an unimplemented step'
|
13
|
+
end
|
14
|
+
|
15
|
+
step 'a global step call' do
|
16
|
+
step 'there is a monster'
|
17
|
+
@monster.should == 1
|
18
|
+
end
|
19
|
+
|
20
|
+
step 'an included call' do
|
21
|
+
step 'an auto-loaded step is available'
|
22
|
+
end
|
23
|
+
end
|
data/examples/steps.rb
CHANGED
@@ -32,14 +32,6 @@ step 'it should be called "John"' do
|
|
32
32
|
@monster_name.should == "John"
|
33
33
|
end
|
34
34
|
|
35
|
-
step "there are :count monkeys with :color hair" do |count, color|
|
36
|
-
@monkeys = Array.new(count) { color }
|
37
|
-
end
|
38
|
-
|
39
|
-
step "there should be 3 monkeys with blue hair" do
|
40
|
-
@monkeys.should == [:blue, :blue, :blue]
|
41
|
-
end
|
42
|
-
|
43
35
|
step "there is a monster with :count hitpoints" do |count|
|
44
36
|
@monster = count
|
45
37
|
end
|
@@ -75,20 +67,6 @@ step "the song should have :count lines" do |count|
|
|
75
67
|
@song.to_s.split("\n").length.should eq(count)
|
76
68
|
end
|
77
69
|
|
78
|
-
step "the monster has an alignment", :for => :evil do
|
79
|
-
@alignment = 'Evil'
|
80
|
-
end
|
81
|
-
|
82
|
-
steps_for :neutral do
|
83
|
-
step "the monster has an alignment" do
|
84
|
-
@alignment = 'Neutral'
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
step "that alignment should be :alignment" do |alignment|
|
89
|
-
@alignment.should eq(alignment)
|
90
|
-
end
|
91
|
-
|
92
70
|
placeholder :count do
|
93
71
|
match /\d+/ do |count|
|
94
72
|
count.to_i
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Feature: Red Dragons are deadly
|
2
|
+
|
3
|
+
@dragon
|
4
|
+
Scenario:
|
5
|
+
Given there is a dragon
|
6
|
+
And there is a knight
|
7
|
+
When the dragon attacks the knight
|
8
|
+
Then the knight is alive
|
9
|
+
|
10
|
+
@red_dragon
|
11
|
+
Scenario:
|
12
|
+
Given there is a dragon
|
13
|
+
And the dragon breathes fire
|
14
|
+
And there is a knight
|
15
|
+
When the dragon attacks the knight
|
16
|
+
Then the knight is dead
|
data/lib/turnip.rb
CHANGED
@@ -4,31 +4,31 @@ require "gherkin/formatter/tag_count_formatter"
|
|
4
4
|
require "turnip/version"
|
5
5
|
require "turnip/dsl"
|
6
6
|
|
7
|
+
require 'rspec'
|
8
|
+
|
7
9
|
module Turnip
|
10
|
+
autoload :Config, 'turnip/config'
|
11
|
+
autoload :FeatureFile, 'turnip/feature_file'
|
8
12
|
autoload :Loader, 'turnip/loader'
|
9
13
|
autoload :Builder, 'turnip/builder'
|
10
14
|
autoload :StepDefinition, 'turnip/step_definition'
|
11
15
|
autoload :Placeholder, 'turnip/placeholder'
|
12
16
|
autoload :Table, 'turnip/table'
|
17
|
+
autoload :StepLoader, 'turnip/step_loader'
|
18
|
+
autoload :StepModule, 'turnip/step_module'
|
19
|
+
autoload :ScenarioRunner, 'turnip/scenario_runner'
|
20
|
+
autoload :RunnerDSL, 'turnip/runner_dsl'
|
21
|
+
autoload :ScenarioContext, 'turnip/scenario_context'
|
13
22
|
|
14
23
|
class << self
|
15
24
|
attr_accessor :type
|
16
25
|
|
17
|
-
def run(
|
18
|
-
Turnip::Builder.build(
|
26
|
+
def run(feature_file)
|
27
|
+
Turnip::Builder.build(feature_file).features.each do |feature|
|
19
28
|
describe feature.name, feature.metadata_hash do
|
20
|
-
feature.backgrounds.each do |background|
|
21
|
-
before do
|
22
|
-
background.steps.each do |step|
|
23
|
-
Turnip::StepDefinition.execute(self, step)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
29
|
feature.scenarios.each do |scenario|
|
28
30
|
it scenario.name, scenario.metadata_hash do
|
29
|
-
|
30
|
-
Turnip::StepDefinition.execute(self, step)
|
31
|
-
end
|
31
|
+
Turnip::ScenarioRunner.new(self).load(Turnip::ScenarioContext.new(feature, scenario)).run
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
data/lib/turnip/builder.rb
CHANGED
@@ -4,6 +4,10 @@ module Turnip
|
|
4
4
|
def tags
|
5
5
|
@raw.tags.map { |tag| tag.name.sub(/^@/, '') }
|
6
6
|
end
|
7
|
+
|
8
|
+
def active_tags
|
9
|
+
tags.map(&:to_sym)
|
10
|
+
end
|
7
11
|
|
8
12
|
def tags_hash
|
9
13
|
Hash[tags.map { |t| [t.to_sym, true] }]
|
@@ -25,12 +29,21 @@ module Turnip
|
|
25
29
|
include Name
|
26
30
|
|
27
31
|
attr_reader :scenarios, :backgrounds
|
32
|
+
attr_accessor :feature_tag
|
28
33
|
|
29
34
|
def initialize(raw)
|
30
35
|
@raw = raw
|
31
36
|
@scenarios = []
|
32
37
|
@backgrounds = []
|
33
38
|
end
|
39
|
+
|
40
|
+
# Feature's active_tags automatically prepends the :global tag
|
41
|
+
# as well as its feature_tag if defined
|
42
|
+
def active_tags
|
43
|
+
active_tags = [:global]
|
44
|
+
active_tags << feature_tag.to_sym if feature_tag
|
45
|
+
active_tags + super
|
46
|
+
end
|
34
47
|
|
35
48
|
def metadata_hash
|
36
49
|
super.merge(:type => Turnip.type, :turnip => true)
|
@@ -88,16 +101,17 @@ module Turnip
|
|
88
101
|
attr_reader :features
|
89
102
|
|
90
103
|
class << self
|
91
|
-
def build(
|
92
|
-
Turnip::Builder.new.tap do |builder|
|
104
|
+
def build(feature_file)
|
105
|
+
Turnip::Builder.new(feature_file).tap do |builder|
|
93
106
|
formatter = Gherkin::Formatter::TagCountFormatter.new(builder, {})
|
94
107
|
parser = Gherkin::Parser::Parser.new(formatter, true, "root", false)
|
95
|
-
parser.parse(content, nil, 0)
|
108
|
+
parser.parse(feature_file.content, nil, 0)
|
96
109
|
end
|
97
110
|
end
|
98
111
|
end
|
99
112
|
|
100
|
-
def initialize
|
113
|
+
def initialize(feature_file)
|
114
|
+
@feature_file = feature_file
|
101
115
|
@features = []
|
102
116
|
end
|
103
117
|
|
@@ -108,6 +122,8 @@ module Turnip
|
|
108
122
|
|
109
123
|
def feature(feature)
|
110
124
|
@current_feature = Feature.new(feature)
|
125
|
+
# Automatically add a tag based on the name of the feature to the Feature if configured to
|
126
|
+
@current_feature.feature_tag = @feature_file.feature_name if Turnip::Config.autotag_features
|
111
127
|
@features << @current_feature
|
112
128
|
end
|
113
129
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Turnip
|
2
|
+
module Config
|
3
|
+
extend self
|
4
|
+
|
5
|
+
attr_accessor :autotag_features
|
6
|
+
|
7
|
+
def step_dirs
|
8
|
+
@step_dirs ||= ['spec']
|
9
|
+
end
|
10
|
+
|
11
|
+
def step_dirs=(dirs)
|
12
|
+
@step_dirs = [] unless @step_dirs
|
13
|
+
@step_dirs.concat(Array(dirs))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Turnip::Config.autotag_features = true
|
data/lib/turnip/dsl.rb
CHANGED
@@ -1,24 +1,30 @@
|
|
1
1
|
module Turnip
|
2
2
|
module DSL
|
3
|
-
|
4
|
-
|
3
|
+
def placeholder(name, &block)
|
4
|
+
Turnip::Placeholder.add(name, &block)
|
5
5
|
end
|
6
6
|
|
7
|
-
def step(description,
|
8
|
-
|
9
|
-
options[:for] = [options[:for], *Turnip::DSL.current_taggings].compact.flatten
|
10
|
-
end
|
11
|
-
Turnip::StepDefinition.add(description, options, &block)
|
7
|
+
def step(description, &block)
|
8
|
+
global_step_module_entry.step_module.steps << Turnip::StepDefinition.new(description, &block)
|
12
9
|
end
|
13
10
|
|
14
|
-
def steps_for(
|
15
|
-
Turnip::
|
16
|
-
yield
|
17
|
-
Turnip::DSL.current_taggings = nil
|
11
|
+
def steps_for(tag, &block)
|
12
|
+
Turnip::StepModule.steps_for(tag, &block)
|
18
13
|
end
|
19
14
|
|
20
|
-
|
21
|
-
|
15
|
+
private
|
16
|
+
|
17
|
+
def global_step_module_entry
|
18
|
+
@global_step_module_entry ||= begin
|
19
|
+
anon = Module.new do
|
20
|
+
def self.steps
|
21
|
+
@steps ||= []
|
22
|
+
end
|
23
|
+
end
|
24
|
+
entry = Turnip::StepModule::Entry.new([:global], anon, [])
|
25
|
+
Turnip::StepModule.module_registry[:global] << entry
|
26
|
+
entry
|
27
|
+
end
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Turnip
|
2
|
+
class FeatureFile
|
3
|
+
attr_accessor :file_name, :content, :feature_name
|
4
|
+
|
5
|
+
def initialize(file_name)
|
6
|
+
@file_name = file_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def feature_name
|
10
|
+
@feature_name ||= begin
|
11
|
+
file = Pathname.new(file_name).basename.to_s
|
12
|
+
file[0...file.index('.feature')]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def content
|
17
|
+
@content ||= File.read(file_name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/turnip/loader.rb
CHANGED
@@ -2,9 +2,12 @@ module Turnip
|
|
2
2
|
module Loader
|
3
3
|
def load(*a, &b)
|
4
4
|
if a.first.end_with?('.feature')
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
begin
|
6
|
+
require 'spec_helper'
|
7
|
+
rescue LoadError
|
8
|
+
end
|
9
|
+
Turnip::StepLoader.load_steps
|
10
|
+
Turnip.run(Turnip::FeatureFile.new(a.first))
|
8
11
|
else
|
9
12
|
super
|
10
13
|
end
|
data/lib/turnip/placeholder.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Turnip
|
2
|
+
class ScenarioContext
|
3
|
+
attr_accessor :feature
|
4
|
+
attr_accessor :scenario
|
5
|
+
|
6
|
+
def initialize(feature, scenario)
|
7
|
+
self.feature = feature
|
8
|
+
self.scenario = scenario
|
9
|
+
end
|
10
|
+
|
11
|
+
def available_background_steps
|
12
|
+
available_steps_for(*feature_tags)
|
13
|
+
end
|
14
|
+
|
15
|
+
def available_scenario_steps
|
16
|
+
available_steps_for(*scenario_tags)
|
17
|
+
end
|
18
|
+
|
19
|
+
def backgrounds
|
20
|
+
feature.backgrounds
|
21
|
+
end
|
22
|
+
|
23
|
+
def modules
|
24
|
+
Turnip::StepModule.modules_for(*scenario_tags)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def available_steps_for(*tags)
|
30
|
+
Turnip::StepModule.all_steps_for(*tags)
|
31
|
+
end
|
32
|
+
|
33
|
+
def feature_tags
|
34
|
+
@feature_tags ||= feature.active_tags.uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
def scenario_tags
|
38
|
+
@scenario_tags ||= (feature_tags + scenario.active_tags).uniq
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Turnip
|
2
|
+
class ScenarioRunner
|
3
|
+
attr_accessor :available_steps
|
4
|
+
attr_accessor :context
|
5
|
+
attr_accessor :world
|
6
|
+
|
7
|
+
def initialize(world)
|
8
|
+
self.world = world
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(context)
|
12
|
+
self.context = context
|
13
|
+
world.extend Turnip::RunnerDSL
|
14
|
+
world.turnip_runner = self
|
15
|
+
context.modules.each {|mod| world.extend mod }
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
self.available_steps = context.available_background_steps
|
21
|
+
context.backgrounds.each do |background|
|
22
|
+
run_steps(background.steps)
|
23
|
+
end
|
24
|
+
|
25
|
+
self.available_steps = context.available_scenario_steps
|
26
|
+
run_steps(context.scenario.steps)
|
27
|
+
end
|
28
|
+
|
29
|
+
def run_steps(steps)
|
30
|
+
steps.each do |step|
|
31
|
+
Turnip::StepDefinition.execute(world, available_steps, step)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -2,17 +2,16 @@ module Turnip
|
|
2
2
|
class StepDefinition
|
3
3
|
class Match < Struct.new(:step_definition, :params, :block)
|
4
4
|
def expression; step_definition.expression; end
|
5
|
-
def options; step_definition.options; end
|
6
5
|
end
|
7
6
|
|
8
7
|
class Pending < StandardError; end
|
9
8
|
class Ambiguous < StandardError; end
|
10
9
|
|
11
|
-
attr_reader :expression, :block
|
10
|
+
attr_reader :expression, :block
|
12
11
|
|
13
12
|
class << self
|
14
|
-
def execute(context, step)
|
15
|
-
match = find(step.description
|
13
|
+
def execute(context, available_steps, step)
|
14
|
+
match = find(available_steps, step.description)
|
16
15
|
params = match.params
|
17
16
|
params << step.extra_arg if step.extra_arg
|
18
17
|
context.instance_exec(*params, &match.block)
|
@@ -20,53 +19,38 @@ module Turnip
|
|
20
19
|
context.pending "the step '#{step.description}' is not implemented"
|
21
20
|
end
|
22
21
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def find(description, metadata={})
|
28
|
-
found = all.map do |step|
|
29
|
-
step.match(description, metadata)
|
22
|
+
def find(available_steps, description)
|
23
|
+
found = available_steps.map do |step|
|
24
|
+
step.match(description)
|
30
25
|
end.compact
|
31
26
|
raise Pending, description if found.length == 0
|
32
27
|
raise Ambiguous, description if found.length > 1
|
33
28
|
found[0]
|
34
29
|
end
|
35
|
-
|
36
|
-
def all
|
37
|
-
@all ||= []
|
38
|
-
end
|
39
30
|
end
|
40
31
|
|
41
|
-
def initialize(expression,
|
32
|
+
def initialize(expression, &block)
|
42
33
|
@expression = expression
|
43
34
|
@block = block
|
44
|
-
@options = options
|
45
35
|
end
|
46
36
|
|
47
37
|
def regexp
|
48
38
|
@regexp ||= compile_regexp
|
49
39
|
end
|
50
40
|
|
51
|
-
def match(description
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
params[index] = Turnip::Placeholder.apply(name.to_sym, params[index])
|
58
|
-
end
|
59
|
-
Match.new(self, params, block)
|
41
|
+
def match(description)
|
42
|
+
result = description.match(regexp)
|
43
|
+
if result
|
44
|
+
params = result.captures
|
45
|
+
result.names.each_with_index do |name, index|
|
46
|
+
params[index] = Turnip::Placeholder.apply(name.to_sym, params[index])
|
60
47
|
end
|
48
|
+
Match.new(self, params, block)
|
61
49
|
end
|
62
50
|
end
|
63
51
|
|
64
52
|
protected
|
65
53
|
|
66
|
-
def matches_metadata?(metadata)
|
67
|
-
not options[:for] or [options[:for]].flatten.any? { |option| metadata.has_key?(option) }
|
68
|
-
end
|
69
|
-
|
70
54
|
OPTIONAL_WORD_REGEXP = /(\\\s)?\\\(([^)]+)\\\)(\\\s)?/
|
71
55
|
PLACEHOLDER_REGEXP = /:([\w]+)/
|
72
56
|
ALTERNATIVE_WORD_REGEXP = /(\w+)((\/\w+)+)/
|