xspec 0.1.0 → 0.2.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.
- 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
|