sus 0.18.0 → 0.18.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77921dd1b6bbde6e2a47db7f4a2f0c421c3f517eb04b7d4273538a5891f51ad2
4
- data.tar.gz: 204c68cd216e6ddb74e469bbdc01ce042f4d4d0d9fab9b3b897d2ebbcce7fec6
3
+ metadata.gz: e39bdf28a3135bf28cf66714d3ac2998c1c863e979c172573a3e5e509de2246e
4
+ data.tar.gz: 88273ca646abe719cb562693f233d6210d8e505456ae8dd663d74971086d33b7
5
5
  SHA512:
6
- metadata.gz: a9e50aa174880249cd96f519242d59396315475972432b60dd41e6b2c35d5025a7dfb7824fc538a1bee5199c2d93686c0673e4d72de7126f512a1c237041c8ad
7
- data.tar.gz: 8580ec8e113b18c6e61f14c3f31804d76663cfa6288e965fb44d4a06238283461b856d593abb83bee4d270d32da865c609fc253a840d6f58d3ac49a8a7d9cc66
6
+ metadata.gz: eca3ca7daddbc30591698658f6d87cc11a56c9dee2456b1060339fe1442aa5d8479a11ccdf7d9f4e7d617478ff033b0c24482db8d8145b7046e69a106a75409c
7
+ data.tar.gz: 2df536e6206a37f475bde130d8a6f8f4f8c8eab49a3ea2f7d29f48b4a64b53d6145cc77c27f2105043385756cc8d44d34983334a6de633c5c8d78275777b1ee7
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, message: assertions.output.string, duration: assertions.clock.ms})
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, message: assertions.output.string, duration: assertions.clock.ms})
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
@@ -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(location, message)
143
- @location = location
150
+ def initialize(identity, message)
151
+ @identity = identity
144
152
  @message = message
145
153
  end
146
154
 
147
- attr :location
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(message, backtrace)
178
+ @passed << Assert.new(identity, message)
157
179
 
158
180
  if !@orientation || @verbose
159
- @output.puts(:indent, *pass_prefix, message || "assertion", backtrace)
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 || "assertion", backtrace)
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
- @errored << self
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
@@ -45,7 +45,7 @@ module Sus
45
45
  base.extend(Context)
46
46
  base.identity = Identity.new(root) if root
47
47
  base.description = description
48
-
48
+
49
49
  return base
50
50
  end
51
51
  end
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
- @assertions.nested(self, inverted: @inverted) do |assertions|
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
@@ -19,11 +19,17 @@ module Sus
19
19
 
20
20
  def self.build(parent, path)
21
21
  base = Class.new(parent)
22
+
22
23
  base.extend(File)
23
24
  base.description = path
24
- base.identity = Identity.new(path)
25
+ base.identity = Identity.file(parent.identity, path)
25
26
 
26
- TOPLEVEL_CLASS_EVAL.call(base, path)
27
+ begin
28
+ TOPLEVEL_CLASS_EVAL.call(base, path)
29
+ rescue StandardError, LoadError, SyntaxError => error
30
+ # 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:
31
+ base.add FileLoadError.build(self, path, error)
32
+ end
27
33
 
28
34
  return base
29
35
  end
@@ -35,7 +41,8 @@ module Sus
35
41
 
36
42
  class FileLoadError
37
43
  def self.build(parent, path, error)
38
- self.new(Identity.new(path), path, error)
44
+ identity = Identity.file(parent.identity, path).scoped(error.backtrace_locations)
45
+ self.new(identity, path, error)
39
46
  end
40
47
 
41
48
  def initialize(identity, path, error)
@@ -50,12 +57,16 @@ module Sus
50
57
  true
51
58
  end
52
59
 
53
- EMPTY = Array.new.freeze
60
+ EMPTY = Hash.new.freeze
54
61
 
55
62
  def children
56
63
  EMPTY
57
64
  end
58
65
 
66
+ def description
67
+ @path
68
+ end
69
+
59
70
  def print(output)
60
71
  output.write("file ", :path, @identity)
61
72
  end
@@ -72,8 +83,6 @@ module Sus
72
83
  module Context
73
84
  def file(path)
74
85
  add File.build(self, path)
75
- rescue StandardError, LoadError, SyntaxError => error
76
- add FileLoadError.build(self, path, error)
77
86
  end
78
87
  end
79
88
  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
 
@@ -32,6 +36,13 @@ module Sus
32
36
  self.key
33
37
  end
34
38
 
39
+ def to_location
40
+ {
41
+ path: ::File.expand_path(@path),
42
+ line: @line,
43
+ }
44
+ end
45
+
35
46
  def inspect
36
47
  "\#<#{self.class} #{self.to_s}>"
37
48
  end
@@ -69,8 +80,33 @@ module Sus
69
80
  return @key
70
81
  end
71
82
 
83
+ # 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.
84
+ def scoped(locations = nil)
85
+ if locations
86
+ # This code path is normally taken if we've got an exception with a backtrace:
87
+ locations.each do |location|
88
+ if location.path == @path
89
+ return self.with_line(location.lineno)
90
+ end
91
+ end
92
+ else
93
+ # In theory this should be a bit faster:
94
+ Thread.each_caller_location do |location|
95
+ if location.path == @path
96
+ return self.with_line(location.lineno)
97
+ end
98
+ end
99
+ end
100
+
101
+ return self
102
+ end
103
+
72
104
  protected
73
105
 
106
+ def with_line(line)
107
+ Identity.new(@path, @name, line, @parent, unique: @unique)
108
+ end
109
+
74
110
  def append_unique_key(key, unique = @unique)
75
111
  if @parent
76
112
  @parent.append_unique_key(key)
data/lib/sus/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2021-2022, by Samuel Williams.
5
5
 
6
6
  module Sus
7
- VERSION = "0.18.0"
7
+ VERSION = "0.18.1"
8
8
  end
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.0
4
+ version: 0.18.1
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-19 00:00:00.000000000 Z
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