turnip 0.2.0 → 0.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.
- 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+)+)/
|