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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8e0db7c132f0bf73f74ed8e0def2d4e53d41d48133f8cd3183c18fa530ec847
4
- data.tar.gz: f0e3c8b30d700ed917d2a74a81837f21278c4f45fdc3271a7a9213a0dd631895
3
+ metadata.gz: 13490197ec8a1933e204be63306d7073dc91031f91014540d2fb32f28a5a9fe8
4
+ data.tar.gz: d95be355e8ee973b7c6873024dea1aca229ead1ca07ac32ee758c1c200cffa2c
5
5
  SHA512:
6
- metadata.gz: 0f5d22b2d3548fef4d43afeea24fa74a2c47fa99d6640b7db4c0b6af00ecd07cdcb52303c4d3276fc32d4e62e7ae045eeffbbef54c379bd0f21f207e820b9aef
7
- data.tar.gz: 8b1485cee341ba0b8083bda0c5da868f97f68002657f051d977b590fbb4e1bf7a97e9fa816c6949961740b39704de507e003e5c6dc6c213b6791f7ebdc7d53dc
6
+ metadata.gz: e7282096b713bbaf9b6e08839ccc8436c4660c4e2b913c08a9af6867b84ea518f0ba2366db46a67d30e81d20ce845a32dcfb4f13e1712fdc6aabe01e7f67b4af
7
+ data.tar.gz: be1584808b3db0e3ef6bfc1c9d06c928ad0ca292244dd8a53633cc619107ac74604240d5fb916329654ac68df5033456e1802db28f9af308528341b17bf899c4
checksums.yaml.gz.sig CHANGED
Binary file
@@ -108,7 +108,7 @@ Use `describe` to group related tests:
108
108
  ```ruby
109
109
  describe MyThing do
110
110
  # The subject will be whatever is described:
111
- let(:my_thing){subject.new}
111
+ let(:my_thing) {subject.new}
112
112
  end
113
113
  ```
114
114
 
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(" ", :variable, arguments.map(&:inspect).join, :reset)
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.inspect
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 ", :variable, @inspect, :reset, " ")
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 ", :variable, @name.inspect, :reset, " ", @predicate, :reset)
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
- assertions.nested("[#{index}] = #{value.inspect}", distinct: true) do |assertions|
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
 
@@ -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)
@@ -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)
@@ -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, Regexp, Object | Nil] Optional message matcher.
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, Regexp, Object | Nil] Optional message matcher.
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(...)
@@ -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 ", :variable, @options.inspect)
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
@@ -5,5 +5,5 @@
5
5
 
6
6
  # @namespace
7
7
  module Sus
8
- VERSION = "0.36.0"
8
+ VERSION = "0.37.0"
9
9
  end
data/lib/sus.rb CHANGED
@@ -6,6 +6,7 @@
6
6
  require_relative "sus/version"
7
7
  require_relative "sus/config"
8
8
  require_relative "sus/registry"
9
+ require_relative "sus/output/variable"
9
10
  require_relative "sus/assertions"
10
11
  require_relative "sus/tree"
11
12
 
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�YikJ
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��Vj��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.36.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.6
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