turnip 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +5 -0
  3. data/README.md +196 -44
  4. data/Rakefile +8 -0
  5. data/examples/alignment_steps.rb +7 -0
  6. data/examples/autoload_steps.feature +5 -0
  7. data/examples/autoload_steps.rb +5 -0
  8. data/examples/dragon_steps.rb +17 -0
  9. data/examples/evil_steps.rb +7 -0
  10. data/examples/knight_steps.rb +29 -0
  11. data/examples/more_steps.rb +7 -0
  12. data/examples/neutral_steps.rb +7 -0
  13. data/examples/red_dragon_steps.rb +18 -0
  14. data/examples/step_calling.feature +14 -0
  15. data/examples/step_calling_steps.rb +23 -0
  16. data/examples/steps.rb +0 -22
  17. data/examples/steps_for_super.feature +16 -0
  18. data/lib/turnip.rb +12 -12
  19. data/lib/turnip/builder.rb +20 -4
  20. data/lib/turnip/config.rb +18 -0
  21. data/lib/turnip/dsl.rb +19 -13
  22. data/lib/turnip/feature_file.rb +20 -0
  23. data/lib/turnip/loader.rb +6 -3
  24. data/lib/turnip/placeholder.rb +1 -1
  25. data/lib/turnip/runner_dsl.rb +9 -0
  26. data/lib/turnip/scenario_context.rb +41 -0
  27. data/lib/turnip/scenario_runner.rb +35 -0
  28. data/lib/turnip/step_definition.rb +14 -30
  29. data/lib/turnip/step_loader.rb +27 -0
  30. data/lib/turnip/step_module.rb +89 -0
  31. data/lib/turnip/table.rb +12 -0
  32. data/lib/turnip/version.rb +1 -1
  33. data/spec/builder_spec.rb +41 -2
  34. data/spec/dsl_spec.rb +22 -34
  35. data/spec/feature_file_spec.rb +18 -0
  36. data/spec/integration_spec.rb +1 -1
  37. data/spec/runner_dsl_spec.rb +23 -0
  38. data/spec/scenario_context_spec.rb +51 -0
  39. data/spec/scenario_runner_spec.rb +79 -0
  40. data/spec/spec_helper.rb +1 -3
  41. data/spec/step_definition_spec.rb +22 -34
  42. data/spec/step_loader_spec.rb +29 -0
  43. data/spec/step_module_spec.rb +106 -0
  44. data/spec/table_spec.rb +8 -1
  45. data/turnip.gemspec +2 -1
  46. 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
@@ -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
@@ -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(content)
18
- Turnip::Builder.build(content).features.each do |feature|
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
- scenario.steps.each do |step|
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
@@ -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(content)
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
@@ -1,24 +1,30 @@
1
1
  module Turnip
2
2
  module DSL
3
- class << self
4
- attr_accessor :current_taggings
3
+ def placeholder(name, &block)
4
+ Turnip::Placeholder.add(name, &block)
5
5
  end
6
6
 
7
- def step(description, options={}, &block)
8
- if Turnip::DSL.current_taggings
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(*taggings)
15
- Turnip::DSL.current_taggings = [taggings, *Turnip::DSL.current_taggings].compact.flatten
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
- def placeholder(name, &block)
21
- Turnip::Placeholder.add(name, &block)
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
@@ -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
- require 'spec_helper'
6
- content = File.read(a.first)
7
- Turnip.run(content)
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
@@ -27,7 +27,7 @@ module Turnip
27
27
 
28
28
  def default
29
29
  @default ||= new(:default) do
30
- match %r((?:"([^"]+)"|([a-zA-Z0-9_-]+))) do |first, second|
30
+ match %r((?:["']([^["']]+)["']|([a-zA-Z0-9_-]+))) do |first, second|
31
31
  first or second
32
32
  end
33
33
  end
@@ -0,0 +1,9 @@
1
+ module Turnip
2
+ module RunnerDSL
3
+ attr_accessor :turnip_runner
4
+
5
+ def step(description, extra_arg = nil)
6
+ turnip_runner.run_steps([Turnip::Builder::Step.new(description, extra_arg)])
7
+ end
8
+ end
9
+ end
@@ -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, :options
10
+ attr_reader :expression, :block
12
11
 
13
12
  class << self
14
- def execute(context, step)
15
- match = find(step.description, context.example.metadata)
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 add(expression, options={}, &block)
24
- all << StepDefinition.new(expression, options, &block)
25
- end
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, options={}, &block)
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, metadata={})
52
- if matches_metadata?(metadata)
53
- result = description.match(regexp)
54
- if result
55
- params = result.captures
56
- result.names.each_with_index do |name, index|
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+)+)/