class QED::Evaluator
Demonstrandum Evaluator
is responsible for running demo scripts.
Constants
- FORCED_EXCEPTIONS
Exceptions to always raise regardless.
- SPLIT_PATTERN
- SPLIT_PATTERNS
Attributes
The Demo
being evaluated.
@return [Demo]
The observers.
Public Class Methods
Setup new evaluator instance.
@param [Demo] demo
The demo to run.
@option options [Boolean] :applique
Is this applique code?
@option options [Array] :observers
Objects that respond to observable interface. Typically this is just a Reporter instance.
# File lib/qed/evaluator.rb, line 26 def initialize(demo, options={}) @demo = demo @steps = demo.steps #@settings = options[:settings] @applique = options[:applique] # BOOLEAN FLAG @observers = options[:observers].to_a @observers += applique_observers @scope = options[:scope] || Scope.new(demo) end
Create new Evaluator
instance and then run it.
# File lib/qed/evaluator.rb, line 10 def self.run(demo, options={}) new(demo, options).run end
Public Instance Methods
Collect applique all the signal-based advice and wrap their evaluation in observable procedure calls.
# File lib/qed/evaluator.rb, line 42 def applique_observers demo = @demo demo.applique.map do |a| Proc.new do |type, *args| proc = a.__signals__[type.to_sym] @scope.instance_exec(*args, &proc) if proc end end end
Run the demo.
# File lib/qed/evaluator.rb, line 65 def run advise!(:before_demo, @demo) begin advise!(:demo, @demo) run_steps ensure advise!(:after_demo, @demo) end end
Private Instance Methods
Dispatch an advice event to observers.
@param [Symbol] signal
The name of the dispatch.
@param [Array<Object>] args
Any arguments to send along witht =the signal to the observers.
@return nothing
# File lib/qed/evaluator.rb, line 319 def advise!(signal, *args) @observers.each{ |o| o.call(signal.to_sym, *args) } end
Evaluate a step.
@macro [new] step
@param [Step] step
The step being evaluated.
@return nothing
# File lib/qed/evaluator.rb, line 93 def evaluate(step) advise!(:before_step, step) advise!(:step, step) evaluate_links(step) unless step.heading? if step.assertive? && !@applique evaluate_test(step) else evaluate_applique(step) end advise!(:after_step, step) end
Evaluate step at the *applique level*. This means the execution of code and even matcher evaluations will not be captured by a rescue clause.
@macro step
# File lib/qed/evaluator.rb, line 148 def evaluate_applique(step) advise!(:before_applique, step) begin advise!(:applique, step) evaluate_matchers(step) evaluate_example(step) ensure advise!(:after_applique, step) end end
Evaluate the step’s example in the demo’s context, if the example is source code.
@macro step
# File lib/qed/evaluator.rb, line 195 def evaluate_example(step) @scope.evaluate(step.code, step.file, step.lineno) if step.code? end
If there are embedded links in the step description than extract them and load them in.
@macro step
# File lib/qed/evaluator.rb, line 119 def evaluate_links(step) step.text.scan(/(?:\(|\[)qed:\/\/(.*?)(?:\)|\])/) do |match| file = $1 # relative to demo demo if File.exist?(File.join(@demo.directory,file)) file = File.join(@demo.directory,file) end advise!(:before_import, file) begin advise!(:import, file) case File.extname(file) when '.rb' Kernel.eval(File.read(file), @scope.__binding__, file) else demo = Demo.new(file) Evaluator.new(demo, :scope=>@scope).run end ensure advise!(:after_import, file) end end end
Search the step’s description for applique matches and evaluate them.
@macro step
# File lib/qed/evaluator.rb, line 203 def evaluate_matchers(step) match = step.text @demo.applique.each do |app| app.__matchers__.each do |(patterns, proc)| compare = match matched = true params = [] patterns.each do |pattern| case pattern when Regexp regex = pattern else regex = match_string_to_regexp(pattern) end if md = regex.match(compare) advise!(:match, step, md) # ADVISE ! params.concat(md[1..-1]) compare = md.post_match else matched = false break end end if matched #args = [params, arguments].reject{|e| e == []} # use single argument for params in 3.0? args = params args = args + [step.sample_text] if step.data? args = proc.arity < 0 ? args : args[0,proc.arity] #@demo.scope @scope.instance_exec(*args, &proc) #proc.call(*args) end end end end
Evaluate the step’s matchers and code sample, wrapped in a begin-rescue clause.
@macro step
# File lib/qed/evaluator.rb, line 166 def evaluate_test(step) advise!(:before_test, step) begin advise!(:test, step) # name ? evaluate_matchers(step) evaluate_example(step) rescue *FORCED_EXCEPTIONS raise rescue SystemExit # TODO: why pass on SystemExit ? advise!(:pass, step) #rescue Assertion => exception # advise!(:fail, step, exception) rescue Exception => exception if exception.assertion? advise!(:fail, step, exception) else advise!(:error, step, exception) end else advise!(:pass, step) ensure advise!(:after_test, step) end end
Convert matching string into a regular expression. If the string contains double parenthesis, such as ((.*?)), then the text within them is treated as in regular expression and kept verbatium.
# File lib/qed/evaluator.rb, line 250 def match_string_to_regexp(str) re = nil str = str.split(SPLIT_PATTERN).map do |x| case x when /\A\(\((.*?)\)\)(?!\))/ $1 when /\A\/(\(.*?\))\// $1 when /\A\/(\?.*?)\// "(#{$1})" else Regexp.escape(x) end end.join str = str.gsub(/\\\s+/, '\s+') # Replace space with variable space. Regexp.new(str, Regexp::IGNORECASE) end
Interate over each step and evaluate it.
# File lib/qed/evaluator.rb, line 79 def run_steps @steps.each do |step| evaluate(step) end end