sus 0.36.0 → 0.37.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
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +1 -1
- data/lib/sus/be.rb +4 -1
- data/lib/sus/expect.rb +5 -3
- data/lib/sus/have.rb +11 -2
- data/lib/sus/output/buffered.rb +18 -0
- data/lib/sus/output/text.rb +8 -0
- data/lib/sus/output/variable.rb +178 -0
- data/lib/sus/output.rb +4 -0
- data/lib/sus/raise_exception.rb +2 -2
- data/lib/sus/respond_to.rb +2 -1
- data/lib/sus/version.rb +1 -1
- data/lib/sus.rb +1 -0
- data/readme.md +5 -0
- data/releases.md +4 -0
- data.tar.gz.sig +4 -1
- metadata +3 -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: 13490197ec8a1933e204be63306d7073dc91031f91014540d2fb32f28a5a9fe8
|
|
4
|
+
data.tar.gz: d95be355e8ee973b7c6873024dea1aca229ead1ca07ac32ee758c1c200cffa2c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7282096b713bbaf9b6e08839ccc8436c4660c4e2b913c08a9af6867b84ea518f0ba2366db46a67d30e81d20ce845a32dcfb4f13e1712fdc6aabe01e7f67b4af
|
|
7
|
+
data.tar.gz: be1584808b3db0e3ef6bfc1c9d06c928ad0ca292244dd8a53633cc619107ac74604240d5fb916329654ac68df5033456e1802db28f9af308528341b17bf899c4
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/context/getting-started.md
CHANGED
data/lib/sus/be.rb
CHANGED
|
@@ -146,7 +146,10 @@ module Sus
|
|
|
146
146
|
output.write("be ", :be, operation.to_s, :reset)
|
|
147
147
|
|
|
148
148
|
if arguments.any?
|
|
149
|
-
output.write(" "
|
|
149
|
+
output.write(" ")
|
|
150
|
+
arguments.each do |argument|
|
|
151
|
+
output.variable(argument)
|
|
152
|
+
end
|
|
150
153
|
end
|
|
151
154
|
end
|
|
152
155
|
|
data/lib/sus/expect.rb
CHANGED
|
@@ -15,8 +15,8 @@ module Sus
|
|
|
15
15
|
@assertions = assertions
|
|
16
16
|
@subject = subject
|
|
17
17
|
|
|
18
|
-
# We capture this here, as changes to state may cause the inspect output to change, affecting the output produced by #print.
|
|
19
|
-
@inspect = @subject
|
|
18
|
+
# We capture this here, as changes to state may cause the inspect output to change, affecting the output produced by #print. The representation is buffered (as a stream of styled tokens) and truncated to avoid excessively noisy output for large subjects; colours are resolved later when the buffer is replayed into the output.
|
|
19
|
+
@inspect = Output::Variable.buffer(@subject)
|
|
20
20
|
|
|
21
21
|
@inverted = inverted
|
|
22
22
|
@distinct = true
|
|
@@ -39,7 +39,9 @@ module Sus
|
|
|
39
39
|
# Print a representation of this expectation.
|
|
40
40
|
# @parameter output [Output] The output target.
|
|
41
41
|
def print(output)
|
|
42
|
-
output.write("expect "
|
|
42
|
+
output.write("expect ")
|
|
43
|
+
output.append(@inspect)
|
|
44
|
+
output.write(" ")
|
|
43
45
|
|
|
44
46
|
if @inverted
|
|
45
47
|
output.write("not to", :reset)
|
data/lib/sus/have.rb
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
require_relative "have/all"
|
|
7
7
|
require_relative "have/any"
|
|
8
|
+
require_relative "output/variable"
|
|
8
9
|
|
|
9
10
|
module Sus
|
|
10
11
|
# Represents predicates for checking collections and object attributes.
|
|
@@ -22,7 +23,9 @@ module Sus
|
|
|
22
23
|
# Print a representation of this predicate.
|
|
23
24
|
# @parameter output [Output] The output target.
|
|
24
25
|
def print(output)
|
|
25
|
-
output.write("key "
|
|
26
|
+
output.write("key ")
|
|
27
|
+
output.variable(@name)
|
|
28
|
+
output.write(" ", @predicate, :reset)
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
# Evaluate this predicate against a subject.
|
|
@@ -89,7 +92,13 @@ module Sus
|
|
|
89
92
|
index = 0
|
|
90
93
|
|
|
91
94
|
subject.each do |value|
|
|
92
|
-
|
|
95
|
+
# Capture the label (index and value) as a buffer of formatting
|
|
96
|
+
# instructions, so the value is truncated/styled consistently:
|
|
97
|
+
label = Output::Buffered.new
|
|
98
|
+
label.write("[#{index}] = ")
|
|
99
|
+
label.variable(value)
|
|
100
|
+
|
|
101
|
+
assertions.nested(label, distinct: true) do |assertions|
|
|
93
102
|
@predicate&.call(assertions, value)
|
|
94
103
|
end
|
|
95
104
|
|
data/lib/sus/output/buffered.rb
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
require "io/console"
|
|
7
7
|
require "stringio"
|
|
8
8
|
|
|
9
|
+
require_relative "text"
|
|
10
|
+
|
|
9
11
|
module Sus
|
|
10
12
|
module Output
|
|
11
13
|
# Represents a buffered output handler that stores output operations for later replay.
|
|
@@ -51,6 +53,14 @@ module Sus
|
|
|
51
53
|
@tee&.append(buffer)
|
|
52
54
|
end
|
|
53
55
|
|
|
56
|
+
# Replay this buffer into the given output. This allows a buffer (a captured
|
|
57
|
+
# stream of formatting instructions) to be used anywhere a printable target
|
|
58
|
+
# is expected, e.g. as a nested assertion label.
|
|
59
|
+
# @parameter output [Output] The output target.
|
|
60
|
+
def print(output)
|
|
61
|
+
output.append(self)
|
|
62
|
+
end
|
|
63
|
+
|
|
54
64
|
# @returns [String] The buffered output as a string.
|
|
55
65
|
def string
|
|
56
66
|
io = StringIO.new
|
|
@@ -99,6 +109,14 @@ module Sus
|
|
|
99
109
|
@tee&.puts(*arguments)
|
|
100
110
|
end
|
|
101
111
|
|
|
112
|
+
# Write a value in the variable style: a compact, truncated representation
|
|
113
|
+
# (handling large values, recursion and styling internally).
|
|
114
|
+
# @parameter value [Object] The value to represent.
|
|
115
|
+
# @parameter limit [Integer] The maximum length of the representation.
|
|
116
|
+
def variable(value, limit: Variable::TRUNCATION_LIMIT)
|
|
117
|
+
Variable.format(self, value, limit: limit)
|
|
118
|
+
end
|
|
119
|
+
|
|
102
120
|
# Record an assertion.
|
|
103
121
|
# @parameter arguments [Array] The assertion arguments.
|
|
104
122
|
def assert(*arguments)
|
data/lib/sus/output/text.rb
CHANGED
|
@@ -139,6 +139,14 @@ module Sus
|
|
|
139
139
|
write(*arguments)
|
|
140
140
|
@io.puts(self.reset)
|
|
141
141
|
end
|
|
142
|
+
|
|
143
|
+
# Write a value in the variable style: a compact, truncated representation
|
|
144
|
+
# (handling large values, recursion and styling internally).
|
|
145
|
+
# @parameter value [Object] The value to represent.
|
|
146
|
+
# @parameter limit [Integer] The maximum length of the representation.
|
|
147
|
+
def variable(value, limit: Variable::TRUNCATION_LIMIT)
|
|
148
|
+
Variable.format(self, value, limit: limit)
|
|
149
|
+
end
|
|
142
150
|
end
|
|
143
151
|
end
|
|
144
152
|
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "buffered"
|
|
7
|
+
|
|
8
|
+
module Sus
|
|
9
|
+
module Output
|
|
10
|
+
# Provides a compact, truncated representation of values for output.
|
|
11
|
+
#
|
|
12
|
+
# Rather than building a full `inspect` string and then truncating it, we walk
|
|
13
|
+
# the value and stream tokens directly into an output, aborting as soon as a
|
|
14
|
+
# character budget is exhausted. This means we never materialize the full
|
|
15
|
+
# representation of a large subject (e.g. a big array or richly inspected
|
|
16
|
+
# instance).
|
|
17
|
+
#
|
|
18
|
+
# Containers (arrays and hashes) are formatted directly so we can recurse with
|
|
19
|
+
# the budget; leaf values delegate to native `inspect` so their formatting
|
|
20
|
+
# exactly matches Ruby. The value itself is emitted in a single style (matching
|
|
21
|
+
# the rest of sus's output), and only the truncation ellipsis is highlighted
|
|
22
|
+
# distinctly so it's clear where output was cut.
|
|
23
|
+
module Variable
|
|
24
|
+
# The maximum length of an inspected value before it is truncated. Override
|
|
25
|
+
# it with the `SUS_OUTPUT_VARIABLE_TRUNCATION_LIMIT` environment variable; a
|
|
26
|
+
# value of `0` (or `nil`) disables truncation entirely.
|
|
27
|
+
TRUNCATION_LIMIT = ENV.fetch("SUS_OUTPUT_VARIABLE_TRUNCATION_LIMIT", 100).then do |value|
|
|
28
|
+
value = Integer(value)
|
|
29
|
+
value.zero? ? nil : value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The string appended to a truncated value.
|
|
33
|
+
ELLIPSIS = "…"
|
|
34
|
+
|
|
35
|
+
# Walks a value and emits styled tokens to an output, aborting once the
|
|
36
|
+
# character budget is exhausted.
|
|
37
|
+
class Formatter
|
|
38
|
+
# Raised internally to abort formatting once the limit is reached.
|
|
39
|
+
class Truncated < StandardError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @parameter output [Output] The output target to write tokens to.
|
|
43
|
+
# @parameter limit [Integer] The maximum number of characters to emit.
|
|
44
|
+
def initialize(output, limit:)
|
|
45
|
+
@output = output
|
|
46
|
+
@remaining = limit
|
|
47
|
+
@seen = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Emit a token in the variable style, truncating and aborting once the
|
|
51
|
+
# budget is exceeded.
|
|
52
|
+
# @parameter text [String] The token text to emit.
|
|
53
|
+
def emit(text)
|
|
54
|
+
truncated = false
|
|
55
|
+
|
|
56
|
+
if text.length > @remaining
|
|
57
|
+
text = text[0, @remaining]
|
|
58
|
+
truncated = true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@remaining -= text.length
|
|
62
|
+
|
|
63
|
+
@output.write(:variable, text, :reset)
|
|
64
|
+
|
|
65
|
+
raise Truncated if truncated
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Format a value, emitting styled tokens to the output.
|
|
69
|
+
# @parameter value [Object] The value to format.
|
|
70
|
+
def format(value)
|
|
71
|
+
case value
|
|
72
|
+
when String
|
|
73
|
+
# Inspect only a prefix so we never escape a huge string:
|
|
74
|
+
slice = value.length > @remaining ? value[0, @remaining] : value
|
|
75
|
+
emit(slice.inspect)
|
|
76
|
+
when Array
|
|
77
|
+
format_array(value)
|
|
78
|
+
when Hash
|
|
79
|
+
format_hash(value)
|
|
80
|
+
else
|
|
81
|
+
format_object(value)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def format_array(array)
|
|
88
|
+
if @seen&.key?(array.object_id)
|
|
89
|
+
return emit("[...]")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
(@seen ||= {})[array.object_id] = true
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
emit("[")
|
|
96
|
+
array.each_with_index do |element, index|
|
|
97
|
+
emit(", ") if index > 0
|
|
98
|
+
format(element)
|
|
99
|
+
end
|
|
100
|
+
emit("]")
|
|
101
|
+
ensure
|
|
102
|
+
@seen.delete(array.object_id)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def format_hash(hash)
|
|
107
|
+
if @seen&.key?(hash.object_id)
|
|
108
|
+
return emit("{...}")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
(@seen ||= {})[hash.object_id] = true
|
|
112
|
+
|
|
113
|
+
begin
|
|
114
|
+
emit("{")
|
|
115
|
+
first = true
|
|
116
|
+
hash.each do |key, value|
|
|
117
|
+
emit(", ") unless first
|
|
118
|
+
first = false
|
|
119
|
+
|
|
120
|
+
if key.is_a?(Symbol)
|
|
121
|
+
# Label form for symbol keys, e.g. `key: value`:
|
|
122
|
+
emit("#{key.inspect.delete_prefix(":")}: ")
|
|
123
|
+
else
|
|
124
|
+
format(key)
|
|
125
|
+
emit(" => ")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
format(value)
|
|
129
|
+
end
|
|
130
|
+
emit("}")
|
|
131
|
+
ensure
|
|
132
|
+
@seen.delete(hash.object_id)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def format_object(value)
|
|
137
|
+
emit(value.inspect)
|
|
138
|
+
rescue Truncated
|
|
139
|
+
raise
|
|
140
|
+
rescue => error
|
|
141
|
+
emit("#<#{value.class} (inspect failed: #{error.class})>")
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Format a value into the given output, truncating at the limit.
|
|
146
|
+
# @parameter output [Output] The output target.
|
|
147
|
+
# @parameter value [Object] The value to format.
|
|
148
|
+
# @parameter limit [Integer] The maximum length of the representation.
|
|
149
|
+
def self.format(output, value, limit: TRUNCATION_LIMIT)
|
|
150
|
+
# With no limit, the formatter would produce exactly `value.inspect`, so
|
|
151
|
+
# we can skip walking the value and emit it directly:
|
|
152
|
+
unless limit
|
|
153
|
+
return output.write(:variable, value.inspect, :reset)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
formatter = Formatter.new(output, limit: limit)
|
|
157
|
+
|
|
158
|
+
begin
|
|
159
|
+
formatter.format(value)
|
|
160
|
+
rescue Formatter::Truncated
|
|
161
|
+
output.write(:ellipsis, ELLIPSIS, :reset)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Capture a value's representation into a buffer, resolving the value
|
|
166
|
+
# immediately, but deferring colour resolution until the buffer is replayed
|
|
167
|
+
# into a real output.
|
|
168
|
+
# @parameter value [Object] The value to capture.
|
|
169
|
+
# @parameter limit [Integer] The maximum length of the representation.
|
|
170
|
+
# @returns [Buffered] A buffer containing the captured representation.
|
|
171
|
+
def self.buffer(value, limit: TRUNCATION_LIMIT)
|
|
172
|
+
buffer = Buffered.new
|
|
173
|
+
self.format(buffer, value, limit: limit)
|
|
174
|
+
return buffer
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
data/lib/sus/output.rb
CHANGED
|
@@ -49,6 +49,10 @@ module Sus
|
|
|
49
49
|
|
|
50
50
|
output[:variable] = output.style(:blue, nil, :bold)
|
|
51
51
|
|
|
52
|
+
# The ellipsis marks where an inspected value was truncated; it is shown
|
|
53
|
+
# faintly so it reads as "trailing off" without competing with the value.
|
|
54
|
+
output[:ellipsis] = output.style(nil, nil, :faint)
|
|
55
|
+
|
|
52
56
|
output[:path] = output.style(:yellow)
|
|
53
57
|
output[:line] = output.style(:yellow)
|
|
54
58
|
output[:identity] = output.style(:yellow)
|
data/lib/sus/raise_exception.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Sus
|
|
|
8
8
|
class RaiseException
|
|
9
9
|
# Initialize a new RaiseException predicate.
|
|
10
10
|
# @parameter exception_class [Class] The exception class to expect.
|
|
11
|
-
# @parameter message [String
|
|
11
|
+
# @parameter message [String | Regexp | Object | Nil] Optional message matcher.
|
|
12
12
|
def initialize(exception_class = Exception, message: nil)
|
|
13
13
|
@exception_class = exception_class
|
|
14
14
|
@message = message
|
|
@@ -68,7 +68,7 @@ module Sus
|
|
|
68
68
|
class Base
|
|
69
69
|
# Create a predicate that checks if a block raises an exception.
|
|
70
70
|
# @parameter exception_class [Class] The exception class to expect.
|
|
71
|
-
# @parameter message [String
|
|
71
|
+
# @parameter message [String | Regexp | Object | Nil] Optional message matcher.
|
|
72
72
|
# @returns [RaiseException] A new RaiseException predicate.
|
|
73
73
|
def raise_exception(...)
|
|
74
74
|
RaiseException.new(...)
|
data/lib/sus/respond_to.rb
CHANGED
|
@@ -49,7 +49,8 @@ module Sus
|
|
|
49
49
|
# Print a representation of this constraint.
|
|
50
50
|
# @parameter output [Output] The output target.
|
|
51
51
|
def print(output)
|
|
52
|
-
output.write("with options "
|
|
52
|
+
output.write("with options ")
|
|
53
|
+
output.variable(@options)
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
# Evaluate this constraint against method parameters.
|
data/lib/sus/version.rb
CHANGED
data/lib/sus.rb
CHANGED
data/readme.md
CHANGED
|
@@ -33,6 +33,10 @@ Please see the [project documentation](https://socketry.github.io/sus/) for more
|
|
|
33
33
|
|
|
34
34
|
Please see the [project releases](https://socketry.github.io/sus/releases/index) for all releases.
|
|
35
35
|
|
|
36
|
+
### v0.37.0
|
|
37
|
+
|
|
38
|
+
- Long values in verbose (and failure) output are now truncated to a configurable length (default 100 characters), preventing huge objects from flooding the output. Set the `SUS_OUTPUT_VARIABLE_TRUNCATION_LIMIT` environment variable to change the limit, or `0` to disable truncation.
|
|
39
|
+
|
|
36
40
|
### v0.36.0
|
|
37
41
|
|
|
38
42
|
- Hard code `XTerm` output for GitHub Actions, as it supports ANSI escape codes.
|
|
@@ -57,6 +61,7 @@ Please see the [project releases](https://socketry.github.io/sus/releases/index)
|
|
|
57
61
|
## See Also
|
|
58
62
|
|
|
59
63
|
- [sus-vscode](https://github.com/socketry/sus-vscode) - Visual Studio Code extension for Sus.
|
|
64
|
+
- [minitest-sus](https://github.com/socketry/minitest-sus) - Use Sus fixtures within Minitest tests.
|
|
60
65
|
|
|
61
66
|
## Contributing
|
|
62
67
|
|
data/releases.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.37.0
|
|
4
|
+
|
|
5
|
+
- Long values in verbose (and failure) output are now truncated to a configurable length (default 100 characters), preventing huge objects from flooding the output. Set the `SUS_OUTPUT_VARIABLE_TRUNCATION_LIMIT` environment variable to change the limit, or `0` to disable truncation.
|
|
6
|
+
|
|
3
7
|
## v0.36.0
|
|
4
8
|
|
|
5
9
|
- Hard code `XTerm` output for GitHub Actions, as it supports ANSI escape codes.
|
data.tar.gz.sig
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
��Hk��r�
|
|
1
|
+
#�l~���ͬ�[�WE�P�,9��q���5���-� ��I�sՁ��Hk\n���֍H1fS Ldz���70�=��^�]Ӏ��£���1g2-~.p/U\�O�V��̺հ���>�����3�/�d ��J��P�֮B�ѐ{���p{�E�r��V�j��r(��0 @���@�ڂ!���ֈ��jumО�=VU��R�L�yuf2Q����c�*$5� ������"��<���M&A�{ev�
|
|
2
|
+
TS%�)iiLN���ɶ��bD�G3���O�:�+B����e�#�"�z��'���,*-;�|L�v6x��w3I�� !Ҝ��^t���k�*���p�c�?l@o�) �_
|
|
3
|
+
|
|
4
|
+
2�:�R�=C9=��y�$�i[��j��L�b^r���s���<
|
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.
|
|
4
|
+
version: 0.37.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -93,6 +93,7 @@ files:
|
|
|
93
93
|
- lib/sus/output/status.rb
|
|
94
94
|
- lib/sus/output/structured.rb
|
|
95
95
|
- lib/sus/output/text.rb
|
|
96
|
+
- lib/sus/output/variable.rb
|
|
96
97
|
- lib/sus/output/xterm.rb
|
|
97
98
|
- lib/sus/raise_exception.rb
|
|
98
99
|
- lib/sus/receive.rb
|
|
@@ -126,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
126
127
|
- !ruby/object:Gem::Version
|
|
127
128
|
version: '0'
|
|
128
129
|
requirements: []
|
|
129
|
-
rubygems_version: 4.0.
|
|
130
|
+
rubygems_version: 4.0.10
|
|
130
131
|
specification_version: 4
|
|
131
132
|
summary: A fast and scalable test runner.
|
|
132
133
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|