require 'cucumber/step_definition'
require 'cucumber/world'
require 'cucumber/core_ext/instance_exec'
module Cucumber
class Undefined < StandardError
attr_reader :step_name
def initialize(step_name)
super %{Undefined step: "#{step_name}"}
@step_name = step_name
end
def nested!
@nested = true
end
def nested?
@nested
end
end
# Raised when a StepDefinition's block invokes World#pending
class Pending < StandardError
end
# Raised when a step matches 2 or more StepDefinition
class Ambiguous < StandardError
def initialize(step_name, step_definitions, used_guess)
message = "Ambiguous match of \"#{step_name}\":\n\n"
message << step_definitions.map{|sd| sd.backtrace_line}.join("\n")
message << "\n\n"
message << "You can run again with --guess to make Cucumber be more smart about it\n" unless used_guess
super(message)
end
end
# Raised when 2 or more StepDefinition have the same Regexp
class Redundant < StandardError
def initialize(step_def_1, step_def_2)
message = "Multiple step definitions have the same Regexp:\n\n"
message << step_def_1.backtrace_line << "\n"
message << step_def_2.backtrace_line << "\n\n"
super(message)
end
end
class NilWorld < StandardError
def initialize
super("World procs should never return nil")
end
end
class MultipleWorld < StandardError
def initialize(first_proc, second_proc)
message = "You can only pass a proc to #World once, but it's happening\n"
message << "in 2 places:\n\n"
message << first_proc.backtrace_line('World') << "\n"
message << second_proc.backtrace_line('World') << "\n\n"
message << "Use Ruby modules instead to extend your worlds. See the Cucumber::StepMother#World RDoc\n"
message << "or http://wiki.github.com/aslakhellesoy/cucumber/a-whole-new-world.\n\n"
super(message)
end
end
# This is the main interface for registering step definitions, which is done
# from *_steps.rb files. This module is included right at the top-level
# so #register_step_definition (and more interestingly - its aliases) are
# available from the top-level.
module StepMother
class Hook
def initialize(tag_names, proc)
@tag_names = tag_names.map{|tag| Ast::Tags.strip_prefix(tag)}
@proc = proc
end
def matches_tag_names?(tag_names)
@tag_names.empty? || (@tag_names & tag_names).any?
end
def execute_in(world, scenario, location, exception_fails_scenario = true)
begin
world.cucumber_instance_exec(false, location, scenario, &@proc)
rescue Exception => exception
if exception_fails_scenario
scenario.fail!(exception)
else
raise
end
end
end
end
class << self
def alias_adverb(adverb)
adverb = adverb.gsub(/\s/, '')
alias_method adverb, :register_step_definition
end
end
attr_writer :snippet_generator, :options, :visitor
def step_visited(step)
steps << step unless steps.index(step)
end
def steps(status = nil)
@steps ||= []
if(status)
@steps.select{|step| step.status == status}
else
@steps
end
end
def scenarios(status = nil)
@scenarios ||= []
if(status)
@scenarios.select{|scenario| scenario.status == status}
else
@scenarios
end
end
# Registers a new StepDefinition. This method is aliased
# to Given, When and Then.
#
# See Cucumber#alias_steps for details on how to
# create your own aliases.
#
# The +&proc+ gets executed in the context of a world
# object, which is defined by #World. A new world
# object is created for each scenario and is shared across
# step definitions within that scenario.
def register_step_definition(regexp, &proc)
step_definition = StepDefinition.new(regexp, &proc)
step_definitions.each do |already|
raise Redundant.new(already, step_definition) if already.match(regexp)
end
step_definitions << step_definition
step_definition
end
# Registers a Before proc. You can call this method as many times as you
# want (typically from ruby scripts under support).
def Before(*tag_names, &proc)
register_hook(:before, tag_names, proc)
end
def After(*tag_names, &proc)
register_hook(:after, tag_names, proc)
end
def AfterStep(*tag_names, &proc)
register_hook(:after_step, tag_names, proc)
end
def register_hook(phase, tags, proc)
hook = Hook.new(tags, proc)
hooks[phase] << hook
hook
end
def hooks
@hooks ||= Hash.new {|hash, phase| hash[phase] = []}
end
def hooks_for(phase, scenario)
hooks[phase].select{|hook| scenario.accept_hook?(hook)}
end
# Registers any number of +world_modules+ (Ruby Modules) and/or a Proc.
# The +proc+ will be executed once before each scenario to create an
# Object that the scenario's steps will run within. Any +world_modules+
# will be mixed into this Object (via Object#extend).
#
# This method is typically called from one or more Ruby scripts under
# features/support. You can call this method as many times as you
# like (to register more modules), but if you try to register more than
# one Proc you will get an error.
#
# Cucumber will not yield anything to the +proc+ (like it used to do before v0.3).
#
# In earlier versions of Cucumber (before 0.3) you could not register
# any +world_modules+. Instead you would register several Proc objects (by
# calling the method several times). The result of each +proc+ would be yielded
# to the next +proc+. Example:
#
# World do |world| # NOT SUPPORTED FROM 0.3
# MyClass.new
# end
#
# World do |world| # NOT SUPPORTED FROM 0.3
# world.extend(MyModule)
# end
#
# From Cucumber 0.3 the recommended way to do this is:
#
# World do
# MyClass.new
# end
#
# World(MyModule)
#
def World(*world_modules, &proc)
if(proc)
raise MultipleWorld.new(@world_proc, proc) if @world_proc
@world_proc = proc
end
@world_modules ||= []
@world_modules += world_modules
end
def current_world
@current_world
end
def step_match(step_name, formatted_step_name=nil)
matches = step_definitions.map { |d| d.step_match(step_name, formatted_step_name) }.compact
raise Undefined.new(step_name) if matches.empty?
matches = best_matches(step_name, matches) if matches.size > 1 && options[:guess]
raise Ambiguous.new(step_name, matches, options[:guess]) if matches.size > 1
matches[0]
end
def best_matches(step_name, step_matches)
max_arg_length = step_matches.map {|step_match| step_match.args.length }.max
top_groups = step_matches.select {|step_match| step_match.args.length == max_arg_length }
if top_groups.length > 1
shortest_capture_length = top_groups.map {|step_match| step_match.args.inject(0) {|sum, c| sum + c.length } }.min
top_groups.select {|step_match| step_match.args.inject(0) {|sum, c| sum + c.length } == shortest_capture_length }
else
top_groups
end
end
def step_definitions
@step_definitions ||= []
end
def snippet_text(step_keyword, step_name, multiline_arg_class)
@snippet_generator.snippet_text(step_keyword, step_name, multiline_arg_class)
end
def before_and_after(scenario, skip=false)
before(scenario) unless skip
@current_scenario = scenario
yield scenario
@current_scenario = nil
after(scenario) unless skip
scenario_visited(scenario)
end
def before(scenario)
unless current_world
new_world!
execute_before(scenario)
end
end
def after(scenario)
execute_after(scenario)
nil_world!
end
def after_step
execute_after_step(@current_scenario)
end
private
def max_step_definition_length
@max_step_definition_length ||= step_definitions.map{|step_definition| step_definition.text_length}.max
end
def options
@options || {}
end
# Creates a new world instance
def new_world!
return if options[:dry_run]
create_world!
extend_world
connect_world
@current_world
end
def create_world!
if(@world_proc)
@current_world = @world_proc.call
check_nil(@current_world, @world_proc)
else
@current_world = Object.new
end
end
def extend_world
@current_world.extend(World)
@current_world.extend(::Spec::Matchers) if defined?(::Spec::Matchers)
(@world_modules || []).each do |mod|
@current_world.extend(mod)
end
end
def connect_world
@current_world.__cucumber_step_mother = self
@current_world.__cucumber_visitor = @visitor
end
def check_nil(o, proc)
if o.nil?
begin
raise NilWorld.new
rescue NilWorld => e
e.backtrace.clear
e.backtrace.push(proc.backtrace_line("World"))
raise e
end
else
o
end
end
def nil_world!
@current_world = nil
end
def execute_before(scenario)
return if options[:dry_run]
hooks_for(:before, scenario).each do |hook|
hook.execute_in(@current_world, scenario, 'Before')
end
end
def execute_after(scenario)
return if options[:dry_run]
hooks_for(:after, scenario).each do |hook|
hook.execute_in(@current_world, scenario, 'After')
end
end
def execute_after_step(scenario)
return if options[:dry_run]
hooks_for(:after_step, scenario).each do |hook|
hook.execute_in(@current_world, scenario, 'AfterStep', false)
end
end
def scenario_visited(scenario)
scenarios << scenario unless scenarios.index(scenario)
end
def options
@options || {}
end
end
end