exemplor 2010.0.2 → 2010.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +30 -30
- data/VERSION +1 -1
- data/examples/assertion_success_and_failure.rb +1 -1
- data/examples/assertion_success_and_info.rb +2 -2
- data/lib/checker.rb +39 -0
- data/lib/command.rb +9 -0
- data/lib/environment.rb +113 -0
- data/lib/examples.rb +62 -0
- data/lib/exemplor.rb +50 -288
- data/lib/ext.rb +15 -0
- data/lib/result_printer.rb +67 -0
- metadata +9 -3
data/README
CHANGED
@@ -6,20 +6,20 @@ An example-based test framework inspired by [testy](http://github.com/ahoward/te
|
|
6
6
|
Simpler that BDD/TDD, fancier than a file with a bunch of print statements.
|
7
7
|
|
8
8
|
Very tiny api. Here it is
|
9
|
-
|
9
|
+
|
10
10
|
eg.helpers do
|
11
11
|
... your helpers ...
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
eg.setup { ... setup codez ... }
|
15
|
-
|
15
|
+
|
16
16
|
eg "an example" do
|
17
17
|
... example code ...
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
eg "another example" do
|
21
21
|
... etc ...
|
22
|
-
end
|
22
|
+
end
|
23
23
|
|
24
24
|
The output is both human readable and machine parsable, which means you can test your tests.
|
25
25
|
|
@@ -29,29 +29,29 @@ Writing Examples
|
|
29
29
|
See `examples.rb` and `/examples` for more examples.
|
30
30
|
|
31
31
|
The simplest possible example:
|
32
|
-
|
32
|
+
|
33
33
|
eg 'An example block without any checks prints the value of the block' do
|
34
34
|
"foo"
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
#=>
|
38
|
-
|
38
|
+
|
39
39
|
(i) An example block without any checks prints the value of the block: foo
|
40
40
|
|
41
41
|
The 'i' stands for 'info' which means the example ran without error.
|
42
42
|
|
43
43
|
Inspecting a few values, by default `Check` prints its argument and the value of the argument:
|
44
|
-
|
44
|
+
|
45
45
|
eg 'Accessing different parts of an array' do
|
46
46
|
list = [1, 2, 3]
|
47
47
|
Check(list.first)
|
48
48
|
Check(list[1])
|
49
49
|
Check(list.last)
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
#=>
|
53
|
-
|
54
|
-
(I) Accessing different parts of an array:
|
53
|
+
|
54
|
+
(I) Accessing different parts of an array:
|
55
55
|
(i) list.first: 1
|
56
56
|
(i) list[1]: 2
|
57
57
|
(i) list.last: 3
|
@@ -66,31 +66,31 @@ Calls to `Check` with the same argument can be disambiguated with `[]`:
|
|
66
66
|
list << 2
|
67
67
|
Check(list.last)["after append"]
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
#=>
|
71
|
-
|
72
|
-
(I) Array appending:
|
71
|
+
|
72
|
+
(I) Array appending:
|
73
73
|
(i) list.last before append: 42
|
74
74
|
(i) list.last after append: 2
|
75
75
|
|
76
76
|
Errors are caught and reported nicely:
|
77
|
-
|
77
|
+
|
78
78
|
eg 'Raising an error' do
|
79
|
-
raise "boom!"
|
79
|
+
raise "boom!"
|
80
80
|
end
|
81
81
|
|
82
82
|
#=>
|
83
83
|
|
84
|
-
(e) Raising an error:
|
84
|
+
(e) Raising an error:
|
85
85
|
class: RuntimeError
|
86
86
|
message: boom!
|
87
|
-
backtrace:
|
87
|
+
backtrace:
|
88
88
|
- examples/an_error.rb:4
|
89
89
|
# ... more backtrace lines
|
90
90
|
|
91
91
|
Once you're happy with how your code is running you can make some assertions about its behaviour by adding `is()` calls after your `Check()` statements:
|
92
|
-
|
93
|
-
|
92
|
+
|
93
|
+
|
94
94
|
eg 'Asserting first is first' do
|
95
95
|
list = [1, 2, 3]
|
96
96
|
Check(list.first).is(1)
|
@@ -98,7 +98,7 @@ Once you're happy with how your code is running you can make some assertions abo
|
|
98
98
|
|
99
99
|
#=>
|
100
100
|
|
101
|
-
(s) Asserting first is first:
|
101
|
+
(s) Asserting first is first:
|
102
102
|
(s) list.first: 1
|
103
103
|
|
104
104
|
's' stands for 'success' and 'f' for failure:
|
@@ -110,8 +110,8 @@ Once you're happy with how your code is running you can make some assertions abo
|
|
110
110
|
|
111
111
|
#=>
|
112
112
|
|
113
|
-
(f) Assertion failure:
|
114
|
-
(f) list.first:
|
113
|
+
(f) Assertion failure:
|
114
|
+
(f) list.first:
|
115
115
|
expected: 2
|
116
116
|
actual: 1
|
117
117
|
|
@@ -138,15 +138,15 @@ Running with `--list` or `-l` lists all examples:
|
|
138
138
|
- called with some other arg (always interpreted as a regex)
|
139
139
|
|
140
140
|
Otherwise the first argument is taken as a regex and only examples whose titles match are run:
|
141
|
-
|
141
|
+
|
142
142
|
$> ruby examples.rb called
|
143
|
-
(s) called with --list arg:
|
144
|
-
(s) list:
|
143
|
+
(s) called with --list arg:
|
144
|
+
(s) list:
|
145
145
|
- Modified env
|
146
146
|
- Unmodified env
|
147
|
-
(s) called with --l arg:
|
148
|
-
(s) list:
|
147
|
+
(s) called with --l arg:
|
148
|
+
(s) list:
|
149
149
|
- Modified env
|
150
150
|
- Unmodified env
|
151
|
-
(s) called with some other arg (always interpreted as a regex):
|
151
|
+
(s) called with some other arg (always interpreted as a regex):
|
152
152
|
(s) tests_run: 1
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2010.0
|
1
|
+
2010.1.0
|
data/lib/checker.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Exemplor
|
2
|
+
class Check
|
3
|
+
|
4
|
+
attr_reader :expectation, :value, :status
|
5
|
+
|
6
|
+
def initialize(name, value)
|
7
|
+
@name = name
|
8
|
+
@value = value
|
9
|
+
@status = :info
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](disambiguate)
|
13
|
+
@disambiguate = disambiguate
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@name + (defined?(@disambiguate) ? " #{@disambiguate}" : '')
|
19
|
+
end
|
20
|
+
|
21
|
+
def is(expectation)
|
22
|
+
@expectation = expectation
|
23
|
+
@status = (value == expectation) ? :success : :failure
|
24
|
+
end
|
25
|
+
|
26
|
+
def success?
|
27
|
+
status == :success
|
28
|
+
end
|
29
|
+
|
30
|
+
def failure?
|
31
|
+
status == :failure
|
32
|
+
end
|
33
|
+
|
34
|
+
def info?
|
35
|
+
status == :info
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/command.rb
ADDED
data/lib/environment.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
module Exemplor
|
2
|
+
|
3
|
+
class ExampleEnv
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
alias_method :helpers, :class_eval
|
8
|
+
attr_accessor :setup_block
|
9
|
+
|
10
|
+
def setup(&blk) self.setup_block = blk end
|
11
|
+
|
12
|
+
# runs the block in the example environment, returns triple:
|
13
|
+
# [status, result, stderr]
|
14
|
+
def run(&code)
|
15
|
+
env = self.new
|
16
|
+
stderr = fake_stderr!
|
17
|
+
status, result = begin
|
18
|
+
|
19
|
+
env.instance_eval(&self.setup_block) if self.setup_block
|
20
|
+
value = env.instance_eval(&code)
|
21
|
+
result = env._status == :info ?
|
22
|
+
render_value(value) : render_checks(env._checks)
|
23
|
+
[env._status, result]
|
24
|
+
|
25
|
+
rescue Object => error
|
26
|
+
[:error, render_error(error)]
|
27
|
+
ensure
|
28
|
+
restore_stderr!
|
29
|
+
end
|
30
|
+
[status, result, stderr.rewind && stderr.read]
|
31
|
+
end
|
32
|
+
|
33
|
+
# tests are run with a fake stderr so warnings output can be assoicated
|
34
|
+
# with the specific test. this is still a little hokey and hard to test
|
35
|
+
# properly
|
36
|
+
def fake_stderr!
|
37
|
+
fake = StringIO.new
|
38
|
+
@real_stderr = $stderr
|
39
|
+
$stderr = fake
|
40
|
+
end
|
41
|
+
|
42
|
+
def restore_stderr!
|
43
|
+
$stderr = @real_stderr
|
44
|
+
end
|
45
|
+
|
46
|
+
# -- these "render" methods could probably be factored away
|
47
|
+
|
48
|
+
# yaml doesn't want to print a class
|
49
|
+
def render_value(value)
|
50
|
+
value.kind_of?(Class) ? value.inspect : value
|
51
|
+
end
|
52
|
+
|
53
|
+
def render_checks(checks)
|
54
|
+
failure = nil
|
55
|
+
out = []
|
56
|
+
checks.each do |check|
|
57
|
+
failure = check if check.failure?
|
58
|
+
break if failure
|
59
|
+
out << OrderedHash do |o|
|
60
|
+
o['name'] = check.name
|
61
|
+
o['status'] = check.status.to_s
|
62
|
+
o['result'] = render_value check.value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
if failure
|
66
|
+
out << OrderedHash do |o|
|
67
|
+
o['name'] = failure.name
|
68
|
+
o['status'] = failure.status.to_s
|
69
|
+
o['expected'] = failure.expectation
|
70
|
+
o['actual'] = render_value failure.value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
out
|
74
|
+
end
|
75
|
+
|
76
|
+
def render_error(error)
|
77
|
+
OrderedHash do |o|
|
78
|
+
o['class'] = error.class.name
|
79
|
+
o['message'] = error.message
|
80
|
+
o['backtrace'] = error.backtrace
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_accessor :_checks
|
87
|
+
|
88
|
+
def initialize
|
89
|
+
@_checks = []
|
90
|
+
end
|
91
|
+
|
92
|
+
def Check(value)
|
93
|
+
file, line_number = caller.first.match(/^(.+):(\d+)/).captures
|
94
|
+
line = File.readlines(file)[line_number.to_i - 1].strip
|
95
|
+
name = line[/Check\((.+?)\)\s*($|#|\[|\.is.+)/,1]
|
96
|
+
check = Check.new(name, value)
|
97
|
+
_checks << check
|
98
|
+
check
|
99
|
+
end
|
100
|
+
|
101
|
+
def _status
|
102
|
+
(:info if _checks.empty?) ||
|
103
|
+
(:failure if _checks.any? { |c| c.failure? }) ||
|
104
|
+
(:success if _checks.all? { |c| c.success? }) ||
|
105
|
+
:infos
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
def environment
|
111
|
+
ExampleEnv
|
112
|
+
end
|
113
|
+
end
|
data/lib/examples.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Exemplor
|
2
|
+
|
3
|
+
def examples
|
4
|
+
@examples ||= Examples.new
|
5
|
+
end
|
6
|
+
|
7
|
+
# sets @example_file to first file that calls the `eg` method
|
8
|
+
def set_example_file_from(caller_trace)
|
9
|
+
@example_file ||= caller_trace.first.split(":").first
|
10
|
+
end
|
11
|
+
|
12
|
+
def example_file_set?
|
13
|
+
!!@example_file
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_directly?
|
17
|
+
@example_file == $0
|
18
|
+
end
|
19
|
+
|
20
|
+
class ExampleDefinitionError < StandardError ; end
|
21
|
+
|
22
|
+
def make_example_name_from(caller_trace)
|
23
|
+
file, line_number = caller_trace.first.match(/^(.+):(\d+)/).captures
|
24
|
+
line = File.readlines(file)[line_number.to_i - 1].strip
|
25
|
+
name = line[/^eg\s*\{\s*(.+?)\s*\}$/,1]
|
26
|
+
raise Exemplor::ExampleDefinitionError, "example at #{caller_trace.first} has no name so must be on one line" if name.nil?
|
27
|
+
name
|
28
|
+
end
|
29
|
+
|
30
|
+
class Examples
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@examples = OrderedHash.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def add(name, &body)
|
37
|
+
@examples[name] = body
|
38
|
+
end
|
39
|
+
|
40
|
+
def run(patterns)
|
41
|
+
fails = 0
|
42
|
+
# unoffically supports multiple patterns
|
43
|
+
patterns = Regexp.new(patterns.join('|'))
|
44
|
+
examples_to_run = @examples.select { |name,_| name =~ patterns }
|
45
|
+
return 0 if examples_to_run.empty?
|
46
|
+
examples_to_run.each do |name, code|
|
47
|
+
result = ResultPrinter.new(name, *ExampleEnv.run(&code))
|
48
|
+
fails +=1 if result.failure?
|
49
|
+
puts($stdout.tty? ? result.fancy : result.yaml)
|
50
|
+
end
|
51
|
+
(fails.to_f/examples_to_run.size)*100
|
52
|
+
end
|
53
|
+
|
54
|
+
def list(patterns)
|
55
|
+
patterns = Regexp.new(patterns.join('|'))
|
56
|
+
list = @examples.keys.select { |name| name =~ patterns }
|
57
|
+
print YAML.without_header(list)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/lib/exemplor.rb
CHANGED
@@ -1,305 +1,67 @@
|
|
1
|
+
# -- Implementation
|
2
|
+
|
1
3
|
require 'orderedhash'
|
2
4
|
require 'yaml'
|
3
5
|
|
4
|
-
def OrderedHash(&blk)
|
5
|
-
ohsh = OrderedHash.new
|
6
|
-
blk.call(ohsh)
|
7
|
-
ohsh
|
8
|
-
end
|
9
|
-
|
10
|
-
def YAML.without_header(obj)
|
11
|
-
obj.to_yaml.match(/^--- \n?/).post_match
|
12
|
-
end
|
13
|
-
|
14
|
-
class String
|
15
|
-
def indent
|
16
|
-
self.split("\n").map { |line| ' ' + line }.join("\n")
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
6
|
module Exemplor
|
21
7
|
|
22
|
-
|
23
|
-
|
24
|
-
class ExampleDefinitionError < StandardError ; end
|
25
|
-
|
26
|
-
class Check
|
27
|
-
|
28
|
-
attr_reader :expectation, :value
|
29
|
-
|
30
|
-
def initialize(name, value)
|
31
|
-
@name = name
|
32
|
-
@value = value
|
33
|
-
end
|
34
|
-
|
35
|
-
def [](disambiguate)
|
36
|
-
@disambiguate = disambiguate
|
37
|
-
self
|
38
|
-
end
|
39
|
-
|
40
|
-
def name
|
41
|
-
@name + (defined?(@disambiguate) ? " #{@disambiguate}" : '')
|
42
|
-
end
|
43
|
-
|
44
|
-
def is(expectation)
|
45
|
-
@expectation = expectation
|
46
|
-
end
|
47
|
-
|
48
|
-
def status
|
49
|
-
return :info unless defined? @expectation
|
50
|
-
@value == @expectation ? :success : :failure
|
51
|
-
end
|
52
|
-
|
53
|
-
def success?
|
54
|
-
status == :success
|
55
|
-
end
|
56
|
-
|
57
|
-
def failure?
|
58
|
-
status == :failure
|
59
|
-
end
|
60
|
-
|
61
|
-
def info?
|
62
|
-
status == :info
|
63
|
-
end
|
8
|
+
extend self
|
64
9
|
|
10
|
+
def path(rel_path)
|
11
|
+
File.join(File.dirname(__FILE__), rel_path)
|
65
12
|
end
|
66
13
|
|
67
|
-
|
68
|
-
|
69
|
-
attr_reader :name,:status,:result,:stderr
|
70
|
-
|
71
|
-
def initialize(name,status,result,stderr)
|
72
|
-
@name,@status,@result,@stderr = name,status,result,stderr
|
73
|
-
end
|
74
|
-
|
75
|
-
def failure?
|
76
|
-
[:error,:failure].include?(self.status)
|
77
|
-
end
|
78
|
-
|
79
|
-
def yaml
|
80
|
-
hsh = OrderedHash do |o|
|
81
|
-
o['name'] = self.name
|
82
|
-
o['status'] = case status = self.status
|
83
|
-
when :info : 'info (no checks)'
|
84
|
-
when :infos : 'info (with checks)'
|
85
|
-
else ; status.to_s
|
86
|
-
end
|
87
|
-
o['result'] = self.result
|
88
|
-
end
|
89
|
-
YAML.without_header([hsh])# prints an array
|
90
|
-
end
|
91
|
-
|
92
|
-
def fancy
|
93
|
-
# •∙ are inverted in my terminal font (Incosolata) so I'm swapping them
|
94
|
-
require 'term/ansicolor'
|
95
|
-
case status
|
96
|
-
when :info : blue format_info("• #{name}", result)
|
97
|
-
when :infos
|
98
|
-
formatted_result = result.map do |r|
|
99
|
-
# TODO: successful ones should be green
|
100
|
-
format_info("#{{'success' => '✓', 'info' => '•' }[r['status']]} #{r['name']}", r['result']).rstrip
|
101
|
-
end.join("\n")
|
102
|
-
blue("∙ #{name}\n#{formatted_result.indent}")
|
103
|
-
when :success
|
104
|
-
green("✓ #{name}")
|
105
|
-
when :failure
|
106
|
-
# sooo hacky
|
107
|
-
failure = result.find { |r| r['status'] == 'failure' }
|
108
|
-
out = failure.dup
|
109
|
-
out.delete('status')
|
110
|
-
out.delete('name')
|
111
|
-
color(:red, "✗ #{name} - #{failure['name']}\n#{YAML.without_header(out).indent}")
|
112
|
-
when :error
|
113
|
-
class_and_message = "#{result['class']} - #{result['message']}"
|
114
|
-
backtrace = result['backtrace'].join("\n")
|
115
|
-
color(:red, "☠ #{name}\n#{class_and_message.indent}\n#{backtrace.indent}")
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def blue(str) color(:blue,str) end
|
120
|
-
def green(str) color(:green,str) end
|
121
|
-
|
122
|
-
def color(color, str)
|
123
|
-
[Term::ANSIColor.send(color), str, Term::ANSIColor.reset].join
|
124
|
-
end
|
125
|
-
|
126
|
-
# whatahack
|
127
|
-
def format_info(str, result)
|
128
|
-
YAML.without_header({'FANCY' => result}).sub('FANCY', str)
|
129
|
-
end
|
130
|
-
|
14
|
+
def version
|
15
|
+
File.read(path('/../VERSION'))
|
131
16
|
end
|
132
17
|
|
133
|
-
|
134
|
-
|
135
|
-
class << self
|
136
|
-
|
137
|
-
alias_method :helpers, :class_eval
|
138
|
-
attr_accessor :setup_block
|
139
|
-
|
140
|
-
def setup(&blk) self.setup_block = blk end
|
141
|
-
|
142
|
-
# runs the block in the example environment, returns triple:
|
143
|
-
# [status, result, stderr]
|
144
|
-
def run(&code)
|
145
|
-
env = self.new
|
146
|
-
stderr = StringIO.new
|
147
|
-
status, result = begin
|
148
|
-
real_stderr = $stderr ; $stderr = stderr # swap stderr
|
149
|
-
|
150
|
-
env.instance_eval(&self.setup_block) if self.setup_block
|
151
|
-
value = env.instance_eval(&code)
|
152
|
-
result = env._status == :info ?
|
153
|
-
render_value(value) : render_checks(env._checks)
|
154
|
-
[env._status, result]
|
155
|
-
|
156
|
-
rescue Object => error
|
157
|
-
[:error, render_error(error)]
|
158
|
-
ensure
|
159
|
-
$stderr = real_stderr # swap stderr back
|
160
|
-
end
|
161
|
-
[status, result, stderr.rewind && stderr.read]
|
162
|
-
end
|
163
|
-
|
164
|
-
# -- these "render" methods could probably be factored away
|
165
|
-
|
166
|
-
# yaml doesn't want to print a class
|
167
|
-
def render_value(value)
|
168
|
-
value.kind_of?(Class) ? value.inspect : value
|
169
|
-
end
|
170
|
-
|
171
|
-
def render_checks(checks)
|
172
|
-
failure = nil
|
173
|
-
out = []
|
174
|
-
checks.each do |check|
|
175
|
-
failure = check if check.failure?
|
176
|
-
break if failure
|
177
|
-
out << OrderedHash do |o|
|
178
|
-
o['name'] = check.name
|
179
|
-
o['status'] = check.status.to_s
|
180
|
-
o['result'] = render_value check.value
|
181
|
-
end
|
182
|
-
end
|
183
|
-
if failure
|
184
|
-
out << OrderedHash do |o|
|
185
|
-
o['name'] = failure.name
|
186
|
-
o['status'] = failure.status.to_s
|
187
|
-
o['expected'] = failure.expectation
|
188
|
-
o['actual'] = render_value failure.value
|
189
|
-
end
|
190
|
-
end
|
191
|
-
out
|
192
|
-
end
|
193
|
-
|
194
|
-
def render_error(error)
|
195
|
-
OrderedHash do |o|
|
196
|
-
o['class'] = error.class.name
|
197
|
-
o['message'] = error.message
|
198
|
-
o['backtrace'] = error.backtrace
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
end
|
203
|
-
|
204
|
-
attr_accessor :_checks
|
205
|
-
|
206
|
-
def initialize
|
207
|
-
@_checks = []
|
208
|
-
end
|
209
|
-
|
210
|
-
def Check(value)
|
211
|
-
file, line_number = caller.first.match(/^(.+):(\d+)/).captures
|
212
|
-
line = File.readlines(file)[line_number.to_i - 1].strip
|
213
|
-
name = line[/Check\((.+?)\)\s*($|#|\[|\.is.+)/,1]
|
214
|
-
check = Check.new(name, value)
|
215
|
-
_checks << check
|
216
|
-
check
|
217
|
-
end
|
218
|
-
|
219
|
-
def _status
|
220
|
-
(:info if _checks.empty?) ||
|
221
|
-
(:failure if _checks.any? { |c| c.failure? }) ||
|
222
|
-
(:success if _checks.all? { |c| c.success? }) ||
|
223
|
-
:infos
|
224
|
-
end
|
225
|
-
|
226
|
-
end
|
227
|
-
|
228
|
-
class Examples
|
229
|
-
|
230
|
-
def initialize
|
231
|
-
@examples = OrderedHash.new
|
232
|
-
end
|
233
|
-
|
234
|
-
def add(name, &body)
|
235
|
-
@examples[name] = body
|
236
|
-
end
|
237
|
-
|
238
|
-
def run(patterns)
|
239
|
-
fails = 0
|
240
|
-
# unoffically supports multiple patterns
|
241
|
-
patterns = Regexp.new(patterns.join('|'))
|
242
|
-
examples_to_run = @examples.select { |name,_| name =~ patterns }
|
243
|
-
return 0 if examples_to_run.empty?
|
244
|
-
examples_to_run.each do |name, code|
|
245
|
-
result = ResultPrinter.new(name, *ExampleEnv.run(&code))
|
246
|
-
fails +=1 if result.failure?
|
247
|
-
puts($stdout.tty? ? result.fancy : result.yaml)
|
248
|
-
end
|
249
|
-
(fails.to_f/examples_to_run.size)*100
|
250
|
-
end
|
251
|
-
|
252
|
-
def list(patterns)
|
253
|
-
patterns = Regexp.new(patterns.join('|'))
|
254
|
-
list = @examples.keys.select { |name| name =~ patterns }
|
255
|
-
print YAML.without_header(list)
|
256
|
-
end
|
257
|
-
|
258
|
-
end
|
259
|
-
|
260
|
-
class << self
|
261
|
-
|
262
|
-
def examples
|
263
|
-
@examples ||= Examples.new
|
264
|
-
end
|
265
|
-
|
266
|
-
def extract_example_file(caller_trace)
|
267
|
-
@example_file ||= caller_trace.first.split(":").first
|
268
|
-
end
|
269
|
-
|
270
|
-
# attr_reader :example_file
|
18
|
+
end
|
271
19
|
|
272
|
-
|
273
|
-
|
274
|
-
|
20
|
+
require Exemplor.path('/ext')
|
21
|
+
require Exemplor.path('/checker')
|
22
|
+
require Exemplor.path('/result_printer')
|
23
|
+
require Exemplor.path('/environment')
|
24
|
+
require Exemplor.path('/examples')
|
25
|
+
require Exemplor.path('/command')
|
26
|
+
|
27
|
+
# -- Public API
|
28
|
+
|
29
|
+
# Interface for defining examples and configuring their environment.
|
30
|
+
#
|
31
|
+
# To define an example call eg with a name and block
|
32
|
+
#
|
33
|
+
# eg "yellling" do
|
34
|
+
# "hi".capitalize
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# `eg` can be called without a name if the entire call is on one line, in
|
38
|
+
# that case the code in the example is used as its name:
|
39
|
+
#
|
40
|
+
# eg { the_duck_swims_in_the_pond }
|
41
|
+
# same as:
|
42
|
+
# eg("the_duck_swims_in_the_pond") { the_duck_swims_in_the_pond }
|
43
|
+
#
|
44
|
+
# Call `eg` without args to configure the examples enviornment:
|
45
|
+
#
|
46
|
+
# eg.setup do
|
47
|
+
# # code here will run before each example
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# eg.helpers do
|
51
|
+
# # methods defined here can be called from inside an example
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
def eg(name = nil, &example)
|
55
|
+
called_without_args = name.nil? && example.nil?
|
56
|
+
return Exemplor.environment if called_without_args
|
275
57
|
|
276
|
-
|
58
|
+
Exemplor.set_example_file_from caller unless Exemplor.example_file_set?
|
277
59
|
|
278
|
-
|
60
|
+
called_without_explicit_name = name.nil? && !example.nil?
|
61
|
+
name = Exemplor.make_example_name_from caller if called_without_explicit_name
|
279
62
|
|
280
|
-
# Defines an example. After definition, an example is an entry in the
|
281
|
-
# Examples.examples ordered hash, the key is the name, the body is the example
|
282
|
-
# code
|
283
|
-
def eg(name = nil, &example)
|
284
|
-
return Exemplor::ExampleEnv if name.nil? && example.nil?
|
285
|
-
Exemplor.extract_example_file caller # only runs once
|
286
|
-
if name.nil?
|
287
|
-
file, line_number = caller.first.match(/^(.+):(\d+)/).captures
|
288
|
-
line = File.readlines(file)[line_number.to_i - 1].strip
|
289
|
-
name = line[/^eg\s*\{\s*(.+?)\s*\}$/,1] if name.nil?
|
290
|
-
raise Exemplor::ExampleDefinitionError, "example at #{caller.first} has no name so must be on one line" if name.nil?
|
291
|
-
end
|
292
63
|
Exemplor.examples.add(name, &example)
|
293
64
|
end
|
294
65
|
|
295
|
-
#
|
296
|
-
at_exit
|
297
|
-
if Exemplor.run_directly?
|
298
|
-
args = ARGV.dup
|
299
|
-
if args.delete('--list') || args.delete('-l')
|
300
|
-
Exemplor.examples.list(args)
|
301
|
-
else
|
302
|
-
exit Exemplor.examples.run(args)
|
303
|
-
end
|
304
|
-
end
|
305
|
-
end
|
66
|
+
# Command line interface
|
67
|
+
at_exit { Exemplor(ARGV) if Exemplor.run_directly? }
|
data/lib/ext.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
def OrderedHash(&blk)
|
2
|
+
ohsh = OrderedHash.new
|
3
|
+
blk.call(ohsh)
|
4
|
+
ohsh
|
5
|
+
end
|
6
|
+
|
7
|
+
def YAML.without_header(obj)
|
8
|
+
obj.to_yaml.match(/^--- \n?/).post_match
|
9
|
+
end
|
10
|
+
|
11
|
+
class String
|
12
|
+
def indent
|
13
|
+
self.split("\n").map { |line| ' ' + line }.join("\n")
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Exemplor
|
2
|
+
class ResultPrinter
|
3
|
+
|
4
|
+
attr_reader :name,:status,:result,:stderr
|
5
|
+
|
6
|
+
def initialize(name,status,result,stderr)
|
7
|
+
@name,@status,@result,@stderr = name,status,result,stderr
|
8
|
+
end
|
9
|
+
|
10
|
+
def failure?
|
11
|
+
[:error,:failure].include?(self.status)
|
12
|
+
end
|
13
|
+
|
14
|
+
def yaml
|
15
|
+
hsh = OrderedHash do |o|
|
16
|
+
o['name'] = self.name
|
17
|
+
o['status'] = case status = self.status
|
18
|
+
when :info : 'info (no checks)'
|
19
|
+
when :infos : 'info (with checks)'
|
20
|
+
else ; status.to_s
|
21
|
+
end
|
22
|
+
o['result'] = self.result
|
23
|
+
end
|
24
|
+
YAML.without_header([hsh])# prints an array
|
25
|
+
end
|
26
|
+
|
27
|
+
def fancy
|
28
|
+
# •∙ are inverted in my terminal font (Incosolata) so I'm swapping them
|
29
|
+
require 'term/ansicolor'
|
30
|
+
case status
|
31
|
+
when :info : blue format_info("• #{name}", result)
|
32
|
+
when :infos
|
33
|
+
formatted_result = result.map do |r|
|
34
|
+
# TODO: successful ones should be green
|
35
|
+
format_info("#{{'success' => '✓', 'info' => '•' }[r['status']]} #{r['name']}", r['result']).rstrip
|
36
|
+
end.join("\n")
|
37
|
+
blue("∙ #{name}\n#{formatted_result.indent}")
|
38
|
+
when :success
|
39
|
+
green("✓ #{name}")
|
40
|
+
when :failure
|
41
|
+
# sooo hacky
|
42
|
+
failure = result.find { |r| r['status'] == 'failure' }
|
43
|
+
out = failure.dup
|
44
|
+
out.delete('status')
|
45
|
+
out.delete('name')
|
46
|
+
color(:red, "✗ #{name} - #{failure['name']}\n#{YAML.without_header(out).indent}")
|
47
|
+
when :error
|
48
|
+
class_and_message = "#{result['class']} - #{result['message']}"
|
49
|
+
backtrace = result['backtrace'].join("\n")
|
50
|
+
color(:red, "☠ #{name}\n#{class_and_message.indent}\n#{backtrace.indent}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def blue(str) color(:blue,str) end
|
55
|
+
def green(str) color(:green,str) end
|
56
|
+
|
57
|
+
def color(color, str)
|
58
|
+
[Term::ANSIColor.send(color), str, Term::ANSIColor.reset].join
|
59
|
+
end
|
60
|
+
|
61
|
+
# whatahack
|
62
|
+
def format_info(str, result)
|
63
|
+
YAML.without_header({'FANCY' => result}).sub('FANCY', str)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 2010
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 2010.0.2
|
9
|
+
version: 2010.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Myles Byrne
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-03-
|
17
|
+
date: 2010-03-31 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -77,7 +77,13 @@ files:
|
|
77
77
|
- examples/ten_percent_failures.rb
|
78
78
|
- examples/with_checks.rb
|
79
79
|
- examples/with_setup.rb
|
80
|
+
- lib/checker.rb
|
81
|
+
- lib/command.rb
|
82
|
+
- lib/environment.rb
|
83
|
+
- lib/examples.rb
|
80
84
|
- lib/exemplor.rb
|
85
|
+
- lib/ext.rb
|
86
|
+
- lib/result_printer.rb
|
81
87
|
has_rdoc: true
|
82
88
|
homepage: http://github.com/quackingduck/exemplor
|
83
89
|
licenses: []
|