sus 0.18.0 → 0.18.2
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
- checksums.yaml.gz.sig +0 -0
- data/bin/sus-host +14 -3
- data/lib/sus/assertions.rb +71 -11
- data/lib/sus/base.rb +1 -1
- data/lib/sus/expect.rb +9 -5
- data/lib/sus/file.rb +35 -6
- data/lib/sus/identity.rb +36 -0
- data/lib/sus/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7368b0ed0e288959f59f0fe6c2c5dd93ff90ba52f423b1365406e8de260536c1
|
4
|
+
data.tar.gz: 10fe5fec8209ce9cea739f580a3f4bd1b36be3b6e384f6ff85ad813fad32d0f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1456e34138bea3c5952ababab2950ac946ddce0a3f55879ed1bedb9828c536ffb5c4ced7b9a35f4e80ad1879e230dd26b0fc120b7258e218710530165d9233e5
|
7
|
+
data.tar.gz: b1ea6d027ec46f0aa72e74fd6cb169a71b5209a57791b52b7f41c1a1f2fc7d70353e08e73ebfef2d7262e67a7be10702500d07ee99f18499d898de5b0ad917ed
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/bin/sus-host
CHANGED
@@ -20,6 +20,16 @@ $stdin.reopen(File::NULL)
|
|
20
20
|
output = $stdout.dup
|
21
21
|
$stdout.reopen($stderr)
|
22
22
|
|
23
|
+
def failure_messages_for(assertions)
|
24
|
+
messages = []
|
25
|
+
|
26
|
+
assertions.each_failure do |failure|
|
27
|
+
messages << failure.message
|
28
|
+
end
|
29
|
+
|
30
|
+
return messages
|
31
|
+
end
|
32
|
+
|
23
33
|
while line = input.gets
|
24
34
|
message = JSON.parse(line)
|
25
35
|
|
@@ -55,14 +65,15 @@ while line = input.gets
|
|
55
65
|
|
56
66
|
assertions = Sus::Assertions.new(measure: true)
|
57
67
|
job.call(assertions)
|
58
|
-
|
59
68
|
results.push(assertions)
|
60
69
|
|
61
70
|
guard.synchronize do
|
62
71
|
if assertions.passed?
|
63
|
-
output.puts JSON.generate({passed: job.identity,
|
72
|
+
output.puts JSON.generate({passed: job.identity, duration: assertions.clock.ms})
|
73
|
+
elsif assertions.errored?
|
74
|
+
output.puts JSON.generate({errored: job.identity, messages: failure_messages_for(assertions), duration: assertions.clock.ms})
|
64
75
|
else
|
65
|
-
output.puts JSON.generate({failed: job.identity,
|
76
|
+
output.puts JSON.generate({failed: job.identity, messages: failure_messages_for(assertions), duration: assertions.clock.ms})
|
66
77
|
end
|
67
78
|
end
|
68
79
|
end
|
data/lib/sus/assertions.rb
CHANGED
@@ -41,6 +41,7 @@ module Sus
|
|
41
41
|
@count = 0
|
42
42
|
end
|
43
43
|
|
44
|
+
attr :identity
|
44
45
|
attr :target
|
45
46
|
attr :output
|
46
47
|
attr :level
|
@@ -75,6 +76,13 @@ module Sus
|
|
75
76
|
"\#<#{self.class} #{@passed.size} passed #{@failed.size} failed #{@deferred.size} deferred #{@skipped.size} skipped #{@errored.size} errored>"
|
76
77
|
end
|
77
78
|
|
79
|
+
def message
|
80
|
+
{
|
81
|
+
text: @output.string,
|
82
|
+
location: @identity&.to_location
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
78
86
|
def total
|
79
87
|
@passed.size + @failed.size + @deferred.size + @skipped.size + @errored.size
|
80
88
|
end
|
@@ -139,35 +147,64 @@ module Sus
|
|
139
147
|
end
|
140
148
|
|
141
149
|
class Assert
|
142
|
-
def initialize(
|
143
|
-
@
|
150
|
+
def initialize(identity, message)
|
151
|
+
@identity = identity
|
144
152
|
@message = message
|
145
153
|
end
|
146
154
|
|
147
|
-
attr :
|
155
|
+
attr :identity
|
148
156
|
attr :message
|
157
|
+
|
158
|
+
def each_failure(&block)
|
159
|
+
yield self
|
160
|
+
end
|
161
|
+
|
162
|
+
def message
|
163
|
+
{
|
164
|
+
text: @message,
|
165
|
+
location: @identity&.to_location
|
166
|
+
}
|
167
|
+
end
|
149
168
|
end
|
150
169
|
|
151
170
|
def assert(condition, message = nil)
|
152
171
|
@count += 1
|
172
|
+
|
173
|
+
message ||= self.output.string
|
153
174
|
backtrace = Output::Backtrace.first(@identity)
|
175
|
+
identity = @identity&.scoped
|
154
176
|
|
155
177
|
if condition
|
156
|
-
@passed << Assert.new(
|
178
|
+
@passed << Assert.new(identity, message)
|
157
179
|
|
158
180
|
if !@orientation || @verbose
|
159
|
-
@output.puts(:indent, *pass_prefix, message
|
181
|
+
@output.puts(:indent, *pass_prefix, message, backtrace)
|
160
182
|
end
|
161
183
|
else
|
162
|
-
|
163
|
-
@failed << Assert.new(message, backtrace)
|
184
|
+
@failed << Assert.new(identity, message)
|
164
185
|
|
165
186
|
if @orientation || @verbose
|
166
|
-
@output.puts(:indent, *fail_prefix, message
|
187
|
+
@output.puts(:indent, *fail_prefix, message, backtrace)
|
167
188
|
end
|
168
189
|
end
|
169
190
|
end
|
170
191
|
|
192
|
+
def each_failure(&block)
|
193
|
+
return to_enum(__method__) unless block_given?
|
194
|
+
|
195
|
+
# if self.failed? and @identity
|
196
|
+
# yield self
|
197
|
+
# end
|
198
|
+
|
199
|
+
@failed.each do |assertions|
|
200
|
+
assertions.each_failure(&block)
|
201
|
+
end
|
202
|
+
|
203
|
+
@errored.each do |assertions|
|
204
|
+
assertions.each_failure(&block)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
171
208
|
def skip(reason)
|
172
209
|
@output.puts(:indent, :skipped, skip_prefix, reason)
|
173
210
|
@skipped << self
|
@@ -196,8 +233,31 @@ module Sus
|
|
196
233
|
end
|
197
234
|
end
|
198
235
|
|
236
|
+
class Error
|
237
|
+
def initialize(identity, error)
|
238
|
+
@identity = identity
|
239
|
+
@error = error
|
240
|
+
end
|
241
|
+
|
242
|
+
attr :identity
|
243
|
+
attr :error
|
244
|
+
|
245
|
+
def each_failure(&block)
|
246
|
+
yield self
|
247
|
+
end
|
248
|
+
|
249
|
+
def message
|
250
|
+
{
|
251
|
+
text: @error.message,
|
252
|
+
location: @identity&.to_location
|
253
|
+
}
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
199
257
|
def error!(error)
|
200
|
-
|
258
|
+
identity = @identity.scoped(error.backtrace_locations)
|
259
|
+
|
260
|
+
@errored << Error.new(identity, error)
|
201
261
|
|
202
262
|
lines = error.message.split(/\r?\n/)
|
203
263
|
|
@@ -210,11 +270,11 @@ module Sus
|
|
210
270
|
@output.write(Output::Backtrace.for(error, @identity))
|
211
271
|
end
|
212
272
|
|
213
|
-
def nested(target, identity: @identity, isolated: false, inverted: false, **options)
|
273
|
+
def nested(target, identity: @identity, isolated: false, buffered: false, inverted: false, **options)
|
214
274
|
result = nil
|
215
275
|
|
216
276
|
# Isolated assertions need to have buffered output so they can be replayed if they fail:
|
217
|
-
if isolated
|
277
|
+
if isolated or buffered
|
218
278
|
output = @output.buffered
|
219
279
|
else
|
220
280
|
output = @output
|
data/lib/sus/base.rb
CHANGED
data/lib/sus/expect.rb
CHANGED
@@ -5,10 +5,11 @@
|
|
5
5
|
|
6
6
|
module Sus
|
7
7
|
class Expect
|
8
|
-
def initialize(assertions, subject, inverted: false)
|
8
|
+
def initialize(assertions, subject, inverted: false, buffered: false)
|
9
9
|
@assertions = assertions
|
10
10
|
@subject = subject
|
11
11
|
@inverted = inverted
|
12
|
+
@buffered = buffered
|
12
13
|
end
|
13
14
|
|
14
15
|
attr :subject
|
@@ -32,13 +33,16 @@ module Sus
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def to(predicate)
|
35
|
-
|
36
|
+
# This gets the identity scoped to the current call stack, which ensures that any failures are logged at this point in the code.
|
37
|
+
identity = @assertions.identity&.scoped
|
38
|
+
|
39
|
+
@assertions.nested(self, inverted: @inverted, buffered: @buffered, identity: identity) do |assertions|
|
36
40
|
predicate.call(assertions, @subject)
|
37
41
|
end
|
38
42
|
|
39
43
|
return self
|
40
44
|
end
|
41
|
-
|
45
|
+
|
42
46
|
def and(predicate)
|
43
47
|
return to(predicate)
|
44
48
|
end
|
@@ -47,9 +51,9 @@ module Sus
|
|
47
51
|
class Base
|
48
52
|
def expect(subject = nil, &block)
|
49
53
|
if block_given?
|
50
|
-
Expect.new(@__assertions__, block)
|
54
|
+
Expect.new(@__assertions__, block, buffered: true)
|
51
55
|
else
|
52
|
-
Expect.new(@__assertions__, subject)
|
56
|
+
Expect.new(@__assertions__, subject, buffered: true)
|
53
57
|
end
|
54
58
|
end
|
55
59
|
end
|
data/lib/sus/file.rb
CHANGED
@@ -9,6 +9,17 @@ require_relative 'context'
|
|
9
9
|
# This has to be done at the top level. It allows us to define constants within the given class while still retaining top-level constant resolution.
|
10
10
|
Sus::TOPLEVEL_CLASS_EVAL = ->(__klass__, __path__){__klass__.class_eval(::File.read(__path__), __path__)}
|
11
11
|
|
12
|
+
# This is a hack to allow us to get the line number of a syntax error.
|
13
|
+
unless SyntaxError.method_defined?(:lineno)
|
14
|
+
class SyntaxError
|
15
|
+
def lineno
|
16
|
+
if message =~ /:(\d+):/
|
17
|
+
$1.to_i
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
12
23
|
module Sus
|
13
24
|
module File
|
14
25
|
extend Context
|
@@ -19,11 +30,17 @@ module Sus
|
|
19
30
|
|
20
31
|
def self.build(parent, path)
|
21
32
|
base = Class.new(parent)
|
33
|
+
|
22
34
|
base.extend(File)
|
23
35
|
base.description = path
|
24
|
-
base.identity = Identity.
|
36
|
+
base.identity = Identity.file(parent.identity, path)
|
25
37
|
|
26
|
-
|
38
|
+
begin
|
39
|
+
TOPLEVEL_CLASS_EVAL.call(base, path)
|
40
|
+
rescue StandardError, LoadError, SyntaxError => error
|
41
|
+
# We add this as a child of the base class so that it is included in the tree under the file rather than completely replacing it, which can be confusing:
|
42
|
+
base.add FileLoadError.build(self, path, error)
|
43
|
+
end
|
27
44
|
|
28
45
|
return base
|
29
46
|
end
|
@@ -35,7 +52,16 @@ module Sus
|
|
35
52
|
|
36
53
|
class FileLoadError
|
37
54
|
def self.build(parent, path, error)
|
38
|
-
|
55
|
+
identity = Identity.file(parent.identity, path)
|
56
|
+
|
57
|
+
# This is a mess.
|
58
|
+
if error.is_a?(SyntaxError) and error.path == path
|
59
|
+
identity = identity.with_line(error.lineno)
|
60
|
+
else
|
61
|
+
identity = identity.scoped(error.backtrace_locations)
|
62
|
+
end
|
63
|
+
|
64
|
+
self.new(identity, path, error)
|
39
65
|
end
|
40
66
|
|
41
67
|
def initialize(identity, path, error)
|
@@ -45,17 +71,22 @@ module Sus
|
|
45
71
|
end
|
46
72
|
|
47
73
|
attr :identity
|
74
|
+
attr :error
|
48
75
|
|
49
76
|
def leaf?
|
50
77
|
true
|
51
78
|
end
|
52
79
|
|
53
|
-
EMPTY =
|
80
|
+
EMPTY = Hash.new.freeze
|
54
81
|
|
55
82
|
def children
|
56
83
|
EMPTY
|
57
84
|
end
|
58
85
|
|
86
|
+
def description
|
87
|
+
@path
|
88
|
+
end
|
89
|
+
|
59
90
|
def print(output)
|
60
91
|
output.write("file ", :path, @identity)
|
61
92
|
end
|
@@ -72,8 +103,6 @@ module Sus
|
|
72
103
|
module Context
|
73
104
|
def file(path)
|
74
105
|
add File.build(self, path)
|
75
|
-
rescue StandardError, LoadError, SyntaxError => error
|
76
|
-
add FileLoadError.build(self, path, error)
|
77
106
|
end
|
78
107
|
end
|
79
108
|
end
|
data/lib/sus/identity.rb
CHANGED
@@ -5,6 +5,10 @@
|
|
5
5
|
|
6
6
|
module Sus
|
7
7
|
class Identity
|
8
|
+
def self.file(parent, path, name = path, **options)
|
9
|
+
self.new(path, name, nil, nil, **options)
|
10
|
+
end
|
11
|
+
|
8
12
|
def self.nested(parent, name, location = nil, **options)
|
9
13
|
location ||= caller_locations(3...4).first
|
10
14
|
|
@@ -22,6 +26,10 @@ module Sus
|
|
22
26
|
@key = nil
|
23
27
|
end
|
24
28
|
|
29
|
+
def with_line(line)
|
30
|
+
self.class.new(@path, @name, line, @parent, unique: @unique)
|
31
|
+
end
|
32
|
+
|
25
33
|
attr :path
|
26
34
|
attr :name
|
27
35
|
attr :line
|
@@ -32,6 +40,13 @@ module Sus
|
|
32
40
|
self.key
|
33
41
|
end
|
34
42
|
|
43
|
+
def to_location
|
44
|
+
{
|
45
|
+
path: ::File.expand_path(@path),
|
46
|
+
line: @line,
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
35
50
|
def inspect
|
36
51
|
"\#<#{self.class} #{self.to_s}>"
|
37
52
|
end
|
@@ -69,6 +84,27 @@ module Sus
|
|
69
84
|
return @key
|
70
85
|
end
|
71
86
|
|
87
|
+
# Given a set of locations, find the first one which matches this identity and return a new identity with the updated line number. This can be used to extract a location from a backtrace.
|
88
|
+
def scoped(locations = nil)
|
89
|
+
if locations
|
90
|
+
# This code path is normally taken if we've got an exception with a backtrace:
|
91
|
+
locations.each do |location|
|
92
|
+
if location.path == @path
|
93
|
+
return self.with_line(location.lineno)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
else
|
97
|
+
# In theory this should be a bit faster:
|
98
|
+
Thread.each_caller_location do |location|
|
99
|
+
if location.path == @path
|
100
|
+
return self.with_line(location.lineno)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
return self
|
106
|
+
end
|
107
|
+
|
72
108
|
protected
|
73
109
|
|
74
110
|
def append_unique_key(key, unique = @unique)
|
data/lib/sus/version.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.18.
|
4
|
+
version: 0.18.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -38,7 +38,7 @@ cert_chain:
|
|
38
38
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
39
39
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
40
40
|
-----END CERTIFICATE-----
|
41
|
-
date: 2023-02-
|
41
|
+
date: 2023-02-20 00:00:00.000000000 Z
|
42
42
|
dependencies:
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: bake-test
|
metadata.gz.sig
CHANGED
Binary file
|