lib/lopata/step.rb in lopata-0.1.1 vs lib/lopata/step.rb in lopata-0.1.2

- old
+ new

@@ -1,33 +1,143 @@ module Lopata class Step - attr_reader :block, :status, :exception, :args, :condition + attr_reader :block, :args, :condition, :method_name, :shared_step, :group + # metadata overrien by the step. + attr_accessor :metadata - def initialize(method_name, *args, condition: nil, &block) + def initialize(method_name, *args, condition: nil, shared_step: nil, group: nil, &block) @method_name = method_name @args = args - @status = :not_started @block = block + @shared_step = shared_step + @condition = condition + @group = group + initialized! if defined? initialized! + end + + def title + base_title = args.first + base_title ||= shared_step && "#{method_name.capitalize} #{shared_step.name}" || "Untitled #{method_name}" + if group + "#{group.title}: #{base_title}" + else + base_title + end + end + + def execution_steps(scenario, groups: []) + return [] if condition && !condition.match?(scenario) + return [] unless block + [StepExecution.new(self, groups, &block)] + end + end + + # Used for action, setup, teardown + class ActionStep < Step + def execution_steps(scenario, groups: []) + steps = [] + return steps if condition && !condition.match?(scenario) + convert_args(scenario).each do |step| + if step.is_a?(String) + Lopata::SharedStep.find(step).steps.each do |shared_step| + next if shared_step.condition && !shared_step.condition.match?(scenario) + steps += shared_step.execution_steps(scenario, groups: groups) + end + elsif step.is_a?(Proc) + steps << StepExecution.new(self, groups, &step) + end + end + steps << StepExecution.new(self, groups, &block) if block + steps.reject { |s| !s.block } + end + + def separate_args(args) + args.map { |a| a.is_a?(String) && a =~ /,/ ? a.split(',').map(&:strip) : a }.flatten + end + + def convert_args(scenario) + flat_args = separate_args(args.flatten) + flat_args.map do |arg| + case arg + # trait symbols as link to metadata. + when Symbol then scenario.metadata[arg] + else + arg + end + end.flatten + end + + def title + if group + "%s: %s" % [group.title, method_name] + else + shared_step && "#{method_name.capitalize} #{shared_step.name}" || "Untitled #{method_name}" + end + end + end + + # Used for context + class GroupStep < Step + + def execution_steps(scenario, groups: []) + steps = [] + return steps if condition && !condition.match?(scenario) + @steps.each do |step| + steps += step.execution_steps(scenario, groups: groups + [self]) + end + steps.reject! { |s| !s.block } + steps.reject { |s| s.teardown_group?(self) } + steps.select { |s| s.teardown_group?(self) } + end + + private + + # Group step's block is a block in context of builder, not scenario. So hide the @block to not be used in scenario. + def initialized! + builder = Lopata::ScenarioBuilder.new(title) + builder.group = self + builder.instance_exec(&@block) + @steps = builder.steps + @block = nil + end + end + + class StepExecution + attr_reader :step, :status, :exception, :block, :pending_message, :groups + extend Forwardable + def_delegators :step, :title, :method_name + + class PendingStepFixedError < StandardError; end + + def initialize(step, groups, &block) + @step = step + @status = :not_runned @exception = nil - @condition = condition || Lopata::Condition::EMPTY + @block = block + @groups = groups end def run(scenario) @status = :running world.notify_observers(:step_started, self) begin run_step(scenario) - @status = :passed + if pending? + @status = :failed + raise PendingStepFixedError, 'Expected step to fail since it is pending, but it passed.' + else + @status = :passed + end rescue Exception => e - @status = :failed + @status = :failed unless pending? @exception = e end world.notify_observers(:step_finished, self) end def run_step(scenario) - scenario.instance_exec(&block) if block + return unless block + scenario.instance_exec(&block) end def world @world ||= Lopata::Config.world end @@ -38,49 +148,40 @@ def passed? status == :passed end - def teardown? - %i{ teardown cleanup }.include?(@method_name) + def skipped? + status == :skipped end - def pre_steps(scenario) - [] + def skip! + @status = :skipped end - end - # Used for action, setup, teardown - class ActionStep < Step - def pre_steps(scenario) - steps = [] - convert_args(scenario).each do |step| - if step.is_a?(String) - Lopata::SharedStep.find(step).steps.each do |shared_step| - steps += shared_step.pre_steps(scenario) - steps << shared_step - end - elsif step.is_a?(Proc) - steps << Lopata::Step.new(method_name, &step) - end - end - steps + def pending? + status == :pending end - def separate_args(args) - args.map { |a| a.is_a?(String) && a =~ /,/ ? a.split(',').map(&:strip) : a }.flatten + def pending!(message = nil) + @status = :pending + @pending_message = message end - def convert_args(scenario) - flat_args = separate_args(args.flatten) - flat_args.map do |arg| - case arg - # trait symbols as link to metadata. - when Symbol then scenario.metadata[arg] - else - arg - end - end.flatten + def teardown? + %i{ teardown cleanup }.include?(method_name) end + def teardown_group?(group = nil) + teardown? && self.groups.last == group + end + + def skip_rest_on_failure? + %i{ setup action }.include?(method_name) + end + + # Step metadata is a combination of metadata given for step and all contexts (groups) the step included + def metadata + ([step] + groups).compact.inject({}) { |merged, part| merged.merge(part.metadata) } + end end -end \ No newline at end of file +end