sus 0.35.2 → 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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/sus/base.rb +1 -1
  4. data/lib/sus/be.rb +5 -2
  5. data/lib/sus/be_truthy.rb +1 -1
  6. data/lib/sus/be_within.rb +1 -1
  7. data/lib/sus/clock.rb +1 -1
  8. data/lib/sus/config.rb +2 -1
  9. data/lib/sus/context.rb +1 -1
  10. data/lib/sus/describe.rb +1 -1
  11. data/lib/sus/expect.rb +6 -4
  12. data/lib/sus/file.rb +1 -1
  13. data/lib/sus/filter.rb +1 -1
  14. data/lib/sus/fixtures/temporary_directory_context.rb +1 -1
  15. data/lib/sus/fixtures.rb +1 -1
  16. data/lib/sus/have/all.rb +1 -1
  17. data/lib/sus/have/any.rb +1 -1
  18. data/lib/sus/have.rb +12 -3
  19. data/lib/sus/have_duration.rb +1 -1
  20. data/lib/sus/identity.rb +1 -1
  21. data/lib/sus/integrations.rb +1 -1
  22. data/lib/sus/it.rb +1 -1
  23. data/lib/sus/it_behaves_like.rb +1 -1
  24. data/lib/sus/let.rb +1 -1
  25. data/lib/sus/output/backtrace.rb +1 -1
  26. data/lib/sus/output/bar.rb +1 -1
  27. data/lib/sus/output/buffered.rb +19 -1
  28. data/lib/sus/output/lines.rb +1 -1
  29. data/lib/sus/output/progress.rb +1 -1
  30. data/lib/sus/output/status.rb +3 -3
  31. data/lib/sus/output/structured.rb +1 -1
  32. data/lib/sus/output/text.rb +9 -1
  33. data/lib/sus/output/variable.rb +178 -0
  34. data/lib/sus/output/xterm.rb +1 -1
  35. data/lib/sus/output.rb +19 -6
  36. data/lib/sus/raise_exception.rb +3 -3
  37. data/lib/sus/registry.rb +1 -1
  38. data/lib/sus/respond_to.rb +2 -1
  39. data/lib/sus/shared.rb +1 -1
  40. data/lib/sus/tree.rb +1 -1
  41. data/lib/sus/version.rb +2 -2
  42. data/lib/sus/with.rb +1 -1
  43. data/lib/sus.rb +1 -0
  44. data/license.md +2 -1
  45. data/readme.md +25 -0
  46. data/releases.md +8 -0
  47. data.tar.gz.sig +0 -0
  48. metadata +5 -3
  49. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38873e75813a38f0b6a1d95006cf285ad40deabf372ea4dd6014e0ab41d994a9
4
- data.tar.gz: 9d9bb3cbf3dc3eaba78d5ebe4880f3cef862cffbbb50f7760e27a994776c2ed4
3
+ metadata.gz: 13490197ec8a1933e204be63306d7073dc91031f91014540d2fb32f28a5a9fe8
4
+ data.tar.gz: d95be355e8ee973b7c6873024dea1aca229ead1ca07ac32ee758c1c200cffa2c
5
5
  SHA512:
6
- metadata.gz: 3e6bc26c14aaf3be9b3232eda519a47fa05b659ff4baf501da3c830548ead2e4125ab0e37c7a28693fd5fce93fd0aa122ea09ce3843a939170de4f1726f6decd
7
- data.tar.gz: 100146d5119b3bdff4cfb924173385338dfba86ea1d65f11bd5270422d872185532c59857971e14afeeea2d0212e02d44b7449b1dedd1318030d1e5e41524a04
6
+ metadata.gz: e7282096b713bbaf9b6e08839ccc8436c4660c4e2b913c08a9af6867b84ea518f0ba2366db46a67d30e81d20ce845a32dcfb4f13e1712fdc6aabe01e7f67b4af
7
+ data.tar.gz: be1584808b3db0e3ef6bfc1c9d06c928ad0ca292244dd8a53633cc619107ac74604240d5fb916329654ac68df5033456e1802db28f9af308528341b17bf899c4
checksums.yaml.gz.sig CHANGED
Binary file
data/lib/sus/base.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "context"
7
7
 
