Class: QED::Evaluator

Inherits:
Object show all
Defined in:
lib/qed/evaluator.rb

Overview

Demonstrandum Evaluator is responsible for running demo scripts.

Constant Summary collapse

FORCED_EXCEPTIONS =

Exceptions to always raise regardless.

[NoMemoryError, SignalException, Interrupt]
SPLIT_PATTERNS =
[ /(\(\(.*?\)\)(?!\)))/, /(\/\(.*?\)\/)/, /(\/\?.*?\/)/ ]
SPLIT_PATTERN =
Regexp.new(SPLIT_PATTERNS.join('|'))

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(demo, options = {}) ⇒ Evaluator

Setup new evaluator instance.

Parameters:

  • demo (Demo)

    The demo to run.

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :applique (Boolean)

    Is this applique code?

  • :observers (Array)

    Objects that respond to observable interface. Typically this is just a Reporter instance.


26
27
28
29
30
31
32
33
34
35
36
37
# 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

Instance Attribute Details

#demoDemo (readonly)

The Demo being evaluated.

Returns:


57
58
59
# File 'lib/qed/evaluator.rb', line 57

def demo
  @demo
end

#observersObject (readonly)

The observers.


61
62
63
# File 'lib/qed/evaluator.rb', line 61

def observers
  @observers
end

Class Method Details

.run(demo, options = {}) ⇒ Object

Create new Evaluator instance and then run it.


10
11
12
# File 'lib/qed/evaluator.rb', line 10

def self.run(demo, options={})
  new(demo, options).run
end

Instance Method Details

#advise!(signal, *args) ⇒ Object (private)

Dispatch an advice event to observers.

Parameters:

  • signal (Symbol)

    The name of the dispatch.

  • args (Array<Object>)

    Any arguments to send along witht =the signal to the observers.

Returns:

  • nothing


319
320
321
# File 'lib/qed/evaluator.rb', line 319

def advise!(signal, *args)
  @observers.each{ |o| o.call(signal.to_sym, *args) }
end

#applique_observersObject

Collect applique all the signal-based advice and wrap their evaluation in observable procedure calls.


42
43
44
45
46
47
48
49
50
# 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

#evaluate(step) ⇒ Object (private)

Evaluate a step.

Parameters:

  • step (Step)

    The step being evaluated.

Returns:

  • nothing


93
94
95
96
97
98
99
100
101
102
103
104
105
106
# 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) ⇒ Object (private)

Evaluate step at the *applique level*. This means the execution of code and even matcher evaluations will not be captured by a rescue clause.


148
149
150
151
152
153
154
155
156
157
# 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) ⇒ Object (private)

Evaluate the step's example in the demo's context, if the example is source code.


195
196
197
# 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.


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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

#evaluate_matchers(step) ⇒ Object (private)

Search the step's description for applique matches and evaluate them.


203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# 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) ⇒ Object (private)

Evaluate the step's matchers and code sample, wrapped in a begin-rescue clause.


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# 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) ⇒ Object (private)

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.


250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# 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

#runObject

Run the demo.


65
66
67
68
69
70
71
72
73
# 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

#run_stepsObject (private)

Interate over each step and evaluate it.


79
80
81
82
83
# File 'lib/qed/evaluator.rb', line 79

def run_steps
  @steps.each do |step|
    evaluate(step)
  end
end