sus 0.18.0 → 0.18.1

Sign up to get free protection for your applications and to get access to all the features.
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