class QED::Evaluator

Demonstrandum Evaluator is responsible for running demo scripts.

Constants

FORCED_EXCEPTIONS

Exceptions to always raise regardless.

SPLIT_PATTERN
SPLIT_PATTERNS

Attributes

demo[R]

The Demo being evaluated.

@return [Demo]

observers[R]

The observers.

Public Class Methods

new(demo, options={}) click to toggle source

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
run(demo, options={}) click to toggle source

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

applique_observers() click to toggle source

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() click to toggle source

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

advise!(signal, *args) click to toggle source

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(step) click to toggle source

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_applique(step) click to toggle source

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_example(step) click to toggle source

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
evaluate_matchers(step) click to toggle source

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_test(step) click to toggle source

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
match_string_to_regexp(str) click to toggle source

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
run_steps() click to toggle source

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