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.
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+)+)/