lib/lopata/scenario.rb in lopata-0.0.16 vs lib/lopata/scenario.rb in lopata-0.1.0
- old
+ new
@@ -1,393 +1,76 @@
+require 'rspec/expectations'
+
class Lopata::Scenario
- def self.define(title = nil, metadata = nil, &block)
- scenario = new
- scenario.title(title) if title
- scenario.metadata(metadata) if metadata
- scenario.instance_exec &block
- scenario.build_rspec
- end
+ include RSpec::Matchers
- # Do noting. Exclude defined scenario from suite.
- def self.xdefine(*attrs)
- end
+ attr_reader :title, :metadata, :steps, :status
- def build_rspec
- option_combinations.each do |option_set|
- args = prepare_args(option_set)
- raise "scenario required a name in first argument" unless args.first.is_a? String
- steps = @steps
- spec = RSpec.describe(*args)
- spec.send :extend, RSpecInjections
- spec.nested_with_as(*args) do
- steps.each do |block|
- instance_exec &block
- end
- if Lopata::Config.after_scenario
- instance_exec &Lopata::Config.after_scenario
- end
- end
- end
+ def initialize(*args)
+ @title = args.first
+ @metadata = args.last.is_a?(Hash) ? args.last : {}
+ @steps = []
+ @status = :not_runned
end
- def as(*args, &block)
- @roles = args.flatten
- @roles << CalculatedValue.new(&block) if block_given?
- @role_options = nil
+ def run
+ @status = :running
+ world.notify_observers(:scenario_started, self)
+ teardown_steps = []
+ @steps.reject(&:teardown?).each { |step| step.run(self) }
+ @steps.select(&:teardown?).each { |step| step.run(self) }
+ @status = @steps.all?(&:passed?) ? :passed : :failed
+ world.notify_observers(:scenario_finished, self)
end
- def role_options
- @role_options ||= build_role_options
- end
-
- def metadata(hash)
- raise 'metadata expected to be a Hash' unless hash.is_a?(Hash)
- @common_metadata ||= {}
- @common_metadata.merge! hash
- end
-
- def without_user
- @without_user = true
- end
-
- def skip_when(&block)
- @skip_when = block
- end
-
- def skip?(option_set)
- @skip_when && @skip_when.call(option_set)
- end
-
- %i{setup action it context teardown include_context include_examples}.each do |name|
- name_if = "%s_if" % name
- name_unless = "%s_unless" % name
- define_method name, ->(*args, &block) { add_step(name, *args, &block) }
- define_method name_if, ->(condition, *args, &block) { add_if_step(name, condition, *args, &block) }
- define_method name_unless, ->(condition, *args, &block) { add_unless_step(name, condition, *args, &block) }
- end
-
- def cleanup(*args, &block)
- add_step_as_is(:cleanup, *args, &block)
- end
-
- def add_step(method_name, *args, &block)
- @steps ||= []
- @steps << Proc.new do
- # will be called in context of rspec group
- flat_args = args.flatten
- flat_args = Lopata::Scenario.separate_args(flat_args) if method_name =~ /^(setup|action)/
- converted_args = Lopata::Scenario.convert_args(metadata, *flat_args)
- send method_name, *converted_args, &block
+ def match_metadata?(metadata_key)
+ case metadata_key
+ when Hash
+ metadata_key.keys.all? { |k| metadata[k] == metadata_key[k] }
+ when Array
+ metadata_key.map { |key| metadata[key] }.none?(&:nil?)
+ else
+ metadata[metadata_key]
end
end
- def add_if_step(method_name, condition, *args, &block)
- @steps ||= []
- @steps << Proc.new do
- # will be called in context of rspec group
- if match_metadata?(condition)
- flat_args = args.flatten
- flat_args = Lopata::Scenario.separate_args(flat_args) if method_name =~ /^(setup|action)/
- converted_args = Lopata::Scenario.convert_args(metadata, *flat_args)
- send method_name, *converted_args, &block
- end
- end
+ def run_step(method_name, *args, &block)
+ instance_exec(&block)
end
- def add_unless_step(method_name, condition, *args, &block)
- @steps ||= []
- @steps << Proc.new do
- # will be called in context of rspec group
- unless match_metadata?(condition)
- flat_args = args.flatten
- flat_args = Lopata::Scenario.separate_args(flat_args) if method_name =~ /^(setup|action)/
- converted_args = Lopata::Scenario.convert_args(metadata, *flat_args)
- send method_name, *converted_args, &block
- end
- end
+ def world
+ @world ||= Lopata::Config.world
end
- def add_step_as_is(method_name, *args, &block)
- @steps ||= []
- @steps << Proc.new do
- # do not convert args - symbols mean name of instance variable
- send method_name, *args, &block
- end
- end
-
- def let_metadata(*keys)
- @steps ||= []
- @steps << Proc.new do
- m = metadata
- keys.each do |key|
- define_method key do
- m[key]
- end
-
- define_singleton_method key do
- m[key]
- end
- end
- end
- end
-
- def let_method(method_name, &block)
- @steps ||= []
- @steps << Proc.new do
- define_method method_name, &block
- define_singleton_method method_name, &block
- end
- end
-
- def steps(&block)
- @steps ||= []
- @steps << block
- end
-
- def steps_if(metadata_key, &block)
- @steps ||= []
- @steps << Proc.new do
- if match_metadata?(metadata_key)
- instance_exec &block
- end
- end
- end
- alias steps_for steps_if
-
- def steps_unless(metadata_key, &block)
- @steps ||= []
- @steps << Proc.new do
- unless match_metadata?(metadata_key)
- instance_exec &block
- end
- end
- end
- alias steps_for_not steps_unless
-
- def self.convert_args(metadata, *args)
+ def convert_args(*args)
args.map do |arg|
case arg
# trait symbols as link to metadata.
when Symbol then metadata[arg]
else
arg
end
end.flatten
end
- def self.separate_args(args)
+ def separate_args(args)
args.map { |a| a.is_a?(String) && a =~ /,/ ? a.split(',').map(&:strip) : a }.flatten
end
- def build_role_options
- return [] unless roles
- [Diagonal.new(:as, roles.map { |r| [nil, r] })]
+ def failed?
+ status == :failed
end
- def roles
- return false if @without_user
- @roles ||= [Lopata::Config.default_role].compact
- end
+ private
- def title(value)
- @title = value
- end
-
- def option(metadata_key, variants)
- options << Option.new(metadata_key, variants)
- end
-
- def diagonal(metadata_key, variants)
- diagonals << Diagonal.new(metadata_key, variants)
- end
-
- def options
- @options ||= []
- end
-
- def diagonals
- @diagonals ||= []
- end
-
- def option_combinations
- combinations = combine([OptionSet.new], options + diagonals + role_options)
- while !(diagonals + role_options).all?(&:complete?)
- combinations << OptionSet.new(*(options + diagonals + role_options).map(&:next_variant))
- end
- combinations.reject { |option_set| skip?(option_set) }
- end
-
- def combine(source_combinations, rest_options)
- # raise 'source_combinations cannot be empty' if source_combinations.blank?
- return source_combinations if rest_options.blank?
- combinations = []
- current_option = rest_options.shift
- source_combinations.each do |source_variants|
- current_option.level_variants.each do |v|
- combinations << (source_variants + OptionSet.new(v))
+ def method_missing(method, *args, &block)
+ if metadata.keys.include?(method)
+ metadata[method]
+ else
+ super
end
end
- combine(combinations, rest_options)
- end
- def prepare_args(option_set, *args)
- options_title, metadata = option_set.title, option_set.metadata
- if args[0].is_a? String
- args[0] = [@title, options_title, args[0]].reject(&:blank?).join(' ')
- else
- args.unshift([@title, options_title].reject(&:blank?).join(' '))
+ def respond_to_missing?(method, *)
+ metadata.keys.include?(method) or super
end
-
- metadata.merge!(@common_metadata) if @common_metadata
-
- if args.last.is_a? Hash
- args.last.merge!(metadata)
- else
- args << metadata
- end
- args
- end
-
- # Набор вариантов, собранный для одного теста
- class OptionSet
- attr_reader :variants
- def initialize(*variants)
- @variants = {}
- variants.each { |v| self << v }
- end
-
- def +(other_set)
- self.class.new(*@variants.values).tap do |sum|
- other_set.each { |v| sum << v }
- end
- end
-
- def <<(variant)
- @variants[variant.key] = variant
- end
-
- def [](key)
- @variants[key]
- end
-
- def each(&block)
- @variants.values.each(&block)
- end
-
- def title
- @variants.values.map(&:title).compact.join(' ')
- end
-
- def metadata
- @variants.values.inject({}) do |metadata, variant|
- metadata.merge(variant.metadata(self))
- end
- end
- end
-
- class Variant
- attr_reader :key, :title, :value
-
- def initialize(key, title, value)
- @key, @title, @value = key, title, check_lambda_arity(value)
- end
-
- def metadata(option_set)
- data = { key => value }
- if value.is_a? Hash
- value.each do |k, v|
- sub_key = "%s_%s" % [key, k]
- data[sub_key.to_sym] = v
- end
- end
-
- data.each do |key, v|
- data[key] = v.calculate(option_set) if v.is_a? CalculatedValue
- end
- data
- end
-
- def self.join(variants)
- title, metadata = nil, {}
- variants.each do |v|
- title = [title, v.title].compact.join(' ')
- metadata.merge!(v.metadata)
- end
- [title, metadata]
- end
-
- private
-
- # Лямдда будет передаваться как блок в instance_eval, которому плохеет, если пришло что-то с нулевой
- # arity. Поэтому для лямбд с нулевой arity делаем arity == 1
- def check_lambda_arity(v)
- if v.is_a?(Proc) && v.arity == 0
- ->(_) { instance_exec(&v) }
- else
- v
- end
- end
- end
-
- class CalculatedValue
- def initialize(&block)
- @proc = block
- end
-
- def calculate(option_set)
- @proc.call(option_set)
- end
- end
-
- class Option
- attr_reader :variants
- def initialize(key, variants)
- @variants =
- if variants.is_a? Hash
- variants.map { |title, value| Variant.new(key, title, value) }
- else
- # Array of arrays of two elements
- variants.map { |v| Variant.new(key, *v) }
- end
- end
-
- # Variants to apply at one level
- def level_variants
- variants
- end
-
- def next_variant
- @current ||= 0
- selected_variant = variants[@current]
- @current += 1
- if @current >= variants.length
- @current = 0
- @complete = true # all variants have been selected
- end
- selected_variant
- end
- end
-
- class Diagonal < Option
- def level_variants
- [next_variant]
- end
-
- def complete?
- @complete
- end
- end
-
- # RSpec helpers for spec builing
- module RSpecInjections
- def match_metadata?(metadata_key)
- case metadata_key
- when Hash
- metadata_key.keys.all? { |k| metadata[k] == metadata_key[k] }
- when Array
- metadata_key.map { |key| metadata[key] }.none?(&:nil?)
- else
- metadata[metadata_key]
- end
- end
- end
-end
+end
\ No newline at end of file