data/lib/sus/be.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents a predicate matcher that can be used with `expect(...).to be(...)`.
@@ -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/be_truthy.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2023, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents a predicate that checks if the subject is truthy.
data/lib/sus/be_within.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents a predicate that checks if the subject is within a tolerance of a value.
data/lib/sus/clock.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2023, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents a clock for measuring elapsed time during test execution.
data/lib/sus/config.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2025, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
+ # Copyright, 2026, by William T. Nelson.
5
6
 
6
7
  require_relative "clock"
7
8
  require_relative "registry"
data/lib/sus/context.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "assertions"
7
7
  require_relative "identity"
data/lib/sus/describe.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "context"
7
7
 
data/lib/sus/expect.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents an expectation that can be used with predicates to make assertions.
@@ -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/file.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
  # Copyright, 2022, by Brad Schrag.
6
6
 
7
7
  require_relative "context"
data/lib/sus/filter.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Provides a way to filter the registry according to the suffix on loaded paths.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require "tmpdir"
7
7
 
data/lib/sus/fixtures.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # @namespace
data/lib/sus/have/all.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  module Have
data/lib/sus/have/any.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  module Have
data/lib/sus/have.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2024, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
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
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents a predicate that measures the duration of a block execution.
data/lib/sus/identity.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents a unique identity for a test or context, used for identification and location tracking.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # @namespace
data/lib/sus/it.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "context"
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "context"
7
7
 
data/lib/sus/let.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "context"
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2024, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  module Output
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2022, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  module Output
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
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)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require "io/console"
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "bar"
7
7
  require_relative "status"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  module Output
@@ -10,8 +10,8 @@ module Sus
10
10
  # Register status styling with an output handler.
11
11
  # @parameter output [Output] The output handler to register with.
12
12
  def self.register(output)
13
- output[:free] ||= output.style(:blue)
14
- output[:busy] ||= output.style(:orange)
13
+ output[:free] ||= output.style(:green)
14
+ output[:busy] ||= output.style(:blue)
15
15
  end
16
16
 
17
17
  # Initialize a new Status indicator.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "null"
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "messages"
7
7
  require_relative "buffered"
@@ -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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require "io/console"
7
7
 
data/lib/sus/output.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "output/bar"
7
7
  require_relative "output/text"
@@ -13,11 +13,19 @@ require_relative "output/progress"
13
13
  module Sus
14
14
  # Represents output handlers for test results and messages.
15
15
  module Output
16
+ # Detect if we're running in GitHub Actions, where human-readable output is preferred.
17
+ # GitHub Actions sets the GITHUB_ACTIONS environment variable to "true".
18
+ # @parameter env [Hash] The environment variables to check.
19
+ def self.github_actions?(env)
20
+ env["GITHUB_ACTIONS"] == "true"
21
+ end
22
+
16
23
  # Create an appropriate output handler for the given IO.
17
24
  # @parameter io [IO] The IO object to write to.
18
- # @returns [XTerm, Text] An XTerm handler if the IO is a TTY, otherwise a Text handler.
19
- def self.for(io)
20
- if io.isatty
25
+ # @parameter env [Hash] The environment variables to consider (defaults to ENV).
26
+ # @returns [XTerm, Text] An XTerm handler if the IO is a TTY or running in GitHub Actions, otherwise a Text handler.
27
+ def self.for(io, env = ENV)
28
+ if io.isatty or self.github_actions?(env)
21
29
  XTerm.new(io)
22
30
  else
23
31
  Text.new(io)
@@ -26,9 +34,10 @@ module Sus
26
34
 
27
35
  # Create a default output handler with styling configured.
28
36
  # @parameter io [IO] The IO object to write to (defaults to $stderr).
37
+ # @parameter env [Hash] The environment variables to consider (defaults to ENV).
29
38
  # @returns [XTerm, Text] A configured output handler.
30
- def self.default(io = $stderr)
31
- output = self.for(io)
39
+ def self.default(io = $stderr, env = ENV)
40
+ output = self.for(io, env)
32
41
 
