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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77921dd1b6bbde6e2a47db7f4a2f0c421c3f517eb04b7d4273538a5891f51ad2
4
- data.tar.gz: 204c68cd216e6ddb74e469bbdc01ce042f4d4d0d9fab9b3b897d2ebbcce7fec6
3
+ metadata.gz: 7368b0ed0e288959f59f0fe6c2c5dd93ff90ba52f423b1365406e8de260536c1
4
+ data.tar.gz: 10fe5fec8209ce9cea739f580a3f4bd1b36be3b6e384f6ff85ad813fad32d0f2
5
5
  SHA512:
6
- metadata.gz: a9e50aa174880249cd96f519242d59396315475972432b60dd41e6b2c35d5025a7dfb7824fc538a1bee5199c2d93686c0673e4d72de7126f512a1c237041c8ad
7
- data.tar.gz: 8580ec8e113b18c6e61f14c3f31804d76663cfa6288e965fb44d4a06238283461b856d593abb83bee4d270d32da865c609fc253a840d6f58d3ac49a8a7d9cc66
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, 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
@@ -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.new(path)
36
+ base.identity = Identity.file(parent.identity, path)
25
37
 
26
- TOPLEVEL_CLASS_EVAL.call(base, path)
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
- self.new(Identity.new(path), path, error)
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 = Array.new.freeze
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
@@ -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.2"
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.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-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