qed 2.6.3 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.ruby +4 -3
- data/.yardopts +3 -0
- data/HISTORY.rdoc +71 -35
- data/README.rdoc +9 -10
- data/bin/qed +1 -1
- data/bin/qedoc +2 -1
- data/lib/qed.rb +2 -5
- data/lib/qed.yml +4 -3
- data/lib/qed/applique.rb +57 -24
- data/lib/qed/cli.rb +8 -0
- data/lib/qed/cli/qed.rb +124 -0
- data/lib/qed/demo.rb +35 -39
- data/lib/qed/document.rb +5 -3
- data/lib/qed/document/template.rhtml +1 -0
- data/lib/qed/evaluator.rb +227 -199
- data/lib/qed/parser.rb +60 -282
- data/lib/qed/reporter/abstract.rb +54 -58
- data/lib/qed/reporter/dotprogress.rb +6 -4
- data/lib/qed/reporter/html.rb +112 -31
- data/lib/qed/reporter/tapy.rb +95 -125
- data/lib/qed/reporter/verbatim.rb +80 -38
- data/lib/qed/scope.rb +35 -48
- data/lib/qed/session.rb +35 -140
- data/lib/qed/settings.rb +104 -67
- data/lib/qed/step.rb +237 -0
- data/{spec → qed}/01_demos.rdoc +0 -0
- data/{spec → qed}/02_advice.rdoc +18 -7
- data/qed/03_helpers.rdoc +44 -0
- data/{spec → qed}/04_samples.rdoc +4 -4
- data/{spec → qed}/05_quote.rdoc +3 -3
- data/{spec → qed}/07_toplevel.rdoc +0 -0
- data/{spec → qed}/08_cross_script.rdoc +0 -0
- data/{spec → qed}/09_cross_script.rdoc +0 -0
- data/{spec → qed}/10_constant_lookup.rdoc +2 -2
- data/qed/11_embedded_rules.rdoc +46 -0
- data/{test/integration/topcode.rdoc → qed/99_issues/02_topcode.rdoc} +0 -0
- data/{spec → qed}/applique/constant.rb +0 -0
- data/{spec → qed}/applique/env.rb +0 -0
- data/{spec → qed}/applique/fileutils.rb +0 -0
- data/{spec → qed}/applique/markup.rb +0 -0
- data/{spec → qed}/applique/toplevel.rb +0 -0
- data/{spec → qed}/helpers/advice.rb +6 -7
- data/{spec → qed}/helpers/toplevel.rb +0 -0
- data/{spec → qed}/samples/data.txt +0 -0
- data/{spec → qed}/samples/table.yml +0 -0
- metadata +44 -39
- data/LICENSE.rdoc +0 -31
- data/SPECSHEET.rdoc +0 -456
- data/lib/qed/advice.rb +0 -158
- data/lib/qed/reporter/bullet.rb +0 -91
- data/lib/qed/reporter/dtrace.rb +0 -67
- data/lib/yard-qed.rb +0 -1
- data/spec/03_helpers.rdoc +0 -43
- data/spec/applique/quote.rb +0 -4
- data/spec/helpers/sample.rb +0 -4
data/lib/qed/cli.rb
ADDED
data/lib/qed/cli/qed.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module QED
|
2
|
+
|
3
|
+
require 'qed/session'
|
4
|
+
|
5
|
+
#
|
6
|
+
def self.cli(*argv)
|
7
|
+
Session.cli(*argv)
|
8
|
+
end
|
9
|
+
|
10
|
+
class Session
|
11
|
+
|
12
|
+
#
|
13
|
+
def self.cli(*argv)
|
14
|
+
require 'optparse'
|
15
|
+
require 'shellwords'
|
16
|
+
|
17
|
+
files, options = cli_parse(argv)
|
18
|
+
|
19
|
+
#if files.empty?
|
20
|
+
# puts "No files."
|
21
|
+
# exit -1
|
22
|
+
#end
|
23
|
+
|
24
|
+
session = Session.new(files, options)
|
25
|
+
success = session.run
|
26
|
+
|
27
|
+
exit -1 unless success
|
28
|
+
end
|
29
|
+
|
30
|
+
# Instance of OptionParser
|
31
|
+
def self.cli_parse(argv)
|
32
|
+
options = {}
|
33
|
+
options_parser = OptionParser.new do |opt|
|
34
|
+
opt.banner = "Usage: qed [options] <files...>"
|
35
|
+
|
36
|
+
opt.separator("Custom Profiles:") unless settings.profiles.empty?
|
37
|
+
|
38
|
+
settings.profiles.each do |name, value|
|
39
|
+
o = "--#{name}"
|
40
|
+
opt.on(o, "#{name} custom profile") do
|
41
|
+
options[:profile] = name.to_sym
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
opt.separator("Report Formats (pick one):")
|
46
|
+
#opt.on('--dotprogress', '-d', "use dot-progress reporter [default]") do
|
47
|
+
# options[:format] = :dotprogress
|
48
|
+
#end
|
49
|
+
opt.on('--verbatim', '-v', "shortcut for verbatim reporter") do
|
50
|
+
options[:format] = :verbatim
|
51
|
+
end
|
52
|
+
opt.on('--tapy', '-y', "shortcut for TAP-Y reporter") do
|
53
|
+
options[:format] = :tapy
|
54
|
+
end
|
55
|
+
#opt.on('--bullet', '-b', "use bullet-point reporter") do
|
56
|
+
# options[:format] = :bullet
|
57
|
+
#end
|
58
|
+
#opt.on('--html', '-h', "use underlying HTML reporter") do
|
59
|
+
# options[:format] = :html
|
60
|
+
#end
|
61
|
+
#opt.on('--script', "psuedo-reporter") do
|
62
|
+
# options[:format] = :script # psuedo-reporter
|
63
|
+
#end
|
64
|
+
opt.on('--format', '-f FORMAT', "use custom reporter") do |format|
|
65
|
+
options[:format] = format.to_sym
|
66
|
+
end
|
67
|
+
|
68
|
+
opt.separator("Control Options:")
|
69
|
+
opt.on('--comment', '-c', "run comment code") do
|
70
|
+
options[:mode] = :comment
|
71
|
+
end
|
72
|
+
opt.on('--profile', '-p NAME', "load runtime profile") do |name|
|
73
|
+
options[:profile] = name
|
74
|
+
end
|
75
|
+
opt.on('--loadpath', "-I PATH", "add paths to $LOAD_PATH") do |paths|
|
76
|
+
options[:loadpath] = paths.split(/[:;]/).map{|d| File.expand_path(d)}
|
77
|
+
end
|
78
|
+
opt.on('--require', "-r LIB", "require library") do |paths|
|
79
|
+
options[:requires] = paths.split(/[:;]/)
|
80
|
+
end
|
81
|
+
opt.on('--rooted', '-R', "run from project root instead of temporary directory") do
|
82
|
+
options[:rooted] = true
|
83
|
+
end
|
84
|
+
# COMMIT:
|
85
|
+
# The qed command --trace option takes a count.
|
86
|
+
# Use 0 to mean all.
|
87
|
+
opt.on('--trace', '-t [COUNT]', "show full backtraces for exceptions") do |cnt|
|
88
|
+
#options[:trace] = true
|
89
|
+
ENV['trace'] = cnt
|
90
|
+
end
|
91
|
+
opt.on('--warn', "run with warnings turned on") do
|
92
|
+
$VERBOSE = true # wish this were called $WARN!
|
93
|
+
end
|
94
|
+
opt.on('--debug', "exit immediately upon raised exception") do
|
95
|
+
$DEBUG = true
|
96
|
+
end
|
97
|
+
|
98
|
+
opt.separator("Optional Commands:")
|
99
|
+
opt.on_tail('--version', "display version") do
|
100
|
+
puts "QED #{QED::VERSION}"
|
101
|
+
exit
|
102
|
+
end
|
103
|
+
opt.on_tail('--copyright', "display copyrights") do
|
104
|
+
puts "Copyright (c) 2008 Thomas Sawyer, Apache 2.0 License"
|
105
|
+
exit
|
106
|
+
end
|
107
|
+
opt.on_tail('--help', '-h', "display this help message") do
|
108
|
+
puts opt
|
109
|
+
exit
|
110
|
+
end
|
111
|
+
end
|
112
|
+
options_parser.parse!(argv)
|
113
|
+
return argv, options
|
114
|
+
end
|
115
|
+
|
116
|
+
# TODO: Pass to Session class, instead of acting global.
|
117
|
+
# It is used at the class level to get profiles for the cli.
|
118
|
+
def self.settings
|
119
|
+
@settings ||= Settings.new
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
data/lib/qed/demo.rb
CHANGED
@@ -7,7 +7,7 @@ module QED
|
|
7
7
|
require 'qed/evaluator'
|
8
8
|
require 'qed/applique'
|
9
9
|
|
10
|
-
# The Demo class ecapsulates a
|
10
|
+
# The Demo class ecapsulates a demonstrandum script.
|
11
11
|
#
|
12
12
|
class Demo
|
13
13
|
|
@@ -17,27 +17,30 @@ module QED
|
|
17
17
|
# Parser mode.
|
18
18
|
attr :mode
|
19
19
|
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
#
|
20
|
+
# Steup new Demo instance.
|
21
|
+
#
|
22
|
+
# @param [String] file
|
23
|
+
# Path to demo file.
|
24
|
+
#
|
25
|
+
# @param [Hash] options
|
26
|
+
#
|
27
|
+
# @option options [Symbol] :mode
|
28
|
+
# Either `:comment` or other for normal mode.
|
29
|
+
#
|
30
|
+
# @option options [Strng] :at
|
31
|
+
# Working directory.
|
32
|
+
#
|
33
|
+
# @option options [Array] :applique
|
34
|
+
# Overriding applique. Used to import demos into other demos safely.
|
35
|
+
#
|
36
|
+
# @option options [Scope] :scope
|
37
|
+
# Overriding scope, otherwise new Scope instance is created.
|
38
|
+
#
|
27
39
|
def initialize(file, options={})
|
28
|
-
@file
|
29
|
-
@mode = options[:mode]
|
30
|
-
@cwd = options[:at] || fallback_cwd
|
40
|
+
@file = file
|
31
41
|
|
32
|
-
@
|
33
|
-
|
34
|
-
@binding = @scope.__binding__
|
35
|
-
#apply_environment
|
36
|
-
end
|
37
|
-
|
38
|
-
# One binding per demo.
|
39
|
-
def binding
|
40
|
-
@binding #||= @scope.__binding__
|
42
|
+
@mode = options[:mode]
|
43
|
+
@applique = options[:applique]
|
41
44
|
end
|
42
45
|
|
43
46
|
# Expanded dirname of +file+.
|
@@ -50,25 +53,24 @@ module QED
|
|
50
53
|
@name ||= File.basename(file).chomp(File.extname(file))
|
51
54
|
end
|
52
55
|
|
53
|
-
#
|
54
|
-
def evaluate(code, line)
|
55
|
-
#eval(code, @binding, @file, line)
|
56
|
-
@scope.eval(code, @file, line)
|
57
|
-
end
|
58
|
-
|
59
56
|
# Returns a cached Array of Applique modules.
|
60
57
|
def applique
|
61
58
|
@applique ||= (
|
62
59
|
list = [Applique.new]
|
63
60
|
applique_locations.each do |location|
|
64
|
-
Dir[location + '
|
65
|
-
list << Applique.
|
61
|
+
Dir[location + '/**/*'].each do |file|
|
62
|
+
list << Applique.for(file)
|
66
63
|
end
|
67
64
|
end
|
68
65
|
list
|
69
66
|
)
|
70
67
|
end
|
71
68
|
|
69
|
+
#
|
70
|
+
def applique_prime
|
71
|
+
applique.first
|
72
|
+
end
|
73
|
+
|
72
74
|
# Returns a list of applique directories to be used by this
|
73
75
|
# demonstrastion.
|
74
76
|
def applique_locations
|
@@ -94,20 +96,19 @@ module QED
|
|
94
96
|
|
95
97
|
# Parse and cache demonstration script.
|
96
98
|
#
|
97
|
-
# @return [Array] abstract syntax tree
|
99
|
+
# @return [Array] list of steps (abstract syntax tree)
|
98
100
|
alias_method :parse, :steps
|
99
101
|
|
100
102
|
# Get a new Parser instance for this demo.
|
101
103
|
#
|
102
104
|
# @return [Parser] parser for this demo
|
103
105
|
def parser
|
104
|
-
Parser.new(
|
106
|
+
Parser.new(self, :mode=>mode)
|
105
107
|
end
|
106
108
|
|
107
|
-
#
|
108
|
-
def run(
|
109
|
-
|
110
|
-
evaluator.run
|
109
|
+
# Run demo through {Evaluator} instance with given observers.
|
110
|
+
def run(options={})
|
111
|
+
Evaluator.run(self, options)
|
111
112
|
end
|
112
113
|
|
113
114
|
#
|
@@ -123,11 +124,6 @@ module QED
|
|
123
124
|
# )
|
124
125
|
#end
|
125
126
|
|
126
|
-
# This shouldn't be needed, but is here as a precaution.
|
127
|
-
def fallback_cwd
|
128
|
-
@dir ||= File.join(Dir.tmpdir, 'qed', File.filename(Dir.pwd), Time.new.strftime("%Y%m%d%H%M%S"))
|
129
|
-
end
|
130
|
-
|
131
127
|
end
|
132
128
|
|
133
129
|
end
|
data/lib/qed/document.rb
CHANGED
@@ -78,7 +78,11 @@ module QED
|
|
78
78
|
files = files.reject{ |f| File.directory?(f) }
|
79
79
|
files = files.reject{ |f| File.extname(f) == '.rb' }
|
80
80
|
files = files.reject{ |f| /(fixtures|helpers)\// =~ f }
|
81
|
-
|
81
|
+
|
82
|
+
# doesn't include .rb applique but does markup applique
|
83
|
+
applique, files = files.partition{ |f| /applique\// =~ f }
|
84
|
+
|
85
|
+
applique.sort + files.sort
|
82
86
|
)
|
83
87
|
end
|
84
88
|
|
@@ -103,8 +107,6 @@ module QED
|
|
103
107
|
#end
|
104
108
|
#files.sort!
|
105
109
|
|
106
|
-
#TODO: load .config/qedrc.rb
|
107
|
-
|
108
110
|
if dryrun or $DEBUG
|
109
111
|
puts demo_files.sort.join(" ")
|
110
112
|
end
|
data/lib/qed/evaluator.rb
CHANGED
@@ -2,230 +2,203 @@ module QED
|
|
2
2
|
|
3
3
|
require 'qed/scope'
|
4
4
|
|
5
|
-
#
|
5
|
+
# Demonstrandum Evaluator is responsible for running demo scripts.
|
6
|
+
#
|
6
7
|
class Evaluator
|
7
8
|
|
9
|
+
# Create new Evaluator instance and then run it.
|
10
|
+
def self.run(demo, options={})
|
11
|
+
new(demo, options).run
|
12
|
+
end
|
13
|
+
|
14
|
+
# Setup new evaluator instance.
|
15
|
+
#
|
16
|
+
# @param [Demo] demo
|
17
|
+
# The demo to run.
|
18
|
+
#
|
19
|
+
# @param [Array] observers
|
20
|
+
# Objects that respond to observable interface.
|
21
|
+
# Typically this is just a Reporter instance.
|
8
22
|
#
|
9
|
-
def initialize(
|
10
|
-
@
|
11
|
-
@steps
|
23
|
+
def initialize(demo, options={})
|
24
|
+
@demo = demo
|
25
|
+
@steps = demo.steps
|
12
26
|
|
13
|
-
#@
|
14
|
-
|
15
|
-
#@binding = script.binding
|
16
|
-
#@advice = script.advice
|
27
|
+
#@settings = options[:settings]
|
28
|
+
@applique = options[:applique] # BOOLEAN FLAG
|
17
29
|
|
18
|
-
@observers = observers
|
19
|
-
|
30
|
+
@observers = options[:observers].to_a
|
31
|
+
@observers += applique_observers
|
20
32
|
|
21
|
-
|
22
|
-
def run
|
23
|
-
advise!(:before_demo, @script)
|
24
|
-
advise!(:demo, @script)
|
25
|
-
run_steps
|
26
|
-
advise!(:after_demo, @script)
|
33
|
+
@scope = options[:scope] || Scope.new(demo)
|
27
34
|
end
|
28
35
|
|
36
|
+
# Collect applique all the signal-based advice and wrap their evaluation
|
37
|
+
# in observable procedure calls.
|
29
38
|
#
|
30
|
-
def
|
31
|
-
|
32
|
-
|
39
|
+
def applique_observers
|
40
|
+
demo = @demo
|
41
|
+
demo.applique.map do |a|
|
42
|
+
Proc.new do |type, *args|
|
43
|
+
proc = a.__signals__[type.to_sym]
|
44
|
+
@scope.instance_exec(*args, &proc) if proc
|
45
|
+
end
|
33
46
|
end
|
34
47
|
end
|
35
48
|
|
49
|
+
public
|
50
|
+
|
51
|
+
# The Demo being evaluated.
|
36
52
|
#
|
37
|
-
|
38
|
-
|
39
|
-
advise!(:before_step, step) #, @script.file)
|
40
|
-
advise!("before_#{type}".to_sym, step) #, @script.file)
|
41
|
-
case type
|
42
|
-
when :head
|
43
|
-
evaluate_head(step)
|
44
|
-
when :desc
|
45
|
-
evaluate_desc(step)
|
46
|
-
when :data
|
47
|
-
evaluate_data(step)
|
48
|
-
when :code
|
49
|
-
evaluate_code(step)
|
50
|
-
else
|
51
|
-
raise "fatal: unknown #{type}"
|
52
|
-
end
|
53
|
-
advise!("after_#{type}".to_sym, step) #, @script.file)
|
54
|
-
advise!(:after_step, step) #, @script.file)
|
55
|
-
end
|
53
|
+
# @return [Demo]
|
54
|
+
attr :demo
|
56
55
|
|
56
|
+
# The observers.
|
57
57
|
#
|
58
|
-
|
59
|
-
advise!(:head, step)
|
60
|
-
end
|
58
|
+
attr :observers
|
61
59
|
|
60
|
+
# Run the demo.
|
62
61
|
#
|
63
|
-
def
|
64
|
-
|
62
|
+
def run
|
63
|
+
advise!(:before_demo, @demo)
|
65
64
|
begin
|
66
|
-
advise!(:
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
#rescue Assertion => exception
|
71
|
-
# fail!(step, exception)
|
72
|
-
rescue Exception => exception
|
73
|
-
if exception.assertion?
|
74
|
-
fail!(step, exception)
|
75
|
-
else
|
76
|
-
error!(step, exception)
|
77
|
-
end
|
78
|
-
else
|
79
|
-
pass!(step)
|
65
|
+
advise!(:demo, @demo)
|
66
|
+
run_steps
|
67
|
+
ensure
|
68
|
+
advise!(:after_demo, @demo)
|
80
69
|
end
|
81
70
|
end
|
82
71
|
|
72
|
+
private
|
73
|
+
|
74
|
+
# Interate over each step and evaluate it.
|
83
75
|
#
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
advise!(:data, step)
|
88
|
-
rescue SystemExit
|
89
|
-
pass!(step)
|
90
|
-
#rescue Assertion => exception
|
91
|
-
# fail!(step, exception)
|
92
|
-
rescue Exception => exception
|
93
|
-
if exception.assertion?
|
94
|
-
fail!(step, exception)
|
95
|
-
else
|
96
|
-
error!(step, exception)
|
97
|
-
end
|
98
|
-
else
|
99
|
-
pass!(step)
|
76
|
+
def run_steps
|
77
|
+
@steps.each do |step|
|
78
|
+
evaluate(step)
|
100
79
|
end
|
101
80
|
end
|
81
|
+
|
82
|
+
# Evaluate a step.
|
83
|
+
#
|
84
|
+
# @macro [new] step
|
85
|
+
#
|
86
|
+
# @param [Step] step
|
87
|
+
# The step being evaluated.
|
88
|
+
#
|
89
|
+
# @return nothing
|
90
|
+
def evaluate(step)
|
91
|
+
advise!(:before_step, step)
|
92
|
+
advise!(:step, step)
|
102
93
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
@script.evaluate(step.code, step.lineno)
|
108
|
-
rescue SystemExit
|
109
|
-
pass!(step) # TODO: skip!(step)
|
110
|
-
#rescue Assertion => exception
|
111
|
-
# fail!(step, exception)
|
112
|
-
rescue Exception => exception
|
113
|
-
if exception.assertion?
|
114
|
-
fail!(step, exception)
|
115
|
-
else
|
116
|
-
error!(step, exception)
|
117
|
-
end
|
94
|
+
evaluate_links(step) unless step.heading?
|
95
|
+
|
96
|
+
if step.assertive? && !@applique
|
97
|
+
evaluate_test(step)
|
118
98
|
else
|
119
|
-
|
99
|
+
evaluate_applique(step)
|
120
100
|
end
|
101
|
+
|
102
|
+
advise!(:after_step, step)
|
121
103
|
end
|
122
104
|
|
123
105
|
# TODO: Not sure how to handle loading links in --comment runner mode.
|
124
|
-
|
106
|
+
|
107
|
+
# TODO: Should scope be reused by imported demo ?
|
108
|
+
|
109
|
+
# If there are embedded links in the step description than extract
|
110
|
+
# them and load them in.
|
111
|
+
#
|
112
|
+
# @macro step
|
125
113
|
def evaluate_links(step)
|
126
114
|
step.text.scan(/\[qed:\/\/(.*?)\]/) do |match|
|
127
115
|
file = $1
|
128
|
-
# relative to demo
|
129
|
-
if File.exist?(File.join(@
|
130
|
-
file = File.join(@
|
116
|
+
# relative to demo demo
|
117
|
+
if File.exist?(File.join(@demo.directory,file))
|
118
|
+
file = File.join(@demo.directory,file)
|
131
119
|
end
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
120
|
+
|
121
|
+
advise!(:before_import, file)
|
122
|
+
begin
|
123
|
+
advise!(:import, file)
|
124
|
+
case File.extname(file)
|
125
|
+
when '.rb'
|
126
|
+
Kernel.eval(File.read(file), @scope.__binding__, file)
|
127
|
+
else
|
128
|
+
demo = Demo.new(file)
|
129
|
+
Evaluator.new(demo, :scope=>@scope).run
|
130
|
+
end
|
131
|
+
ensure
|
132
|
+
advise!(:after_import, file)
|
138
133
|
end
|
139
134
|
end
|
140
135
|
end
|
141
136
|
|
137
|
+
# Evaluate step at the *applique level*. This means the execution
|
138
|
+
# of code and even matcher evaluations will not be captured by a
|
139
|
+
# rescue clause.
|
142
140
|
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
141
|
+
# @macro step
|
142
|
+
def evaluate_applique(step)
|
143
|
+
advise!(:before_applique, step)
|
144
|
+
begin
|
145
|
+
advise!(:applique, step)
|
146
|
+
evaluate_matchers(step)
|
147
|
+
evaluate_example(step)
|
148
|
+
ensure
|
149
|
+
advise!(:after_applique, step)
|
150
|
+
end
|
151
151
|
end
|
152
152
|
|
153
|
-
#
|
154
|
-
|
155
|
-
advise!(:error, step, exception)
|
156
|
-
#raise exception
|
157
|
-
end
|
153
|
+
# Exceptions to always raise regardless.
|
154
|
+
FORCED_EXCEPTIONS = [NoMemoryError, SignalException, Interrupt] #, SystemExit]
|
158
155
|
|
156
|
+
# Evaluate the step's matchaters and code sample, wrapped in a begin-rescue
|
157
|
+
# clause.
|
159
158
|
#
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
159
|
+
# @macro step
|
160
|
+
def evaluate_test(step)
|
161
|
+
advise!(:before_test, step)
|
162
|
+
begin
|
163
|
+
advise!(:test, step) # name ?
|
164
|
+
evaluate_matchers(step)
|
165
|
+
evaluate_example(step)
|
166
|
+
rescue *FORCED_EXCEPTIONS
|
167
|
+
raise
|
168
|
+
rescue SystemExit # TODO: why pass on SystemExit ?
|
169
|
+
advise!(:pass, step)
|
170
|
+
#rescue Assertion => exception
|
171
|
+
# advise!(:fail, step, exception)
|
172
|
+
rescue Exception => exception
|
173
|
+
if exception.assertion?
|
174
|
+
advise!(:fail, step, exception)
|
175
|
+
else
|
176
|
+
advise!(:error, step, exception)
|
177
|
+
end
|
174
178
|
else
|
175
|
-
|
179
|
+
advise!(:pass, step)
|
180
|
+
ensure
|
181
|
+
advise!(:after_test, step)
|
176
182
|
end
|
177
183
|
end
|
178
184
|
|
185
|
+
# Evaluate the step's example in the demo's context, if the example
|
186
|
+
# is source code.
|
179
187
|
#
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
# React to an event.
|
185
|
-
#
|
186
|
-
# TODO: Should events short circuit on finding first match?
|
187
|
-
# In other words, should there be only one of each type of signal
|
188
|
-
# ragardless of how many applique layers?
|
189
|
-
def call_signals(type, *args)
|
190
|
-
@script.applique.each do |a|
|
191
|
-
signals = a.__signals__
|
192
|
-
proc = signals[type.to_sym]
|
193
|
-
#signals.each do |set|
|
194
|
-
#proc = set[type.to_sym]
|
195
|
-
#proc.call(*args) if proc
|
196
|
-
@script.scope.instance_exec(*args, &proc) if proc
|
197
|
-
#end
|
198
|
-
end
|
199
|
-
|
200
|
-
#@script.applique.each do |a|
|
201
|
-
# signals = a.__signals__
|
202
|
-
# proc = signals[type.to_sym]
|
203
|
-
# if proc
|
204
|
-
# @script.scope.instance_exec(*args, &proc)
|
205
|
-
# break
|
206
|
-
# end
|
207
|
-
#end
|
208
|
-
|
209
|
-
#meth = "qed_#{type}"
|
210
|
-
#if @script.scope.respond_to?(meth)
|
211
|
-
# meth = @script.scope.method(meth)
|
212
|
-
# if meth.arity == 0
|
213
|
-
# meth.call
|
214
|
-
# else
|
215
|
-
# meth.call(*args)
|
216
|
-
# end
|
217
|
-
#end
|
218
|
-
|
219
|
-
#@script.scope.__send__(meth, *args)
|
188
|
+
# @macro step
|
189
|
+
def evaluate_example(step)
|
190
|
+
@scope.evaluate(step.code, step.file, step.lineno) if step.code?
|
220
191
|
end
|
221
192
|
|
193
|
+
# Search the step's description for applique matches and
|
194
|
+
# evaluate them.
|
222
195
|
#
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
196
|
+
# @macro step
|
197
|
+
def evaluate_matchers(step)
|
198
|
+
match = step.text
|
199
|
+
|
200
|
+
@demo.applique.each do |app|
|
201
|
+
app.__matchers__.each do |(patterns, proc)|
|
229
202
|
compare = match
|
230
203
|
matched = true
|
231
204
|
params = []
|
@@ -237,6 +210,7 @@ module QED
|
|
237
210
|
regex = match_string_to_regexp(pattern)
|
238
211
|
end
|
239
212
|
if md = regex.match(compare)
|
213
|
+
advise!(:match, step, md) # ADVISE !
|
240
214
|
params.concat(md[1..-1])
|
241
215
|
compare = md.post_match
|
242
216
|
else
|
@@ -245,46 +219,100 @@ module QED
|
|
245
219
|
end
|
246
220
|
end
|
247
221
|
if matched
|
248
|
-
params
|
249
|
-
|
250
|
-
|
222
|
+
#args = [params, arguments].reject{|e| e == []} # use single argument for params in 3.0?
|
223
|
+
args = params
|
224
|
+
args = args + [step.sample_text] if step.data?
|
225
|
+
args = proc.arity < 0 ? args : args[0,proc.arity]
|
226
|
+
|
227
|
+
#@demo.scope
|
228
|
+
@scope.instance_exec(*args, &proc) #proc.call(*args)
|
251
229
|
end
|
252
230
|
end
|
253
231
|
end
|
254
232
|
end
|
255
233
|
|
234
|
+
#
|
235
|
+
SPLIT_PATTERNS = [ /(\(\(.*?\)\)(?!\)))/, /(\/\(.*?\)\/)/, /(\/\?.*?\/)/ ]
|
236
|
+
|
237
|
+
#
|
238
|
+
SPLIT_PATTERN = Regexp.new(SPLIT_PATTERNS.join('|'))
|
239
|
+
|
256
240
|
# Convert matching string into a regular expression. If the string
|
257
241
|
# contains double parenthesis, such as ((.*?)), then the text within
|
258
242
|
# them is treated as in regular expression and kept verbatium.
|
259
243
|
#
|
260
|
-
# TODO: Better way to isolate regexp. Maybe ?:(.*?) or /(.*?)/.
|
261
|
-
#
|
262
|
-
# TODO: Now that we can use multi-patterns, do we still need this?
|
263
|
-
#
|
264
244
|
def match_string_to_regexp(str)
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
245
|
+
re = nil
|
246
|
+
str = str.split(SPLIT_PATTERN).map do |x|
|
247
|
+
case x
|
248
|
+
when /\A\(\((.*?)\)\)(?!\))/
|
249
|
+
$1
|
250
|
+
when /\A\/(\(.*?\))\//
|
251
|
+
$1
|
252
|
+
when /\A\/(\?.*?)\//
|
253
|
+
"(#{$1})"
|
254
|
+
else
|
255
|
+
Regexp.escape(x)
|
256
|
+
end
|
257
|
+
end.join
|
258
|
+
|
259
|
+
str = str.gsub(/\\\s+/, '\s+') # Replace space with variable space.
|
260
|
+
|
269
261
|
Regexp.new(str, Regexp::IGNORECASE)
|
262
|
+
end
|
263
|
+
|
264
|
+
=begin
|
265
|
+
# The following code works as well, and can provide a MatchData
|
266
|
+
# object instead of just matching params, but I call YAGNI on that
|
267
|
+
# and it has two benefits. 1) the above code is faster, and 2)
|
268
|
+
# using params allows |(name1, name2)| in rule blocks.
|
269
|
+
|
270
|
+
#
|
271
|
+
def evaluate_matchers(step)
|
272
|
+
match = step.text
|
273
|
+
args = step.arguments
|
274
|
+
@demo.applique.each do |a|
|
275
|
+
matchers = a.__matchers__
|
276
|
+
matchers.each do |(patterns, proc)|
|
277
|
+
re = build_matcher_regexp(*patterns)
|
278
|
+
if md = re.match(match)
|
279
|
+
#params = [step.text.strip] + params
|
280
|
+
#proc.call(*params)
|
281
|
+
@demo.scope.instance_exec(md, *args, &proc)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
270
286
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
287
|
+
#
|
288
|
+
def build_matcher_regexp(*patterns)
|
289
|
+
parts = []
|
290
|
+
patterns.each do |pattern|
|
291
|
+
case pattern
|
292
|
+
when Regexp
|
293
|
+
parts << pattern
|
294
|
+
else
|
295
|
+
parts << match_string_to_regexp(pattern)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
Regexp.new(parts.join('.*?'), Regexp::MULTILINE)
|
282
299
|
end
|
300
|
+
=end
|
283
301
|
|
284
|
-
|
285
|
-
|
286
|
-
#
|
287
|
-
#
|
302
|
+
# TODO: pass demo to advice?
|
303
|
+
|
304
|
+
# Dispatch an advice event to observers.
|
305
|
+
#
|
306
|
+
# @param [Symbol] signal
|
307
|
+
# The name of the dispatch.
|
308
|
+
#
|
309
|
+
# @param [Array<Object>] args
|
310
|
+
# Any arguments to send along witht =the signal to the observers.
|
311
|
+
#
|
312
|
+
# @return nothing
|
313
|
+
def advise!(signal, *args)
|
314
|
+
@observers.each{ |o| o.call(signal.to_sym, *args) }
|
315
|
+
end
|
288
316
|
|
289
317
|
end
|
290
318
|
|