33
42
  Output::Bar.register(output)
34
43
 
@@ -40,6 +49,10 @@ module Sus
40
49
 
41
50
  output[:variable] = output.style(:blue, nil, :bold)
42
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
+
43
56
  output[:path] = output.style(:yellow)
44
57
  output[:line] = output.style(:yellow)
45
58
  output[:identity] = output.style(:yellow)
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents a predicate that checks if a block raises an exception.
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(...)
data/lib/sus/registry.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
  # Copyright, 2022, by Brad Schrag.
6
6
 
7
7
  require_relative "base"
@@ -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/shared.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "context"
7
7
 
data/lib/sus/tree.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2026, by Samuel Williams.
5
5
 
6
6
  module Sus
7
7
  # Represents a tree structure of test contexts.
data/lib/sus/version.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2025, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  # @namespace
7
7
  module Sus
8
- VERSION = "0.35.2"
8
+ VERSION = "0.37.0"
9
9
  end
data/lib/sus/with.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "context"
7
7
 
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/license.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2021-2025, by Samuel Williams.
3
+ Copyright, 2021-2026, by Samuel Williams.
4
4
  Copyright, 2022, by Brad Schrag.
5
+ Copyright, 2026, by William T. Nelson.
5
6
 
6
7
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
8
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -33,6 +33,14 @@ 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
+
40
+ ### v0.36.0
41
+
42
+ - Hard code `XTerm` output for GitHub Actions, as it supports ANSI escape codes.
43
+
36
44
  ### v0.35.0
37
45
 
38
46
  - Add `Sus::Fixtures::TemporaryDirectoryContext`.
@@ -53,6 +61,7 @@ Please see the [project releases](https://socketry.github.io/sus/releases/index)
53
61
  ## See Also
54
62
 
55
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.
56
65
 
57
66
  ## Contributing
58
67
 
@@ -64,6 +73,22 @@ We welcome contributions to this project.
64
73
  4. Push to the branch (`git push origin my-new-feature`).
65
74
  5. Create new Pull Request.
66
75
 
76
+ ### Running Tests
77
+
78
+ To run the test suite:
79
+
80
+ ``` shell
81
+ bundle exec sus
82
+ ```
83
+
84
+ ### Making Releases
85
+
86
+ To make a new release:
87
+
88
+ ``` shell
89
+ bundle exec bake gem:release:patch # or minor or major
90
+ ```
91
+
67
92
  ### Developer Certificate of Origin
68
93
 
69
94
  In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
data/releases.md CHANGED
@@ -1,5 +1,13 @@
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
+
7
+ ## v0.36.0
8
+
9
+ - Hard code `XTerm` output for GitHub Actions, as it supports ANSI escape codes.
10
+
3
11
  ## v0.35.0
4
12
 
5
13
  - Add `Sus::Fixtures::TemporaryDirectoryContext`.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.35.2
4
+ version: 0.37.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Brad Schrag
9
+ - William T. Nelson
9
10
  bindir: bin
10
11
  cert_chain:
11
12
  - |
@@ -92,6 +93,7 @@ files:
92
93
  - lib/sus/output/status.rb
93
94
  - lib/sus/output/structured.rb
94
95
  - lib/sus/output/text.rb
96
+ - lib/sus/output/variable.rb
95
97
  - lib/sus/output/xterm.rb
96
98
  - lib/sus/raise_exception.rb
97
99
  - lib/sus/receive.rb
@@ -118,14 +120,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
120
  requirements:
119
121
  - - ">="
120
122
  - !ruby/object:Gem::Version
121
- version: '3.2'
123
+ version: '3.3'
122
124
  required_rubygems_version: !ruby/object:Gem::Requirement
123
125
  requirements:
124
126
  - - ">="
125
127
  - !ruby/object:Gem::Version
126
128
  version: '0'
127
129
  requirements: []
128
- rubygems_version: 4.0.3
130
+ rubygems_version: 4.0.10
129
131
  specification_version: 4
130
132
  summary: A fast and scalable test runner.
131
133
  test_files: []
metadata.gz.sig CHANGED
Binary file