Permalink
Browse files

Support example.feature:20 syntax for running features at a line number

  • Loading branch information...
1 parent 88650dd commit 0808cf78e46d0f8b73a5623c41ebcf8debbeb204 Joseph Wilk committed Nov 17, 2008
View
@@ -1,9 +1,14 @@
== 0.1.10 (In Git)
-Blurb
+Running multiple features at specific lines numbers:
+
+cucumber examples/i18n/en/features/addition.feature:15 examples/i18n/en/features/division.feature:6
+
+Will run addition.feature at line 15 and divison.feature at line 6
=== New features
* Try to load webrat gem if it's not installed as a plugin (Aslak Hellesøy)
+* Support example.feature:20 syntax for running features at a line number. (#88 Joseph Wilk)
=== Bugfixes
* Features written using Ruby where breaking due to missing a line number (#91 Joseph Wilk)
View
@@ -22,7 +22,7 @@ def parse(args)
end
end
- attr_reader :options
+ attr_reader :options, :paths
FORMATS = %w{pretty profile progress html autotest}
DEFAULT_FORMAT = 'pretty'
@@ -130,8 +130,9 @@ def parse_options!(args)
if @options[:formats].empty?
@options[:formats][DEFAULT_FORMAT] = [@out_stream]
end
-
+
# Whatever is left after option parsing is the FILE arguments
+ args = extract_and_store_line_numbers(args)
@paths += args
end
@@ -165,13 +166,26 @@ def execute!(step_mother, executor, features)
require_files
load_plain_text_features(features)
executor.line = @options[:line].to_i if @options[:line]
+ executor.lines_for_features = @options[:lines_for_features]
executor.scenario_names = @options[:scenario_names] if @options[:scenario_names]
executor.visit_features(features)
exit 1 if executor.failed
end
private
+ def extract_and_store_line_numbers(file_arguments)
+ @options[:lines_for_features] = Hash.new{|k,v| k[v] = []}
+ file_arguments.map do |arg|
+ _, file, line = */^([\w\W]*):(\d+)$/.match(arg)
+ unless file.nil?
+ @options[:lines_for_features][file] << line.to_i
+ arg = file
+ end
+ arg
+ end
+ end
+
# Requires files - typically step files and ruby feature files.
def require_files
ARGV.clear # Shut up RSpec
View
@@ -4,7 +4,7 @@ module Cucumber
class Executor
attr_reader :failed
attr_accessor :formatters
- attr_writer :scenario_names
+ attr_writer :scenario_names, :lines_for_features
def line=(line)
@line = line
@@ -50,6 +50,8 @@ def visit_features(features)
end
def visit_feature(feature)
+ @feature_file = feature.file
+
if accept_feature?(feature)
formatters.feature_executing(feature)
feature.accept(self)
@@ -96,6 +98,7 @@ def execute_scenario(scenario)
def accept_scenario?(scenario)
accept = true
+ accept &&= @lines_for_features[@feature_file].inject(false) { |at_line, line| at_line || scenario.at_line?(line) } if !@line && lines_defined_for_current_feature?
accept &&= scenario.at_line?(@line) if @line
accept &&= @scenario_names.include? scenario.name if @scenario_names && !@scenario_names.empty?
accept
@@ -166,5 +169,9 @@ def executing_unprepared_row_scenario?(scenario)
accept_scenario?(scenario) && !@executed_scenarios[scenario.name]
end
+ def lines_defined_for_current_feature?
+ @lines_for_features && !@lines_for_features[@feature_file].nil? && !@lines_for_features[@feature_file].empty?
+ end
+
end
end
@@ -144,15 +144,31 @@ def name
@template_scenario.name
end
+ #We can only cache steps once the template scenarios steps have been bound in order to know what arity the steps have
def steps
- @steps ||= @template_scenario.steps.map do |template_step|
+ if template_steps_bound?
+ @unbound_steps = nil
+ @steps ||= build_steps
+ else
+ @unbound_steps ||= build_steps
+ end
+ end
+
+ private
+ def build_steps
+ @template_scenario.steps.map do |template_step|
args = []
template_step.arity.times do
args << @values.shift
end
RowStep.new(self, template_step, args)
end
end
+
+ def template_steps_bound?
+ @template_steps_bound ||= @template_scenario.steps.inject(0) { |arity_sum, step| arity_sum + step.arity } != 0
+ end
+
end
end
end
View
@@ -5,7 +5,7 @@ module Cucumber
describe CLI do
def mock_executor(stubs = {})
- stub('executor', {:visit_features => nil, :failed => false, :formatters= => nil}.merge(stubs))
+ stub('executor', {:visit_features => nil, :lines_for_features= => nil, :failed => false, :formatters= => nil}.merge(stubs))
end
def mock_broadcaster(stubs = {})
@@ -227,6 +227,27 @@ def given_cucumber_yml_defined_as(hash)
cli.execute!(stub('step mother'), mock_executor, stub('features'))
end
+ it "should extract line numbers attached to the end of feature file args" do
+ cli = CLI.new
+ cli.parse_options!(%w{example.feature:10})
+
+ cli.options[:lines_for_features]['example.feature'].should == [10]
+ end
+
+ it "should remove line numbers attached to the end of feature file" do
+ cli = CLI.new
+ cli.parse_options!(%w{example.feature:10})
+
+ cli.paths.should == ["example.feature"]
+ end
+
+ it "should support multiple feature:line numbers" do
+ cli = CLI.new
+ cli.parse_options!(%w{example.feature:11 another_example.feature:12})
+
+ cli.options[:lines_for_features].should == {'another_example.feature' => [12], 'example.feature' => [11]}
+ end
+
it "should search for all features in the specified directory" do
cli = CLI.new
@@ -3,6 +3,17 @@
module Cucumber
describe Executor do
+
+ def mock_scenario(stubs = {})
+ @scenario ||= stub("scenario", {
+ :row? => false,
+ :name => 'test',
+ :accept => nil,
+ :steps => [],
+ :pending? => true
+ }.merge(stubs))
+ end
+
before do # TODO: Way more setup and duplication of lib code. Use lib code!
@io = StringIO.new
@step_mother = StepMother.new
@@ -35,7 +46,7 @@ module Cucumber
1)
dang
-#{__FILE__}:32:in `Then /I should owe (\\d*) cucumbers/'
+#{__FILE__}:43:in `Then /I should owe (\\d*) cucumbers/'
#{@feature_file}:9:in `Then I should owe 7 cucumbers'
})
end
@@ -49,6 +60,17 @@ module Cucumber
# @io.string.should == "...\n"
# end
+ describe "visiting feature" do
+
+ it "should set the feature file being visited" do
+ mock_feature = mock('feature', :file => 'womble.feature', :scenarios => [])
+ @executor.visit_feature(mock_feature)
+
+ @executor.instance_variable_get('@feature_file').should == 'womble.feature'
+ end
+
+ end
+
describe "visiting steps" do
def make_regex(a,b,c)
exp = "#{a}.*#{b}.*#{c}"
@@ -172,18 +194,53 @@ def mock_row_scenario(stubs = {})
end
end
- describe "caching visited scenarios" do
+ describe "visiting scenarios" do
+
+ it "should check if a scenario is at the specified line number" do
+ mock_scenario = mock('scenario', :null_object => true)
+ @executor.line = 1
- def mock_scenario(stubs = {})
- @scenario ||= stub("scenario", {
- :row? => false,
- :name => 'test',
- :accept => nil,
- :steps => [],
- :pending? => true
- }.merge(stubs))
+ mock_scenario.should_receive(:at_line?).with(1)
+
+ @executor.visit_scenario(mock_scenario)
end
-
+
+ describe "with specific features and lines" do
+
+ it "should check if a scenario is at the specified feature line number" do
+ @executor.instance_variable_set('@feature_file', 'sell_cucumbers.feature')
+ @executor.lines_for_features = {'sell_cucumbers.feature' => [11]}
+
+ mock_scenario.should_receive(:at_line?).with(11).and_return(false)
+
+ @executor.visit_scenario(mock_scenario)
+ end
+
+ it "should not check feature line numbers if --line is already set" do
+ @executor.instance_variable_set('@feature_file', 'sell_cucumbers.feature')
+ @executor.lines_for_features = {'sell_cucumbers.feature' => [11]}
+ @executor.line = 5
+
+ mock_scenario.should_not_receive(:at_line?).with(11).and_return(false)
+
+ @executor.visit_scenario(mock_scenario)
+ end
+
+ it "should not check feature line numbers if the current feature file does not have lines specified" do
+ @executor.instance_variable_set('@feature_file', 'beetlejuice.feature')
+ @executor.lines_for_features = {'sell_cucumbers.feature' => [11]}
+
+ mock_scenario.should_not_receive(:at_line?).with(11).and_return(false)
+
+ @executor.visit_scenario(mock_scenario)
+ end
+
+ end
+
+ end
+
+ describe "caching visited scenarios" do
+
it "should reset cache after each feature visit" do
Tree::Scenario.stub!(:new).and_return(mock_scenario)
@@ -218,5 +275,6 @@ def mock_scenario(stubs = {})
@executor.visit_features(@features)
end
end
+
end
end
@@ -4,6 +4,10 @@ module Cucumber
module Tree
describe RowScenario do
+ def mock_scenario(stubs = {})
+ mock('scenario', {:update_table_column_widths => nil, :steps => []}.merge(stubs))
+ end
+
describe "pending?" do
before :each do
@scenario = Scenario.new(nil, '', 1)
@@ -18,8 +22,34 @@ module Tree
@scenario.create_step('Given', 'a long step', 1)
@row_scenario.should_not be_pending
end
+ end
+
+ describe "generating row steps" do
+
+ it "should cache unbound steps" do
+ row_scenario = RowScenario.new(mock('feature'), mock_scenario, [], 1)
+
+ row_scenario.steps.should equal(row_scenario.steps)
+ end
+ it "should cache bound steps" do
+ mock_step = mock('step', :arity => 1)
+ row_scenario = RowScenario.new(mock('feature'), mock_scenario(:steps => [mock_step]), [], 1)
+
+ row_scenario.steps.should equal(row_scenario.steps)
+ end
+
+ it "should regenerate row steps when scenario template steps have been matched" do
+ mock_step = mock('step', :arity => 0)
+ row_scenario = RowScenario.new(mock('feature'), mock_scenario(:steps => [mock_step]), [], 1)
+ unbound_steps = row_scenario.steps
+ mock_step.stub!(:arity => 1)
+
+ unbound_steps.should_not equal(row_scenario.steps)
+ end
+
end
+
end
end
end

0 comments on commit 0808cf7

Please sign in to comment.