exemplor 2010.0.2 → 2010.1.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/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: []
|