sus 0.34.0 → 0.35.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 +352 -0
- data/context/index.yaml +9 -0
- data/context/mocking.md +100 -30
- data/context/{shared.md → shared-contexts.md} +29 -2
- data/lib/sus/assertions.rb +91 -18
- data/lib/sus/base.rb +13 -1
- data/lib/sus/be.rb +84 -0
- data/lib/sus/be_truthy.rb +16 -0
- data/lib/sus/be_within.rb +25 -0
- data/lib/sus/clock.rb +21 -0
- data/lib/sus/config.rb +58 -1
- data/lib/sus/context.rb +28 -5
- data/lib/sus/describe.rb +14 -0
- data/lib/sus/expect.rb +23 -0
- data/lib/sus/file.rb +38 -0
- data/lib/sus/filter.rb +21 -0
- data/lib/sus/fixtures/temporary_directory_context.rb +27 -0
- data/lib/sus/fixtures.rb +1 -0
- data/lib/sus/have/all.rb +8 -0
- data/lib/sus/have/any.rb +8 -0
- data/lib/sus/have.rb +42 -0
- data/lib/sus/have_duration.rb +16 -0
- data/lib/sus/identity.rb +44 -1
- data/lib/sus/integrations.rb +1 -0
- data/lib/sus/it.rb +33 -0
- data/lib/sus/it_behaves_like.rb +16 -0
- data/lib/sus/let.rb +3 -0
- data/lib/sus/mock.rb +39 -1
- data/lib/sus/output/backtrace.rb +31 -1
- data/lib/sus/output/bar.rb +17 -0
- data/lib/sus/output/buffered.rb +32 -1
- data/lib/sus/output/lines.rb +10 -0
- data/lib/sus/output/messages.rb +26 -3
- data/lib/sus/output/null.rb +16 -2
- data/lib/sus/output/progress.rb +29 -1
- data/lib/sus/output/status.rb +13 -0
- data/lib/sus/output/structured.rb +14 -1
- data/lib/sus/output/text.rb +33 -1
- data/lib/sus/output/xterm.rb +11 -1
- data/lib/sus/output.rb +9 -0
- data/lib/sus/raise_exception.rb +16 -0
- data/lib/sus/receive.rb +82 -0
- data/lib/sus/registry.rb +20 -1
- data/lib/sus/respond_to.rb +29 -2
- data/lib/sus/shared.rb +16 -0
- data/lib/sus/tree.rb +10 -0
- data/lib/sus/version.rb +2 -1
- data/lib/sus/with.rb +18 -0
- data/readme.md +8 -0
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +0 -0
- data/context/usage.md +0 -380
data/lib/sus/mock.rb
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2022-
|
|
4
|
+
# Copyright, 2022-2025, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "expect"
|
|
7
7
|
|
|
8
8
|
module Sus
|
|
9
|
+
# Represents a mock object that can intercept and replace method calls on a target object.
|
|
9
10
|
class Mock
|
|
11
|
+
# Initialize a new mock for the given target.
|
|
12
|
+
# @parameter target [Object] The object to mock.
|
|
10
13
|
def initialize(target)
|
|
11
14
|
@target = target
|
|
12
15
|
@interceptor = Module.new
|
|
@@ -14,18 +17,26 @@ module Sus
|
|
|
14
17
|
@target.singleton_class.prepend(@interceptor)
|
|
15
18
|
end
|
|
16
19
|
|
|
20
|
+
# @attribute [Object] The target object being mocked.
|
|
17
21
|
attr :target
|
|
18
22
|
|
|
23
|
+
# Print a representation of this mock.
|
|
24
|
+
# @parameter output [Output] The output target.
|
|
19
25
|
def print(output)
|
|
20
26
|
output.write("mock ", :context, @target.inspect)
|
|
21
27
|
end
|
|
22
28
|
|
|
29
|
+
# Clear all mocked methods from the target.
|
|
23
30
|
def clear
|
|
24
31
|
@interceptor.instance_methods.each do |method_name|
|
|
25
32
|
@interceptor.remove_method(method_name)
|
|
26
33
|
end
|
|
27
34
|
end
|
|
28
35
|
|
|
36
|
+
# Replace a method implementation.
|
|
37
|
+
# @parameter method [Symbol] The method name to replace.
|
|
38
|
+
# @yields {|*arguments, **options, &block| ...} The replacement implementation.
|
|
39
|
+
# @returns [Mock] Returns self for method chaining.
|
|
29
40
|
def replace(method, &hook)
|
|
30
41
|
execution_context = Thread.current
|
|
31
42
|
|
|
@@ -40,6 +51,10 @@ module Sus
|
|
|
40
51
|
return self
|
|
41
52
|
end
|
|
42
53
|
|
|
54
|
+
# Add a hook that runs before a method is called.
|
|
55
|
+
# @parameter method [Symbol] The method name to hook.
|
|
56
|
+
# @yields {|*arguments, **options, &block| ...} The hook to execute before the method.
|
|
57
|
+
# @returns [Mock] Returns self for method chaining.
|
|
43
58
|
def before(method, &hook)
|
|
44
59
|
execution_context = Thread.current
|
|
45
60
|
|
|
@@ -51,6 +66,10 @@ module Sus
|
|
|
51
66
|
return self
|
|
52
67
|
end
|
|
53
68
|
|
|
69
|
+
# Add a hook that runs after a method is called.
|
|
70
|
+
# @parameter method [Symbol] The method name to hook.
|
|
71
|
+
# @yields {|result, *arguments, **options, &block| ...} The hook to execute after the method, receiving the result as the first argument.
|
|
72
|
+
# @returns [Mock] Returns self for method chaining.
|
|
54
73
|
def after(method, &hook)
|
|
55
74
|
execution_context = Thread.current
|
|
56
75
|
|
|
@@ -64,6 +83,8 @@ module Sus
|
|
|
64
83
|
end
|
|
65
84
|
|
|
66
85
|
# Wrap a method, yielding the original method as the first argument, so you can call it from within the hook.
|
|
86
|
+
# @parameter method [Symbol] The method name to wrap.
|
|
87
|
+
# @yields {|original, *arguments, **options, &block| ...} The wrapper implementation, receiving the original method as the first argument.
|
|
67
88
|
def wrap(method, &hook)
|
|
68
89
|
execution_context = Thread.current
|
|
69
90
|
|
|
@@ -81,13 +102,20 @@ module Sus
|
|
|
81
102
|
end
|
|
82
103
|
end
|
|
83
104
|
|
|
105
|
+
# Provides mock management functionality for test cases.
|
|
84
106
|
module Mocks
|
|
107
|
+
# Clean up all mocks after the test completes.
|
|
108
|
+
# @parameter error [Exception | Nil] The error that occurred, if any.
|
|
85
109
|
def after(error = nil)
|
|
86
110
|
super
|
|
87
111
|
|
|
88
112
|
@mocks&.each_value(&:clear)
|
|
89
113
|
end
|
|
90
114
|
|
|
115
|
+
# Create or access a mock for the given target.
|
|
116
|
+
# @parameter target [Object] The object to mock.
|
|
117
|
+
# @yields {|mock| ...} Optional block to configure the mock.
|
|
118
|
+
# @returns [Mock] The mock instance for the target.
|
|
91
119
|
def mock(target)
|
|
92
120
|
validate_mock!(target)
|
|
93
121
|
|
|
@@ -102,20 +130,30 @@ module Sus
|
|
|
102
130
|
|
|
103
131
|
private
|
|
104
132
|
|
|
133
|
+
# Error raised when attempting to mock a frozen object.
|
|
105
134
|
MockTargetError = Class.new(StandardError)
|
|
106
135
|
|
|
136
|
+
# Validate that the target can be mocked.
|
|
137
|
+
# @parameter target [Object] The object to validate.
|
|
138
|
+
# @raises [MockTargetError] If the target is frozen.
|
|
107
139
|
def validate_mock!(target)
|
|
108
140
|
if target.frozen?
|
|
109
141
|
raise MockTargetError, "Cannot mock frozen object #{target.inspect}!"
|
|
110
142
|
end
|
|
111
143
|
end
|
|
112
144
|
|
|
145
|
+
# Get the mocks hash, creating it if necessary.
|
|
146
|
+
# @returns [Hash] A hash mapping targets to their mock instances.
|
|
113
147
|
def mocks
|
|
114
148
|
@mocks ||= Hash.new{|h,k| h[k] = Mock.new(k)}.compare_by_identity
|
|
115
149
|
end
|
|
116
150
|
end
|
|
117
151
|
|
|
118
152
|
class Base
|
|
153
|
+
# Create or access a mock for the given target.
|
|
154
|
+
# @parameter target [Object] The object to mock.
|
|
155
|
+
# @yields {|mock| ...} Optional block to configure the mock.
|
|
156
|
+
# @returns [Mock] The mock instance for the target.
|
|
119
157
|
def mock(target, &block)
|
|
120
158
|
# Pull in the extra functionality:
|
|
121
159
|
self.singleton_class.prepend(Mocks)
|
data/lib/sus/output/backtrace.rb
CHANGED
|
@@ -5,21 +5,32 @@
|
|
|
5
5
|
|
|
6
6
|
module Sus
|
|
7
7
|
module Output
|
|
8
|
-
#
|
|
8
|
+
# Represents a backtrace for displaying error locations.
|
|
9
9
|
class Backtrace
|
|
10
|
+
# Create a backtrace from the first caller location.
|
|
11
|
+
# @parameter identity [Identity, nil] Optional identity to filter by path.
|
|
12
|
+
# @returns [Backtrace] A new Backtrace instance.
|
|
10
13
|
def self.first(identity = nil)
|
|
11
14
|
# This implementation could be a little more efficient.
|
|
12
15
|
self.new(caller_locations(1), identity&.path, 1)
|
|
13
16
|
end
|
|
14
17
|
|
|
18
|
+
# Create a backtrace from an exception.
|
|
19
|
+
# @parameter exception [Exception] The exception to extract the backtrace from.
|
|
20
|
+
# @parameter identity [Identity, nil] Optional identity to filter by path.
|
|
21
|
+
# @returns [Backtrace] A new Backtrace instance.
|
|
15
22
|
def self.for(exception, identity = nil)
|
|
16
23
|
# I've disabled the root filter here, because partial backtraces are not very useful.
|
|
17
24
|
# We might want to do something to improve presentation of the backtrace based on the root instead.
|
|
18
25
|
self.new(extract_stack(exception), identity&.path)
|
|
19
26
|
end
|
|
20
27
|
|
|
28
|
+
# Represents a location in a backtrace.
|
|
21
29
|
Location = Struct.new(:path, :lineno, :label)
|
|
22
30
|
|
|
31
|
+
# Extract the stack trace from an exception.
|
|
32
|
+
# @parameter exception [Exception] The exception to extract from.
|
|
33
|
+
# @returns [Array] An array of location objects.
|
|
23
34
|
def self.extract_stack(exception)
|
|
24
35
|
if stack = exception.backtrace_locations
|
|
25
36
|
return stack
|
|
@@ -32,16 +43,29 @@ module Sus
|
|
|
32
43
|
end
|
|
33
44
|
end
|
|
34
45
|
|
|
46
|
+
# Initialize a new Backtrace.
|
|
47
|
+
# @parameter stack [Array] The stack trace locations.
|
|
48
|
+
# @parameter root [String, nil] Optional root path to filter by.
|
|
49
|
+
# @parameter limit [Integer, nil] Optional limit on the number of frames.
|
|
35
50
|
def initialize(stack, root = nil, limit = nil)
|
|
36
51
|
@stack = stack
|
|
37
52
|
@root = root
|
|
38
53
|
@limit = limit
|
|
39
54
|
end
|
|
40
55
|
|
|
56
|
+
# @attribute [Array] The stack trace locations.
|
|
41
57
|
attr :stack
|
|
58
|
+
|
|
59
|
+
# @attribute [String, nil] The root path to filter by.
|
|
42
60
|
attr :root
|
|
61
|
+
|
|
62
|
+
# @attribute [Integer, nil] The limit on the number of frames.
|
|
43
63
|
attr :limit
|
|
44
64
|
|
|
65
|
+
# Filter the backtrace by root path and limit.
|
|
66
|
+
# @parameter root [String, nil] Optional root path to filter by.
|
|
67
|
+
# @parameter limit [Integer, nil] Optional limit on the number of frames.
|
|
68
|
+
# @returns [Array, Enumerator] The filtered stack trace.
|
|
45
69
|
def filter(root: @root, limit: @limit)
|
|
46
70
|
if root
|
|
47
71
|
if limit
|
|
@@ -60,6 +84,8 @@ module Sus
|
|
|
60
84
|
end
|
|
61
85
|
end
|
|
62
86
|
|
|
87
|
+
# Print the backtrace to the output.
|
|
88
|
+
# @parameter output [Output] The output handler.
|
|
63
89
|
def print(output)
|
|
64
90
|
if @limit == 1
|
|
65
91
|
filter.each do |frame|
|
|
@@ -74,6 +100,10 @@ module Sus
|
|
|
74
100
|
end
|
|
75
101
|
end
|
|
76
102
|
|
|
103
|
+
# Select items up to and matching a condition.
|
|
104
|
+
# @parameter things [Enumerable] The items to filter.
|
|
105
|
+
# @yields {|thing| ...} The condition to match.
|
|
106
|
+
# @returns [Array] The filtered items.
|
|
77
107
|
private def up_to_and_matching(things, &block)
|
|
78
108
|
preface = true
|
|
79
109
|
things.select do |thing|
|
data/lib/sus/output/bar.rb
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
module Sus
|
|
7
7
|
module Output
|
|
8
|
+
# Represents a progress bar for displaying test execution progress.
|
|
8
9
|
class Bar
|
|
10
|
+
# Unicode block characters for drawing the progress bar.
|
|
9
11
|
BLOCK = [
|
|
10
12
|
" ",
|
|
11
13
|
"▏",
|
|
@@ -18,6 +20,10 @@ module Sus
|
|
|
18
20
|
"█",
|
|
19
21
|
]
|
|
20
22
|
|
|
23
|
+
# Initialize a new progress bar.
|
|
24
|
+
# @parameter current [Integer] The current progress value.
|
|
25
|
+
# @parameter total [Integer] The total value.
|
|
26
|
+
# @parameter message [String, nil] Optional message to display.
|
|
21
27
|
def initialize(current = 0, total = 0, message = nil)
|
|
22
28
|
@maximum_message_width = 0
|
|
23
29
|
|
|
@@ -26,19 +32,30 @@ module Sus
|
|
|
26
32
|
@message = message
|
|
27
33
|
end
|
|
28
34
|
|
|
35
|
+
# Update the progress bar values.
|
|
36
|
+
# @parameter current [Integer] The current progress value.
|
|
37
|
+
# @parameter total [Integer] The total value.
|
|
38
|
+
# @parameter message [String, nil] Optional message to display.
|
|
29
39
|
def update(current, total, message)
|
|
30
40
|
@current = current
|
|
31
41
|
@total = total
|
|
32
42
|
@message = message
|
|
33
43
|
end
|
|
34
44
|
|
|
45
|
+
# Register progress bar styling with an output handler.
|
|
46
|
+
# @parameter output [Output] The output handler to register with.
|
|
35
47
|
def self.register(output)
|
|
36
48
|
output[:progress_bar] ||= output.style(:blue, :white)
|
|
37
49
|
end
|
|
38
50
|
|
|
51
|
+
# The minimum width for the progress bar.
|
|
39
52
|
MINIMUM_WIDTH = 8
|
|
53
|
+
|
|
54
|
+
# The suffix to append to messages.
|
|
40
55
|
MESSAGE_SUFFIX = ": "
|
|
41
56
|
|
|
57
|
+
# Print the progress bar to the output.
|
|
58
|
+
# @parameter output [Output] The output handler.
|
|
42
59
|
def print(output)
|
|
43
60
|
width = output.width
|
|
44
61
|
|
data/lib/sus/output/buffered.rb
CHANGED
|
@@ -7,17 +7,23 @@ require "io/console"
|
|
|
7
7
|
require "stringio"
|
|
8
8
|
|
|
9
9
|
module Sus
|
|
10
|
-
# Styled output output.
|
|
11
10
|
module Output
|
|
11
|
+
# Represents a buffered output handler that stores output operations for later replay.
|
|
12
12
|
class Buffered
|
|
13
|
+
# Initialize a new Buffered output handler.
|
|
14
|
+
# @parameter tee [Output, nil] Optional output handler to tee output to.
|
|
13
15
|
def initialize(tee = nil)
|
|
14
16
|
@chunks = Array.new
|
|
15
17
|
@tee = tee
|
|
16
18
|
end
|
|
17
19
|
|
|
20
|
+
# @attribute [Array] The stored output chunks.
|
|
18
21
|
attr :chunks
|
|
22
|
+
|
|
23
|
+
# @attribute [Output, nil] The output handler to tee to.
|
|
19
24
|
attr :tee
|
|
20
25
|
|
|
26
|
+
# @returns [String] A string representation of this buffered output.
|
|
21
27
|
def inspect
|
|
22
28
|
if @tee
|
|
23
29
|
"\#<#{self.class.name} #{@chunks.size} chunks -> #{@tee.class}>"
|
|
@@ -26,39 +32,52 @@ module Sus
|
|
|
26
32
|
end
|
|
27
33
|
end
|
|
28
34
|
|
|
35
|
+
# Create a nested buffered output handler.
|
|
36
|
+
# @returns [Buffered] A new Buffered instance that tees to this one.
|
|
29
37
|
def buffered
|
|
30
38
|
self.class.new(self)
|
|
31
39
|
end
|
|
32
40
|
|
|
41
|
+
# Iterate over stored chunks.
|
|
42
|
+
# @yields {|chunk| ...} Each stored chunk.
|
|
33
43
|
def each(&block)
|
|
34
44
|
@chunks.each(&block)
|
|
35
45
|
end
|
|
36
46
|
|
|
47
|
+
# Append chunks from another buffer.
|
|
48
|
+
# @parameter buffer [Buffered] The buffer to append from.
|
|
37
49
|
def append(buffer)
|
|
38
50
|
@chunks.concat(buffer.chunks)
|
|
39
51
|
@tee&.append(buffer)
|
|
40
52
|
end
|
|
41
53
|
|
|
54
|
+
# @returns [String] The buffered output as a string.
|
|
42
55
|
def string
|
|
43
56
|
io = StringIO.new
|
|
44
57
|
Text.new(io).append(@chunks)
|
|
45
58
|
return io.string
|
|
46
59
|
end
|
|
47
60
|
|
|
61
|
+
# The indent operation marker.
|
|
48
62
|
INDENT = [:indent].freeze
|
|
49
63
|
|
|
64
|
+
# Increase indentation level.
|
|
50
65
|
def indent
|
|
51
66
|
@chunks << INDENT
|
|
52
67
|
@tee&.indent
|
|
53
68
|
end
|
|
54
69
|
|
|
70
|
+
# The outdent operation marker.
|
|
55
71
|
OUTDENT = [:outdent].freeze
|
|
56
72
|
|
|
73
|
+
# Decrease indentation level.
|
|
57
74
|
def outdent
|
|
58
75
|
@chunks << OUTDENT
|
|
59
76
|
@tee&.outdent
|
|
60
77
|
end
|
|
61
78
|
|
|
79
|
+
# Execute a block with increased indentation.
|
|
80
|
+
# @yields {...} The block to execute.
|
|
62
81
|
def indented
|
|
63
82
|
self.indent
|
|
64
83
|
yield
|
|
@@ -66,31 +85,43 @@ module Sus
|
|
|
66
85
|
self.outdent
|
|
67
86
|
end
|
|
68
87
|
|
|
88
|
+
# Write output.
|
|
89
|
+
# @parameter arguments [Array] The arguments to write.
|
|
69
90
|
def write(*arguments)
|
|
70
91
|
@chunks << [:write, *arguments]
|
|
71
92
|
@tee&.write(*arguments)
|
|
72
93
|
end
|
|
73
94
|
|
|
95
|
+
# Write output followed by a newline.
|
|
96
|
+
# @parameter arguments [Array] The arguments to write.
|
|
74
97
|
def puts(*arguments)
|
|
75
98
|
@chunks << [:puts, *arguments]
|
|
76
99
|
@tee&.puts(*arguments)
|
|
77
100
|
end
|
|
78
101
|
|
|
102
|
+
# Record an assertion.
|
|
103
|
+
# @parameter arguments [Array] The assertion arguments.
|
|
79
104
|
def assert(*arguments)
|
|
80
105
|
@chunks << [:assert, *arguments]
|
|
81
106
|
@tee&.assert(*arguments)
|
|
82
107
|
end
|
|
83
108
|
|
|
109
|
+
# Record a skip.
|
|
110
|
+
# @parameter arguments [Array] The skip arguments.
|
|
84
111
|
def skip(*arguments)
|
|
85
112
|
@chunks << [:skip, *arguments]
|
|
86
113
|
@tee&.skip(*arguments)
|
|
87
114
|
end
|
|
88
115
|
|
|
116
|
+
# Record an error.
|
|
117
|
+
# @parameter arguments [Array] The error arguments.
|
|
89
118
|
def error(*arguments)
|
|
90
119
|
@chunks << [:error, *arguments]
|
|
91
120
|
@tee&.error(*arguments)
|
|
92
121
|
end
|
|
93
122
|
|
|
123
|
+
# Record an informational message.
|
|
124
|
+
# @parameter arguments [Array] The message arguments.
|
|
94
125
|
def inform(*arguments)
|
|
95
126
|
@chunks << [:inform, *arguments]
|
|
96
127
|
@tee&.inform(*arguments)
|
data/lib/sus/output/lines.rb
CHANGED
|
@@ -7,7 +7,10 @@ require "io/console"
|
|
|
7
7
|
|
|
8
8
|
module Sus
|
|
9
9
|
module Output
|
|
10
|
+
# Represents a line buffer for managing multiple lines of output on a terminal.
|
|
10
11
|
class Lines
|
|
12
|
+
# Initialize a new Lines buffer.
|
|
13
|
+
# @parameter output [Output] The output handler to write to.
|
|
11
14
|
def initialize(output)
|
|
12
15
|
@output = output
|
|
13
16
|
@lines = []
|
|
@@ -15,21 +18,28 @@ module Sus
|
|
|
15
18
|
@current_count = 0
|
|
16
19
|
end
|
|
17
20
|
|
|
21
|
+
# @returns [Integer] The height of the terminal.
|
|
18
22
|
def height
|
|
19
23
|
@output.size.first
|
|
20
24
|
end
|
|
21
25
|
|
|
26
|
+
# Set a line at the given index.
|
|
27
|
+
# @parameter index [Integer] The line index.
|
|
28
|
+
# @parameter line [Object] The line content (should respond to #print).
|
|
22
29
|
def []= index, line
|
|
23
30
|
@lines[index] = line
|
|
24
31
|
|
|
25
32
|
redraw(index)
|
|
26
33
|
end
|
|
27
34
|
|
|
35
|
+
# Clear all lines.
|
|
28
36
|
def clear
|
|
29
37
|
@lines.clear
|
|
30
38
|
write
|
|
31
39
|
end
|
|
32
40
|
|
|
41
|
+
# Redraw a specific line or all lines.
|
|
42
|
+
# @parameter index [Integer] The line index to redraw.
|
|
33
43
|
def redraw(index)
|
|
34
44
|
if index < @current_count
|
|
35
45
|
update(index, @lines[index])
|
data/lib/sus/output/messages.rb
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2025, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
module Sus
|
|
7
|
-
# Styled output output.
|
|
8
7
|
module Output
|
|
8
|
+
# Provides message formatting methods for output handlers.
|
|
9
9
|
module Messages
|
|
10
|
+
# The prefix for passed assertions.
|
|
10
11
|
PASSED_PREFIX = [:passed, "✓ "].freeze
|
|
12
|
+
|
|
13
|
+
# The prefix for failed assertions.
|
|
11
14
|
FAILED_PREFIX = [:failed, "✗ "].freeze
|
|
12
15
|
|
|
16
|
+
# Get the prefix for a passed assertion based on orientation.
|
|
17
|
+
# @parameter orientation [Boolean] The orientation of the assertions.
|
|
18
|
+
# @returns [Array] The prefix array.
|
|
13
19
|
def pass_prefix(orientation)
|
|
14
20
|
if orientation
|
|
15
21
|
PASSED_PREFIX
|
|
@@ -18,6 +24,9 @@ module Sus
|
|
|
18
24
|
end
|
|
19
25
|
end
|
|
20
26
|
|
|
27
|
+
# Get the prefix for a failed assertion based on orientation.
|
|
28
|
+
# @parameter orientation [Boolean] The orientation of the assertions.
|
|
29
|
+
# @returns [Array] The prefix array.
|
|
21
30
|
def fail_prefix(orientation)
|
|
22
31
|
if orientation
|
|
23
32
|
FAILED_PREFIX
|
|
@@ -26,6 +35,7 @@ module Sus
|
|
|
26
35
|
end
|
|
27
36
|
end
|
|
28
37
|
|
|
38
|
+
# Print an assertion result.
|
|
29
39
|
# If the orientation is true, and the test passed, then it is a successful outcome.
|
|
30
40
|
# If the orientation is false, and the test failed, then it is a successful outcome.
|
|
31
41
|
# Otherwise, it is a failed outcome.
|
|
@@ -33,7 +43,7 @@ module Sus
|
|
|
33
43
|
# @parameter condition [Boolean] The result of the test.
|
|
34
44
|
# @parameter orientation [Boolean] The orientation of the assertions.
|
|
35
45
|
# @parameter message [String] The message to display.
|
|
36
|
-
# @parameter backtrace [
|
|
46
|
+
# @parameter backtrace [Backtrace] The backtrace to display.
|
|
37
47
|
def assert(condition, orientation, message, backtrace)
|
|
38
48
|
if condition
|
|
39
49
|
self.puts(:indent, *pass_prefix(orientation), message, backtrace)
|
|
@@ -42,18 +52,27 @@ module Sus
|
|
|
42
52
|
end
|
|
43
53
|
end
|
|
44
54
|
|
|
55
|
+
# @returns [String] The prefix for skipped tests.
|
|
45
56
|
def skip_prefix
|
|
46
57
|
"⏸ "
|
|
47
58
|
end
|
|
48
59
|
|
|
60
|
+
# Print a skip message.
|
|
61
|
+
# @parameter reason [String] The reason for skipping.
|
|
62
|
+
# @parameter identity [Identity, nil] The identity where the skip occurred.
|
|
49
63
|
def skip(reason, identity)
|
|
50
64
|
self.puts(:indent, :skipped, skip_prefix, reason)
|
|
51
65
|
end
|
|
52
66
|
|
|
67
|
+
# @returns [Array] The prefix for error messages.
|
|
53
68
|
def error_prefix
|
|
54
69
|
[:errored, "⚠ "]
|
|
55
70
|
end
|
|
56
71
|
|
|
72
|
+
# Print an error message.
|
|
73
|
+
# @parameter error [Exception] The error to display.
|
|
74
|
+
# @parameter identity [Identity, nil] The identity where the error occurred.
|
|
75
|
+
# @parameter prefix [Array] Optional prefix to use.
|
|
57
76
|
def error(error, identity, prefix = error_prefix)
|
|
58
77
|
lines = error.message.split(/\r?\n/)
|
|
59
78
|
|
|
@@ -70,10 +89,14 @@ module Sus
|
|
|
70
89
|
end
|
|
71
90
|
end
|
|
72
91
|
|
|
92
|
+
# @returns [String] The prefix for informational messages.
|
|
73
93
|
def inform_prefix
|
|
74
94
|
"ℹ "
|
|
75
95
|
end
|
|
76
96
|
|
|
97
|
+
# Print an informational message.
|
|
98
|
+
# @parameter message [String] The message to display.
|
|
99
|
+
# @parameter identity [Identity, nil] The identity where the message was generated.
|
|
77
100
|
def inform(message, identity)
|
|
78
101
|
self.puts(:indent, :inform, inform_prefix, message)
|
|
79
102
|
end
|
data/lib/sus/output/null.rb
CHANGED
|
@@ -1,42 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2021-
|
|
4
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "messages"
|
|
7
7
|
|
|
8
8
|
module Sus
|
|
9
|
-
# Styled output output.
|
|
10
9
|
module Output
|
|
10
|
+
# Represents a null output handler that discards all output.
|
|
11
11
|
class Null
|
|
12
12
|
include Messages
|
|
13
13
|
|
|
14
|
+
# Initialize a new Null output handler.
|
|
14
15
|
def initialize
|
|
15
16
|
end
|
|
16
17
|
|
|
18
|
+
# Create a buffered output handler.
|
|
19
|
+
# @returns [Buffered] A new Buffered instance.
|
|
17
20
|
def buffered
|
|
18
21
|
Buffered.new(nil)
|
|
19
22
|
end
|
|
20
23
|
|
|
24
|
+
# @attribute [Hash, nil] Optional options (unused).
|
|
21
25
|
attr :options
|
|
22
26
|
|
|
27
|
+
# Append chunks from a buffer (no-op).
|
|
28
|
+
# @parameter buffer [Buffered] The buffer to append from.
|
|
23
29
|
def append(buffer)
|
|
24
30
|
end
|
|
25
31
|
|
|
32
|
+
# Increase indentation (no-op).
|
|
26
33
|
def indent
|
|
27
34
|
end
|
|
28
35
|
|
|
36
|
+
# Decrease indentation (no-op).
|
|
29
37
|
def outdent
|
|
30
38
|
end
|
|
31
39
|
|
|
40
|
+
# Execute a block with indentation (no-op, just yields).
|
|
41
|
+
# @yields {...} The block to execute.
|
|
32
42
|
def indented
|
|
33
43
|
yield
|
|
34
44
|
end
|
|
35
45
|
|
|
46
|
+
# Write output (no-op).
|
|
47
|
+
# @parameter arguments [Array] The arguments to write.
|
|
36
48
|
def write(*arguments)
|
|
37
49
|
# Do nothing.
|
|
38
50
|
end
|
|
39
51
|
|
|
52
|
+
# Write output followed by a newline (no-op).
|
|
53
|
+
# @parameter arguments [Array] The arguments to write.
|
|
40
54
|
def puts(*arguments)
|
|
41
55
|
# Do nothing.
|
|
42
56
|
end
|
data/lib/sus/output/progress.rb
CHANGED
|
@@ -9,11 +9,18 @@ require_relative "lines"
|
|
|
9
9
|
|
|
10
10
|
module Sus
|
|
11
11
|
module Output
|
|
12
|
+
# Represents a progress tracker for test execution.
|
|
12
13
|
class Progress
|
|
14
|
+
# Get the current monotonic time.
|
|
15
|
+
# @returns [Float] The current time in seconds.
|
|
13
16
|
def self.now
|
|
14
17
|
::Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
15
18
|
end
|
|
16
19
|
|
|
20
|
+
# Initialize a new Progress tracker.
|
|
21
|
+
# @parameter output [Output] The output handler.
|
|
22
|
+
# @parameter total [Integer] The total number of items to track.
|
|
23
|
+
# @parameter minimum_output_duration [Float] Minimum duration before showing output (unused).
|
|
17
24
|
def initialize(output, total = 0, minimum_output_duration: 1.0)
|
|
18
25
|
@output = output
|
|
19
26
|
@subject = subject
|
|
@@ -30,35 +37,47 @@ module Sus
|
|
|
30
37
|
@total = total
|
|
31
38
|
end
|
|
32
39
|
|
|
40
|
+
# @attribute [Object, nil] The subject being tracked.
|
|
33
41
|
attr :subject
|
|
42
|
+
|
|
43
|
+
# @attribute [Integer] The current progress value.
|
|
34
44
|
attr :current
|
|
45
|
+
|
|
46
|
+
# @attribute [Integer] The total value.
|
|
35
47
|
attr :total
|
|
36
48
|
|
|
49
|
+
# @returns [Float] The elapsed duration in seconds.
|
|
37
50
|
def duration
|
|
38
51
|
Progress.now - @start_time
|
|
39
52
|
end
|
|
40
53
|
|
|
54
|
+
# @returns [Float] The progress as a fraction (0.0 to 1.0).
|
|
41
55
|
def progress
|
|
42
56
|
@current.to_f / @total.to_f
|
|
43
57
|
end
|
|
44
58
|
|
|
59
|
+
# @returns [Integer] The remaining items to process.
|
|
45
60
|
def remaining
|
|
46
61
|
@total - @current
|
|
47
62
|
end
|
|
48
63
|
|
|
64
|
+
# @returns [Float, nil] The average duration per item, or nil if no items completed.
|
|
49
65
|
def average_duration
|
|
50
66
|
if @current > 0
|
|
51
67
|
duration / @current
|
|
52
68
|
end
|
|
53
69
|
end
|
|
54
70
|
|
|
71
|
+
# @returns [Float, nil] The estimated remaining time, or nil if cannot be calculated.
|
|
55
72
|
def estimated_remaining_time
|
|
56
73
|
if average_duration = self.average_duration
|
|
57
74
|
average_duration * remaining
|
|
58
75
|
end
|
|
59
76
|
end
|
|
60
77
|
|
|
61
|
-
# Increase the
|
|
78
|
+
# Increase the amount of work done.
|
|
79
|
+
# @parameter amount [Integer] The amount to increment by.
|
|
80
|
+
# @returns [Progress] Returns self for method chaining.
|
|
62
81
|
def increment(amount = 1)
|
|
63
82
|
@current += amount
|
|
64
83
|
|
|
@@ -69,6 +88,8 @@ module Sus
|
|
|
69
88
|
end
|
|
70
89
|
|
|
71
90
|
# Increase the total size of the progress.
|
|
91
|
+
# @parameter amount [Integer] The amount to expand by.
|
|
92
|
+
# @returns [Progress] Returns self for method chaining.
|
|
72
93
|
def expand(amount = 1)
|
|
73
94
|
@total += amount
|
|
74
95
|
|
|
@@ -78,16 +99,23 @@ module Sus
|
|
|
78
99
|
return self
|
|
79
100
|
end
|
|
80
101
|
|
|
102
|
+
# Report the status of a specific item.
|
|
103
|
+
# @parameter index [Integer] The index of the item.
|
|
104
|
+
# @parameter context [Object] The context to display.
|
|
105
|
+
# @parameter state [Symbol] The state (:free or :busy).
|
|
106
|
+
# @returns [Progress] Returns self for method chaining.
|
|
81
107
|
def report(index, context, state)
|
|
82
108
|
@lines&.[]=(index+1, Status.new(state, context))
|
|
83
109
|
|
|
84
110
|
return self
|
|
85
111
|
end
|
|
86
112
|
|
|
113
|
+
# Clear the progress display.
|
|
87
114
|
def clear
|
|
88
115
|
@lines&.clear
|
|
89
116
|
end
|
|
90
117
|
|
|
118
|
+
# @returns [String] A string representation of the progress.
|
|
91
119
|
def to_s
|
|
92
120
|
if estimated_remaining_time = self.estimated_remaining_time
|
|
93
121
|
"#{@current}/#{@total} completed in #{formatted_duration(self.duration)}, #{formatted_duration(estimated_remaining_time)} remaining"
|