xspec 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -14
- data/bin/xspec +42 -1
- data/lib/xspec.rb +13 -9
- data/lib/xspec/data_structures.rb +7 -2
- data/lib/xspec/defaults.rb +17 -0
- data/lib/xspec/dsl.rb +1 -1
- data/lib/xspec/evaluators.rb +12 -20
- data/lib/xspec/notifiers.rb +59 -50
- data/lib/xspec/schedulers.rb +22 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/assertion_spec.rb +5 -5
- data/spec/unit/data_structures_spec.rb +19 -0
- data/spec/unit/doubles_spec.rb +8 -8
- data/spec/unit/notifiers_spec.rb +22 -13
- data/xspec.gemspec +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3831933eef297e842aeb3d87c7fd269f15e1377
|
4
|
+
data.tar.gz: 72891d3ec2b7a439cbedc809c98393591e673d46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3825fe02479cf05849b66351a846adb91d0af0f895b1b5a1e12e555626fca1724066d759466f8b41e7149691b5e290288098e50dff3877beb1f220549d55f9cc
|
7
|
+
data.tar.gz: e4e573a3aca9af04bfa5f9b4db9af146c29f56d1112c3ed410b78e5d5ab785db6c53abc59a74be333dc1fc92d29b1c2bfc20d147882ec194dfb9746fc6650b50
|
data/README.md
CHANGED
@@ -21,7 +21,7 @@ extend XSpec.dsl # Use defaults
|
|
21
21
|
describe 'my application' do
|
22
22
|
it 'does math' do
|
23
23
|
double = instance_double('Calculator')
|
24
|
-
|
24
|
+
stub(double).add(1, 1) { 2 }
|
25
25
|
|
26
26
|
assert_equal 2, double.add(1, 1)
|
27
27
|
end
|
@@ -43,9 +43,9 @@ see the colors in this README, but trust me they are quite lovely.
|
|
43
43
|
> xspec example.rb
|
44
44
|
|
45
45
|
my application
|
46
|
-
0.000s does math
|
47
|
-
0.011s is slow sometimes
|
48
|
-
0.000s fails - FAILED
|
46
|
+
0.000s 3l1 does math
|
47
|
+
0.011s f0j is slow sometimes
|
48
|
+
0.000s juj fails - FAILED
|
49
49
|
|
50
50
|
Timings:
|
51
51
|
0.001 #################### 2
|
@@ -54,11 +54,25 @@ my application
|
|
54
54
|
0.1 ########## 1
|
55
55
|
|
56
56
|
|
57
|
-
my application fails
|
57
|
+
juj - my application fails
|
58
58
|
"fruit" not present in: "punch"
|
59
59
|
|
60
|
-
|
61
|
-
bin/xspec:
|
60
|
+
test.rb:17:in `block (2 levels) in <top (required)>'
|
61
|
+
bin/xspec:44:in `<main>'
|
62
|
+
```
|
63
|
+
|
64
|
+
The three-character tag next to each test is its short id. You can use it to
|
65
|
+
run a single test:
|
66
|
+
|
67
|
+
```
|
68
|
+
> xspec -f 3l1
|
69
|
+
|
70
|
+
my application
|
71
|
+
0.000s 3l1 does math
|
72
|
+
|
73
|
+
Timings:
|
74
|
+
0.001 #################### 1
|
75
|
+
|
62
76
|
```
|
63
77
|
|
64
78
|
### Customization
|
@@ -73,11 +87,11 @@ expectations. You could do that:
|
|
73
87
|
require 'xspec'
|
74
88
|
|
75
89
|
extend XSpec.dsl(
|
76
|
-
|
77
|
-
include XSpec::
|
90
|
+
evaluator_context: XSpec::Evaluator.stack {
|
91
|
+
include XSpec::Evaluator::RSpecExpectations
|
78
92
|
},
|
79
|
-
notifier: XSpec::
|
80
|
-
XSpec::
|
93
|
+
notifier: XSpec::Notifier::Character.new +
|
94
|
+
XSpec::Notifier::FailuresAtEnd.new
|
81
95
|
)
|
82
96
|
|
83
97
|
describe '...' do
|
@@ -86,15 +100,15 @@ end
|
|
86
100
|
```
|
87
101
|
|
88
102
|
Of course, you can make your own extension classes as well. For details, see
|
89
|
-
the
|
103
|
+
the API documentation.
|
90
104
|
|
91
105
|
Documentation
|
92
106
|
-------------
|
93
107
|
|
94
108
|
There are two major sources of documentation:
|
95
109
|
|
96
|
-
* [Main API documentation.](https://xaviershay.github.io/xspec/api.html)
|
97
|
-
* [Literate source code.](https://xaviershay.github.io/xspec/)
|
110
|
+
* [Main API documentation.](https://xaviershay.github.io/xspec/docs/api.html)
|
111
|
+
* [Literate source code.](https://xaviershay.github.io/xspec/docs/xspec.html)
|
98
112
|
|
99
113
|
It is expected that regular users of XSpec will read both at least once. There
|
100
114
|
isn't much to them, and they will give you a useful mental model of how XSpec
|
data/bin/xspec
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'optparse'
|
4
|
+
|
3
5
|
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
4
6
|
|
5
7
|
require 'xspec'
|
@@ -7,6 +9,27 @@ require 'xspec'
|
|
7
9
|
$LOAD_PATH.unshift "spec"
|
8
10
|
$LOAD_PATH.unshift "lib"
|
9
11
|
|
12
|
+
filter = nil
|
13
|
+
short_ids = []
|
14
|
+
|
15
|
+
parser = OptionParser.new
|
16
|
+
parser.banner = "Usage: xspec [options] [files]"
|
17
|
+
parser.separator ""
|
18
|
+
parser.on('-e FILTER', "Run specs with full name including FILTER.") do |f|
|
19
|
+
filter = Regexp.new(f)
|
20
|
+
end
|
21
|
+
parser.on('-f ID', "Run spec with short id ID. Use multiple times to specify more than one id.") do |f|
|
22
|
+
short_ids << f
|
23
|
+
end
|
24
|
+
parser.on("-h", "--help", "Show this message.") do
|
25
|
+
$stderr.puts parser
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
parser.separator ""
|
29
|
+
|
30
|
+
parser.parse!
|
31
|
+
|
32
|
+
|
10
33
|
files = if ARGV.any? {|x| x.length > 0 }
|
11
34
|
ARGV
|
12
35
|
else
|
@@ -18,7 +41,25 @@ files.each do |f|
|
|
18
41
|
end
|
19
42
|
|
20
43
|
if respond_to?(:run!)
|
21
|
-
|
44
|
+
result = run! {|config|
|
45
|
+
config.update(scheduler: XSpec::Scheduler::Filter.new(
|
46
|
+
scheduler: config.fetch(:scheduler),
|
47
|
+
filter: -> uow {
|
48
|
+
inc = true
|
49
|
+
|
50
|
+
if short_ids.any?
|
51
|
+
inc &&= short_ids.include?(config.fetch(:short_id).(uow))
|
52
|
+
end
|
53
|
+
|
54
|
+
if filter
|
55
|
+
inc &&= uow.full_name =~ filter
|
56
|
+
end
|
57
|
+
|
58
|
+
inc
|
59
|
+
}
|
60
|
+
))
|
61
|
+
}
|
62
|
+
exit 1 unless result
|
22
63
|
else
|
23
64
|
$stderr.puts "This script can only be used when XSpec.dsl is mixed in to " +
|
24
65
|
"global scope."
|
data/lib/xspec.rb
CHANGED
@@ -12,8 +12,8 @@ module XSpec
|
|
12
12
|
# This enables different options to be specified per DSL, which is at the
|
13
13
|
# heart of XSpec's modularity. It is easy to change every component to your
|
14
14
|
# liking.
|
15
|
-
def dsl(
|
16
|
-
|
15
|
+
def dsl(config = {})
|
16
|
+
config = XSpec.add_defaults(config)
|
17
17
|
|
18
18
|
Module.new do
|
19
19
|
# Each DSL provides a standard set of methods provided by the [DSL
|
@@ -24,22 +24,26 @@ module XSpec
|
|
24
24
|
# described in detail in the
|
25
25
|
# [`data_structures.rb`](data_structures.html).
|
26
26
|
def __xspec_context
|
27
|
-
evaluator =
|
27
|
+
evaluator = __xspec_config.fetch(:evaluator)
|
28
28
|
@__xspec_context ||= XSpec::Context.root(evaluator)
|
29
29
|
end
|
30
30
|
|
31
|
-
# Some meta-magic is needed to enable the
|
31
|
+
# Some meta-magic is needed to enable the config from local scope above
|
32
32
|
# to be available inside the module.
|
33
|
-
define_method(:
|
33
|
+
define_method(:__xspec_config) { config }
|
34
34
|
|
35
35
|
# `run!` is where the magic happens. Typically called at the end of a
|
36
36
|
# file (or by `autorun!`), this method takes all the data that was
|
37
37
|
# accumulated by the DSL methods above and runs it through the scheduler.
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
#
|
39
|
+
# It takes an optional block that can be used to override any options
|
40
|
+
# set in the initial `XSpec.dsl` call.
|
41
|
+
def run!(&overrides)
|
42
|
+
overrides ||= -> x { x }
|
43
|
+
config = overrides.(__xspec_config)
|
44
|
+
scheduler = config.fetch(:scheduler)
|
41
45
|
|
42
|
-
scheduler.run(__xspec_context,
|
46
|
+
scheduler.run(__xspec_context, config)
|
43
47
|
end
|
44
48
|
|
45
49
|
# It is often convenient to trigger a run after all files have been
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# XSpec data structures are very dumb. They:
|
4
4
|
#
|
5
|
-
# * Only contain iteration and creation logic.
|
5
|
+
# * Only contain iteration, property, and creation logic.
|
6
6
|
# * Do not store recursive references ("everything flows downhill").
|
7
7
|
module XSpec
|
8
8
|
# A unit of work, usually created by the `it` DSL method, is a labeled,
|
@@ -85,7 +85,7 @@ module XSpec
|
|
85
85
|
# A shared context is a floating context that isn't part of any context
|
86
86
|
# heirachy, so its units of work will not be visible to the root node. It
|
87
87
|
# can be brought into any point in the heirachy using `copy_into_tree`
|
88
|
-
# (aliased as `
|
88
|
+
# (aliased as `include_context` in the DSL), and this can be done
|
89
89
|
# multiple times, which allows definitions to be reused.
|
90
90
|
#
|
91
91
|
# This is leaky abstraction, since only units of work are copied from
|
@@ -156,6 +156,10 @@ module XSpec
|
|
156
156
|
def block; unit_of_work.block; end
|
157
157
|
def name; unit_of_work.name; end
|
158
158
|
|
159
|
+
def full_name
|
160
|
+
(parents + [self]).map(&:name).compact.join(' ')
|
161
|
+
end
|
162
|
+
|
159
163
|
def immediate_parent
|
160
164
|
parents.last
|
161
165
|
end
|
@@ -170,6 +174,7 @@ module XSpec
|
|
170
174
|
ExecutedUnitOfWork = Struct.new(:nested_unit_of_work, :errors, :duration) do
|
171
175
|
def name; nested_unit_of_work.name end
|
172
176
|
def parents; nested_unit_of_work.parents end
|
177
|
+
def full_name; nested_unit_of_work.full_name end
|
173
178
|
end
|
174
179
|
|
175
180
|
# A test failure will be reported as a `Failure`, which includes contextual
|
data/lib/xspec/defaults.rb
CHANGED
@@ -7,7 +7,21 @@ require 'xspec/schedulers'
|
|
7
7
|
require 'xspec/evaluators'
|
8
8
|
require 'xspec/notifiers'
|
9
9
|
|
10
|
+
require 'digest/sha1'
|
11
|
+
|
10
12
|
module XSpec
|
13
|
+
def default_short_id(uow)
|
14
|
+
length = 3
|
15
|
+
base = 32
|
16
|
+
digest = Digest::SHA1.hexdigest(uow.full_name).hex
|
17
|
+
bottom = base ** (length-1)
|
18
|
+
top = base ** length
|
19
|
+
shifted = digest % (top - bottom) + bottom
|
20
|
+
|
21
|
+
shifted.to_s(base)
|
22
|
+
end
|
23
|
+
module_function :default_short_id
|
24
|
+
|
11
25
|
def add_defaults(options = {})
|
12
26
|
# A notifier makes it possible to observe the state of the system, be that
|
13
27
|
# progress or details of failing tests.
|
@@ -20,6 +34,9 @@ module XSpec
|
|
20
34
|
# used.
|
21
35
|
options[:evaluator] ||= Evaluator::DEFAULT
|
22
36
|
|
37
|
+
options[:short_id] ||= XSpec.method(:default_short_id)
|
38
|
+
|
39
|
+
|
23
40
|
# An scheduler is responsible for scheduling units of work and handing them
|
24
41
|
# off to the assertion context. Any logic regarding threads, remote
|
25
42
|
# execution or the like belongs in a scheduler.
|
data/lib/xspec/dsl.rb
CHANGED
data/lib/xspec/evaluators.rb
CHANGED
@@ -33,28 +33,24 @@ module XSpec
|
|
33
33
|
module Top
|
34
34
|
def call(unit_of_work)
|
35
35
|
super
|
36
|
+
rescue EvaluateFailed => e
|
37
|
+
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
36
38
|
rescue => e
|
37
39
|
[CodeException.new(unit_of_work, e.message, e.backtrace)]
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
43
|
+
# As long as the `Top` evaluator is used, evaluators can raise
|
44
|
+
# `EvaluateFailed` to indicate a failure separate from a normal code
|
45
|
+
# exception.
|
46
|
+
EvaluateFailed = Class.new(RuntimeError)
|
47
|
+
|
41
48
|
# ### Simple Assertions
|
42
49
|
#
|
43
50
|
# This simple evaluator provides very straight-forward assertion methods.
|
44
51
|
module Simple
|
45
|
-
class AssertionFailed < RuntimeError
|
46
|
-
attr_reader :message, :backtrace
|
47
|
-
|
48
|
-
def initialize(message, backtrace)
|
49
|
-
@message = message
|
50
|
-
@backtrace = backtrace
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
52
|
def call(unit_of_work)
|
55
53
|
super
|
56
|
-
rescue AssertionFailed => e
|
57
|
-
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
58
54
|
end
|
59
55
|
|
60
56
|
def assert(proposition, message=nil)
|
@@ -90,7 +86,7 @@ EOS
|
|
90
86
|
private
|
91
87
|
|
92
88
|
def _raise(message)
|
93
|
-
raise
|
89
|
+
raise EvaluateFailed, message
|
94
90
|
end
|
95
91
|
end
|
96
92
|
|
@@ -99,12 +95,8 @@ EOS
|
|
99
95
|
# The doubles module provides test doubles that can be used in-place of
|
100
96
|
# real objects.
|
101
97
|
module Doubles
|
102
|
-
DoubleFailure = Class.new(RuntimeError)
|
103
|
-
|
104
98
|
def call(unit_of_work)
|
105
99
|
super
|
106
|
-
rescue DoubleFailure => e
|
107
|
-
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
108
100
|
end
|
109
101
|
|
110
102
|
# It can be configured with the following options:
|
@@ -227,7 +219,7 @@ EOS
|
|
227
219
|
@received.delete_at(i)
|
228
220
|
else
|
229
221
|
name, rest = *args
|
230
|
-
::Kernel.raise
|
222
|
+
::Kernel.raise EvaluateFailed,
|
231
223
|
"Did not receive: %s(%s)\nDid receive:%s\n" % [
|
232
224
|
name,
|
233
225
|
[*rest].map(&:inspect).join(", "),
|
@@ -269,7 +261,7 @@ EOS
|
|
269
261
|
name, rest = *args
|
270
262
|
|
271
263
|
unless @klass.respond_to?(name)
|
272
|
-
raise
|
264
|
+
raise EvaluateFailed,
|
273
265
|
"#{@klass}.#{name} is unimplemented or not public"
|
274
266
|
end
|
275
267
|
end
|
@@ -280,7 +272,7 @@ EOS
|
|
280
272
|
name, rest = *args
|
281
273
|
|
282
274
|
unless @klass.public_instance_methods.include?(name)
|
283
|
-
raise
|
275
|
+
raise EvaluateFailed,
|
284
276
|
"#{@klass}##{name} is unimplemented or not public"
|
285
277
|
end
|
286
278
|
end
|
@@ -293,7 +285,7 @@ EOS
|
|
293
285
|
ref = if self.class.const_defined?(klass)
|
294
286
|
type.new(self.class.const_get(klass))
|
295
287
|
else
|
296
|
-
raise
|
288
|
+
raise EvaluateFailed, "#{klass} is not a valid class name"
|
297
289
|
end
|
298
290
|
|
299
291
|
super
|
data/lib/xspec/notifiers.rb
CHANGED
@@ -5,6 +5,17 @@
|
|
5
5
|
# summarizing the run when it finished.
|
6
6
|
module XSpec
|
7
7
|
module Notifier
|
8
|
+
# A formatter must implement at least four methods. `run_start` and
|
9
|
+
# `run_finish` are called at the beginning and end of the full spec run
|
10
|
+
# respectively, while `evaluate_start` and `evaluate_finish` are called for
|
11
|
+
# each test. See [API docs](api.html#notifiers) for more information.
|
12
|
+
module EmptyFormatter
|
13
|
+
def run_start(*_); end
|
14
|
+
def evaluate_start(*_); end
|
15
|
+
def evaluate_finish(*_); end
|
16
|
+
def run_finish(*_); true; end
|
17
|
+
end
|
18
|
+
|
8
19
|
# Many notifiers play nice with others, and can be composed together in a
|
9
20
|
# way that each notifier will have its callback run in turn.
|
10
21
|
module Composable
|
@@ -20,8 +31,8 @@ module XSpec
|
|
20
31
|
@notifiers = notifiers
|
21
32
|
end
|
22
33
|
|
23
|
-
def run_start
|
24
|
-
notifiers.each(
|
34
|
+
def run_start(*args)
|
35
|
+
notifiers.each {|x| x.run_start(*args) }
|
25
36
|
end
|
26
37
|
|
27
38
|
def evaluate_start(*args)
|
@@ -44,16 +55,13 @@ module XSpec
|
|
44
55
|
# Outputs a single character for each executed unit of work representing
|
45
56
|
# the result.
|
46
57
|
class Character
|
58
|
+
include EmptyFormatter
|
47
59
|
include Composable
|
48
60
|
|
49
61
|
def initialize(out = $stdout)
|
50
62
|
@out = out
|
51
63
|
end
|
52
64
|
|
53
|
-
def run_start; end
|
54
|
-
|
55
|
-
def evaluate_start(*_); end
|
56
|
-
|
57
65
|
def evaluate_finish(result)
|
58
66
|
@out.print label_for_failure(result.errors[0])
|
59
67
|
@failed ||= result.errors.any?
|
@@ -73,11 +81,11 @@ module XSpec
|
|
73
81
|
else '.'
|
74
82
|
end
|
75
83
|
end
|
76
|
-
|
77
84
|
end
|
78
85
|
|
79
86
|
# Renders a histogram of test durations after the entire run is complete.
|
80
87
|
class TimingsAtEnd
|
88
|
+
include EmptyFormatter
|
81
89
|
include Composable
|
82
90
|
|
83
91
|
DEFAULT_SPLITS = [0.001, 0.005, 0.01, 0.1, 1.0, Float::INFINITY]
|
@@ -92,11 +100,6 @@ module XSpec
|
|
92
100
|
@out = out
|
93
101
|
end
|
94
102
|
|
95
|
-
def run_start(*_); end
|
96
|
-
|
97
|
-
def evaluate_start(_)
|
98
|
-
end
|
99
|
-
|
100
103
|
def evaluate_finish(result)
|
101
104
|
timings[result] = result.duration
|
102
105
|
end
|
@@ -148,19 +151,29 @@ module XSpec
|
|
148
151
|
end
|
149
152
|
end
|
150
153
|
|
154
|
+
# Provides convenience methods for working with short ids.
|
155
|
+
module ShortIdSupport
|
156
|
+
def run_start(config)
|
157
|
+
super
|
158
|
+
@short_id = config.fetch(:short_id)
|
159
|
+
end
|
160
|
+
|
161
|
+
def short_id_for(x)
|
162
|
+
@short_id.(x)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
151
166
|
# Outputs error messages and backtraces after the entire run is complete.
|
152
167
|
class FailuresAtEnd
|
168
|
+
include EmptyFormatter
|
153
169
|
include Composable
|
170
|
+
include ShortIdSupport
|
154
171
|
|
155
172
|
def initialize(out = $stdout)
|
156
173
|
@errors = []
|
157
174
|
@out = out
|
158
175
|
end
|
159
176
|
|
160
|
-
def run_start; end
|
161
|
-
|
162
|
-
def evaluate_start(*_); end
|
163
|
-
|
164
177
|
def evaluate_finish(result)
|
165
178
|
self.errors += result.errors
|
166
179
|
end
|
@@ -170,7 +183,11 @@ module XSpec
|
|
170
183
|
|
171
184
|
out.puts
|
172
185
|
errors.each do |error|
|
173
|
-
out.puts "%s
|
186
|
+
out.puts "%s - %s\n%s\n\n" % [
|
187
|
+
short_id_for(error.unit_of_work),
|
188
|
+
error.unit_of_work.full_name,
|
189
|
+
error.message.lines.map {|x| " #{x}"}.join("")
|
190
|
+
]
|
174
191
|
clean_backtrace(error.caller).each do |line|
|
175
192
|
out.puts " %s" % line
|
176
193
|
end
|
@@ -182,10 +199,6 @@ module XSpec
|
|
182
199
|
|
183
200
|
private
|
184
201
|
|
185
|
-
def full_name(unit_of_work)
|
186
|
-
(unit_of_work.parents + [unit_of_work]).map(&:name).compact.join(' ')
|
187
|
-
end
|
188
|
-
|
189
202
|
# A standard backtrace contains many entries for XSpec itself which are
|
190
203
|
# not useful for debugging your tests, so they are stripped out.
|
191
204
|
def clean_backtrace(backtrace)
|
@@ -204,9 +217,9 @@ module XSpec
|
|
204
217
|
# Includes nicely formatted names and durations of each test in the output,
|
205
218
|
# with color.
|
206
219
|
class ColoredDocumentation
|
207
|
-
|
208
|
-
|
220
|
+
include EmptyFormatter
|
209
221
|
include Composable
|
222
|
+
include ShortIdSupport
|
210
223
|
|
211
224
|
VT100_COLORS = {
|
212
225
|
:black => 30,
|
@@ -219,24 +232,6 @@ module XSpec
|
|
219
232
|
:white => 37
|
220
233
|
}
|
221
234
|
|
222
|
-
def color_code_for(color)
|
223
|
-
VT100_COLORS.fetch(color)
|
224
|
-
end
|
225
|
-
|
226
|
-
def colorize(text, color)
|
227
|
-
"\e[#{color_code_for(color)}m#{text}\e[0m"
|
228
|
-
end
|
229
|
-
|
230
|
-
def decorate(result)
|
231
|
-
name = result.name
|
232
|
-
out = if result.errors.any?
|
233
|
-
colorize(append_failed(name), :red)
|
234
|
-
else
|
235
|
-
colorize(name , :green)
|
236
|
-
end
|
237
|
-
"%.3fs " % result.duration + out
|
238
|
-
end
|
239
|
-
|
240
235
|
def initialize(out = $stdout)
|
241
236
|
self.indent = 2
|
242
237
|
self.last_seen_names = []
|
@@ -244,10 +239,6 @@ module XSpec
|
|
244
239
|
self.out = out
|
245
240
|
end
|
246
241
|
|
247
|
-
def run_start; end
|
248
|
-
|
249
|
-
def evaluate_start(*_); end
|
250
|
-
|
251
242
|
def evaluate_finish(result)
|
252
243
|
output_context_header! result.parents.map(&:name).compact
|
253
244
|
|
@@ -267,6 +258,28 @@ module XSpec
|
|
267
258
|
|
268
259
|
attr_accessor :last_seen_names, :indent, :failed, :out
|
269
260
|
|
261
|
+
def color_code_for(color)
|
262
|
+
VT100_COLORS.fetch(color)
|
263
|
+
end
|
264
|
+
|
265
|
+
def colorize(text, color)
|
266
|
+
"\e[#{color_code_for(color)}m#{text}\e[0m"
|
267
|
+
end
|
268
|
+
|
269
|
+
def decorate(result)
|
270
|
+
name = result.name
|
271
|
+
out = if result.errors.any?
|
272
|
+
colorize(append_failed(name), :red)
|
273
|
+
else
|
274
|
+
colorize(name , :green)
|
275
|
+
end
|
276
|
+
"%.3fs %s %s" % [
|
277
|
+
result.duration,
|
278
|
+
short_id_for(result),
|
279
|
+
out,
|
280
|
+
]
|
281
|
+
end
|
282
|
+
|
270
283
|
def output_context_header!(parent_names)
|
271
284
|
if parent_names != last_seen_names
|
272
285
|
tail = parent_names - last_seen_names
|
@@ -299,11 +312,7 @@ module XSpec
|
|
299
312
|
# Useful as a parent class for other notifiers or for testing.
|
300
313
|
class Null
|
301
314
|
include Composable
|
302
|
-
|
303
|
-
def run_start; end
|
304
|
-
def evaluate_start(*_); end
|
305
|
-
def evaluate_finish(*_); end
|
306
|
-
def run_finish; true; end
|
315
|
+
include EmptyFormatter
|
307
316
|
end
|
308
317
|
|
309
318
|
DEFAULT =
|
data/lib/xspec/schedulers.rb
CHANGED
@@ -12,8 +12,9 @@ module XSpec
|
|
12
12
|
@clock = opts.fetch(:clock, ->{ Time.now.to_f })
|
13
13
|
end
|
14
14
|
|
15
|
-
def run(context,
|
16
|
-
notifier.
|
15
|
+
def run(context, config)
|
16
|
+
notifier = config.fetch(:notifier)
|
17
|
+
notifier.run_start(config)
|
17
18
|
|
18
19
|
context.nested_units_of_work.each do |x|
|
19
20
|
notifier.evaluate_start(x)
|
@@ -34,6 +35,25 @@ module XSpec
|
|
34
35
|
attr_reader :clock
|
35
36
|
end
|
36
37
|
|
38
|
+
class Filter
|
39
|
+
def initialize(scheduler:, filter:)
|
40
|
+
@scheduler = scheduler
|
41
|
+
@filter = filter
|
42
|
+
end
|
43
|
+
|
44
|
+
def run(context, config)
|
45
|
+
scheduler.run(FilteredContext.new(context, filter), config)
|
46
|
+
end
|
47
|
+
|
48
|
+
FilteredContext = Struct.new(:context, :filter) do
|
49
|
+
def nested_units_of_work
|
50
|
+
context.nested_units_of_work.select(&filter)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :scheduler, :filter
|
55
|
+
end
|
56
|
+
|
37
57
|
DEFAULT = Serial.new
|
38
58
|
end
|
39
59
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -12,7 +12,7 @@ extend XSpec.dsl(
|
|
12
12
|
def assert_errors_from_run(context, expected_error_messages)
|
13
13
|
context.run!
|
14
14
|
|
15
|
-
notifier = context.
|
15
|
+
notifier = context.__xspec_config.fetch(:notifier)
|
16
16
|
assert_equal expected_error_messages, notifier.errors.flatten.map(&:message)
|
17
17
|
end
|
18
18
|
|
data/spec/unit/assertion_spec.rb
CHANGED
@@ -14,7 +14,7 @@ describe 'simple assertion context' do
|
|
14
14
|
begin
|
15
15
|
subject.assert(false)
|
16
16
|
fail "Assertion did not fail"
|
17
|
-
rescue XSpec::Evaluator::
|
17
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
18
18
|
assert_equal "assertion failed", e.message
|
19
19
|
end
|
20
20
|
end
|
@@ -23,7 +23,7 @@ describe 'simple assertion context' do
|
|
23
23
|
begin
|
24
24
|
subject.assert(false, "nope")
|
25
25
|
fail "Assertion did not fail"
|
26
|
-
rescue XSpec::Evaluator::
|
26
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
27
27
|
assert_equal "nope", e.message
|
28
28
|
end
|
29
29
|
end
|
@@ -38,7 +38,7 @@ describe 'simple assertion context' do
|
|
38
38
|
begin
|
39
39
|
subject.assert_equal("a", "b")
|
40
40
|
fail "Assertion did not fail"
|
41
|
-
rescue XSpec::Evaluator::
|
41
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
42
42
|
assert_include 'want: "a"', e.message
|
43
43
|
assert_include 'got: "b"', e.message
|
44
44
|
end
|
@@ -50,7 +50,7 @@ describe 'simple assertion context' do
|
|
50
50
|
begin
|
51
51
|
subject.fail
|
52
52
|
assert false, "fail did not fail"
|
53
|
-
rescue XSpec::Evaluator::
|
53
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
54
54
|
assert_equal "failed", e.message
|
55
55
|
end
|
56
56
|
end
|
@@ -59,7 +59,7 @@ describe 'simple assertion context' do
|
|
59
59
|
begin
|
60
60
|
subject.fail ":("
|
61
61
|
assert false, "fail did not fail"
|
62
|
-
rescue XSpec::Evaluator::
|
62
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
63
63
|
assert_equal ":(", e.message
|
64
64
|
end
|
65
65
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'short ids' do
|
4
|
+
def short_id_for(name)
|
5
|
+
XSpec.default_short_id \
|
6
|
+
XSpec::NestedUnitOfWork.new([], XSpec::UnitOfWork.new(name))
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'is the same for same names' do
|
10
|
+
assert_equal short_id_for("abc"), short_id_for("abc")
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'is always the same length' do
|
14
|
+
100.times do
|
15
|
+
name = rand.to_s
|
16
|
+
assert 3 == short_id_for(name).length, "#{name} had bad short id"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/unit/doubles_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe 'doubles assertion context' do
|
|
12
12
|
|
13
13
|
it 'converts double exceptions to failures' do
|
14
14
|
result = subject.call(XSpec::UnitOfWork.new(nil, ->{
|
15
|
-
raise XSpec::Evaluator::
|
15
|
+
raise XSpec::Evaluator::EvaluateFailed, "nope"
|
16
16
|
}))
|
17
17
|
assert_equal "nope", result[0].message
|
18
18
|
end
|
@@ -81,7 +81,7 @@ describe 'doubles assertion context' do
|
|
81
81
|
verify(double).foo("b")
|
82
82
|
}
|
83
83
|
fail "no error raised"
|
84
|
-
rescue XSpec::Evaluator::
|
84
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
85
85
|
assert_include "Did not receive", e.message
|
86
86
|
assert_include 'foo("b")', e.message
|
87
87
|
end
|
@@ -96,7 +96,7 @@ describe 'doubles assertion context' do
|
|
96
96
|
verify(double).foo("c")
|
97
97
|
}
|
98
98
|
fail "no error raised"
|
99
|
-
rescue XSpec::Evaluator::
|
99
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
100
100
|
assert_include "Did not receive", e.message
|
101
101
|
assert_include 'foo("b")', e.message
|
102
102
|
assert_include "Did receive", e.message
|
@@ -122,7 +122,7 @@ describe 'doubles assertion context' do
|
|
122
122
|
stub(double).bogus_method { 123 }
|
123
123
|
}
|
124
124
|
fail "no error raised"
|
125
|
-
rescue XSpec::Evaluator::
|
125
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
126
126
|
assert_include "LoadedClass#bogus_method", e.message
|
127
127
|
end
|
128
128
|
end
|
@@ -134,7 +134,7 @@ describe 'doubles assertion context' do
|
|
134
134
|
verify(double).bogus_method { 123 }
|
135
135
|
}
|
136
136
|
fail "no error raised"
|
137
|
-
rescue XSpec::Evaluator::
|
137
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
138
138
|
assert_include "LoadedClass#bogus_method", e.message
|
139
139
|
end
|
140
140
|
end
|
@@ -157,7 +157,7 @@ describe 'doubles assertion context' do
|
|
157
157
|
stub(double).bogus_method { 123 }
|
158
158
|
}
|
159
159
|
fail "no error raised"
|
160
|
-
rescue XSpec::Evaluator::
|
160
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
161
161
|
assert_include "LoadedClass.bogus_method", e.message
|
162
162
|
end
|
163
163
|
end
|
@@ -169,7 +169,7 @@ describe 'doubles assertion context' do
|
|
169
169
|
verify(double).bogus_method
|
170
170
|
}
|
171
171
|
fail "no error raised"
|
172
|
-
rescue XSpec::Evaluator::
|
172
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
173
173
|
assert_include "LoadedClass.bogus_method", e.message
|
174
174
|
end
|
175
175
|
end
|
@@ -190,7 +190,7 @@ describe 'strict doubles assertion context' do
|
|
190
190
|
begin
|
191
191
|
subject.instance_double("Bogus")
|
192
192
|
fail "no error raised"
|
193
|
-
rescue XSpec::Evaluator::
|
193
|
+
rescue XSpec::Evaluator::EvaluateFailed => e
|
194
194
|
assert_include "Bogus", e.message
|
195
195
|
end
|
196
196
|
end
|
data/spec/unit/notifiers_spec.rb
CHANGED
@@ -10,6 +10,7 @@ end
|
|
10
10
|
describe 'failures at end notifier' do
|
11
11
|
let(:out) { StringIO.new }
|
12
12
|
let(:notifier) { XSpec::Notifier::FailuresAtEnd.new(out) }
|
13
|
+
let(:config) {{ short_id: -> _ { "XXX" }}}
|
13
14
|
|
14
15
|
it 'returns true when no errors are observed' do
|
15
16
|
assert notifier.run_finish
|
@@ -21,10 +22,11 @@ describe 'failures at end notifier' do
|
|
21
22
|
"failed",
|
22
23
|
[]
|
23
24
|
)
|
25
|
+
notifier.run_start(config)
|
24
26
|
notifier.evaluate_finish(make_executed_test errors: [failure])
|
25
27
|
|
26
28
|
assert !notifier.run_finish
|
27
|
-
assert_include "a b c
|
29
|
+
assert_include "a b c\n failed", out.string
|
28
30
|
end
|
29
31
|
|
30
32
|
it 'cleans lib entries out of backtrace' do
|
@@ -33,13 +35,14 @@ describe 'failures at end notifier' do
|
|
33
35
|
"failed",
|
34
36
|
[File.expand_path('../../../lib', __FILE__) + '/bogus.rb']
|
35
37
|
)
|
38
|
+
notifier.run_start(config)
|
36
39
|
notifier.evaluate_finish(make_executed_test errors: [failure])
|
37
40
|
|
38
41
|
assert !notifier.run_finish
|
39
42
|
assert !out.string.include?('bogus.rb')
|
40
43
|
end
|
41
44
|
|
42
|
-
|
45
|
+
include_context ComposableNotifier
|
43
46
|
end
|
44
47
|
|
45
48
|
describe 'character notifier' do
|
@@ -68,25 +71,27 @@ describe 'character notifier' do
|
|
68
71
|
assert out.string == "E\n"
|
69
72
|
end
|
70
73
|
|
71
|
-
|
74
|
+
include_context ComposableNotifier
|
72
75
|
end
|
73
76
|
|
74
77
|
describe 'documentation notifier' do
|
75
78
|
let(:notifier) { XSpec::Notifier::Documentation.new(out) }
|
76
79
|
let(:out) { StringIO.new }
|
80
|
+
let(:config) {{ short_id: -> uow { 'XXX' }}}
|
77
81
|
|
78
82
|
def evaluate_finish(args)
|
83
|
+
notifier.run_start(config)
|
79
84
|
notifier.evaluate_finish(make_executed_test args)
|
80
85
|
out.string
|
81
86
|
end
|
82
87
|
|
83
88
|
it 'outputs each context with a header and individual tests' do
|
84
|
-
assert_equal "\na\n 0.001s b\n",
|
89
|
+
assert_equal "\na\n 0.001s XXX b\n",
|
85
90
|
evaluate_finish(parents: ['a'], name: 'b')
|
86
91
|
end
|
87
92
|
|
88
93
|
it 'adds an indent for each nested context' do
|
89
|
-
assert_equal "\na\n b\n 0.001s c\n",
|
94
|
+
assert_equal "\na\n b\n 0.001s XXX c\n",
|
90
95
|
evaluate_finish(parents: ['a', 'b'], name: 'c')
|
91
96
|
end
|
92
97
|
|
@@ -94,11 +99,11 @@ describe 'documentation notifier' do
|
|
94
99
|
evaluate_finish(parents: ['a', 'b'], name: 'c')
|
95
100
|
evaluate_finish(parents: ['a', 'd'], name: 'e')
|
96
101
|
|
97
|
-
assert_equal "\na\n b\n 0.001s c\n\n d\n 0.001s e\n", out.string
|
102
|
+
assert_equal "\na\n b\n 0.001s XXX c\n\n d\n 0.001s XXX e\n", out.string
|
98
103
|
end
|
99
104
|
|
100
105
|
it 'ignores contexts with no name' do
|
101
|
-
assert_equal "\na\n 0.001s b\n",
|
106
|
+
assert_equal "\na\n 0.001s XXX b\n",
|
102
107
|
evaluate_finish(parents: [nil, 'a', nil], name: 'b')
|
103
108
|
end
|
104
109
|
|
@@ -116,32 +121,35 @@ describe 'documentation notifier' do
|
|
116
121
|
assert_include "FAILED", evaluate_finish(errors: [make_error])
|
117
122
|
end
|
118
123
|
|
119
|
-
|
124
|
+
include_context ComposableNotifier
|
120
125
|
end
|
121
126
|
|
122
127
|
describe 'colored documentation notifier' do
|
123
128
|
let(:notifier) { XSpec::Notifier::ColoredDocumentation.new(out) }
|
124
129
|
let(:out) { StringIO.new }
|
130
|
+
let(:config) {{ short_id: -> uow { 'XXX' }}}
|
125
131
|
|
126
132
|
it 'colors successful tests green' do
|
133
|
+
notifier.run_start(config)
|
127
134
|
notifier.evaluate_finish(make_executed_test errors: [])
|
128
135
|
|
129
136
|
assert_include "\e[32m\e[0m\n", out.string
|
130
137
|
end
|
131
138
|
|
132
139
|
it 'colors failed and errored tests red' do
|
140
|
+
notifier.run_start(config)
|
133
141
|
notifier.evaluate_finish(make_executed_test errors: [make_failure])
|
134
142
|
|
135
143
|
assert_include "\e[31mFAILED\e[0m", out.string
|
136
144
|
end
|
137
145
|
|
138
|
-
|
146
|
+
include_context ComposableNotifier
|
139
147
|
end
|
140
148
|
|
141
149
|
describe 'composable notifier' do
|
142
150
|
let(:notifier) { XSpec::Notifier::Composite.new }
|
143
151
|
|
144
|
-
|
152
|
+
include_context ComposableNotifier
|
145
153
|
end
|
146
154
|
|
147
155
|
describe 'null notifier' do
|
@@ -151,17 +159,18 @@ describe 'null notifier' do
|
|
151
159
|
assert notifier.run_finish
|
152
160
|
end
|
153
161
|
|
154
|
-
|
162
|
+
include_context ComposableNotifier
|
155
163
|
end
|
156
164
|
|
157
165
|
describe 'timings at end' do
|
158
|
-
let(:
|
166
|
+
let(:out) { StringIO.new }
|
167
|
+
let(:notifier) { XSpec::Notifier::TimingsAtEnd.new(out: out) }
|
159
168
|
|
160
169
|
it 'always returns true' do
|
161
170
|
assert notifier.run_finish
|
162
171
|
end
|
163
172
|
|
164
|
-
|
173
|
+
include_context ComposableNotifier
|
165
174
|
end
|
166
175
|
|
167
176
|
def make_nested_test(parent_names = [], work_name = nil)
|
data/xspec.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Shay
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: yeah
|
14
14
|
email:
|
@@ -32,6 +32,7 @@ files:
|
|
32
32
|
- spec/integration/rspec_expectations_spec.rb
|
33
33
|
- spec/spec_helper.rb
|
34
34
|
- spec/unit/assertion_spec.rb
|
35
|
+
- spec/unit/data_structures_spec.rb
|
35
36
|
- spec/unit/doubles_spec.rb
|
36
37
|
- spec/unit/let_spec.rb
|
37
38
|
- spec/unit/notifiers_spec.rb
|
@@ -66,6 +67,7 @@ test_files:
|
|
66
67
|
- spec/integration/rspec_expectations_spec.rb
|
67
68
|
- spec/spec_helper.rb
|
68
69
|
- spec/unit/assertion_spec.rb
|
70
|
+
- spec/unit/data_structures_spec.rb
|
69
71
|
- spec/unit/doubles_spec.rb
|
70
72
|
- spec/unit/let_spec.rb
|
71
73
|
- spec/unit/notifiers_spec.rb
|