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/filter.rb
CHANGED
|
@@ -12,13 +12,18 @@ module Sus
|
|
|
12
12
|
#
|
|
13
13
|
# When the filter is used to enumerate the registry, it will only return the tests that match the suffix.
|
|
14
14
|
class Filter
|
|
15
|
+
# Represents an index of contexts by their identity keys.
|
|
15
16
|
class Index
|
|
17
|
+
# Initialize a new Index.
|
|
16
18
|
def initialize
|
|
17
19
|
@contexts = {}
|
|
18
20
|
end
|
|
19
21
|
|
|
22
|
+
# @attribute [Hash] A hash mapping identity keys to contexts.
|
|
20
23
|
attr :contexts
|
|
21
24
|
|
|
25
|
+
# Add all children from a parent context to the index.
|
|
26
|
+
# @parameter parent [Object] The parent context.
|
|
22
27
|
def add(parent)
|
|
23
28
|
parent.children&.each do |identity, child|
|
|
24
29
|
insert(identity, child)
|
|
@@ -26,6 +31,10 @@ module Sus
|
|
|
26
31
|
end
|
|
27
32
|
end
|
|
28
33
|
|
|
34
|
+
# Insert a context into the index.
|
|
35
|
+
# @parameter identity [Identity] The identity of the context.
|
|
36
|
+
# @parameter context [Object] The context to index.
|
|
37
|
+
# @raises [KeyError] If a context with the same key already exists.
|
|
29
38
|
def insert(identity, context)
|
|
30
39
|
key = identity.key
|
|
31
40
|
|
|
@@ -36,17 +45,24 @@ module Sus
|
|
|
36
45
|
end
|
|
37
46
|
end
|
|
38
47
|
|
|
48
|
+
# Look up a context by its key.
|
|
49
|
+
# @parameter key [String] The identity key.
|
|
50
|
+
# @returns [Object, nil] The context if found.
|
|
39
51
|
def [] key
|
|
40
52
|
@contexts[key]
|
|
41
53
|
end
|
|
42
54
|
end
|
|
43
55
|
|
|
56
|
+
# Initialize a new Filter.
|
|
57
|
+
# @parameter registry [Registry] The registry to filter.
|
|
44
58
|
def initialize(registry = Registry.new)
|
|
45
59
|
@registry = registry
|
|
46
60
|
@index = nil
|
|
47
61
|
@keys = Array.new
|
|
48
62
|
end
|
|
49
63
|
|
|
64
|
+
# Load a target path, optionally with a filter suffix.
|
|
65
|
+
# @parameter target [String] The target path, optionally with a ":suffix" filter.
|
|
50
66
|
def load(target)
|
|
51
67
|
path, filter = target.split(":", 2)
|
|
52
68
|
|
|
@@ -57,6 +73,8 @@ module Sus
|
|
|
57
73
|
end
|
|
58
74
|
end
|
|
59
75
|
|
|
76
|
+
# Iterate over filtered test cases.
|
|
77
|
+
# @yields {|test| ...} Each test case that matches the filter.
|
|
60
78
|
def each(&block)
|
|
61
79
|
if @keys.any?
|
|
62
80
|
@index = Index.new
|
|
@@ -72,6 +90,9 @@ module Sus
|
|
|
72
90
|
end
|
|
73
91
|
end
|
|
74
92
|
|
|
93
|
+
# Execute filtered tests.
|
|
94
|
+
# @parameter assertions [Assertions] Optional assertions instance to use.
|
|
95
|
+
# @returns [Assertions] The assertions instance with results.
|
|
75
96
|
def call(assertions = Assertions.default)
|
|
76
97
|
if @keys.any?
|
|
77
98
|
@index = Index.new
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
|
|
8
|
+
module Sus
|
|
9
|
+
module Fixtures
|
|
10
|
+
# Provides a temporary directory context for tests that need isolated file system access.
|
|
11
|
+
module TemporaryDirectoryContext
|
|
12
|
+
# Set up a temporary directory before the test and clean it up after.
|
|
13
|
+
# @yields {|&block| ...} The test block to execute.
|
|
14
|
+
def around(&block)
|
|
15
|
+
Dir.mktmpdir do |root|
|
|
16
|
+
@root = root
|
|
17
|
+
super(&block)
|
|
18
|
+
@root = nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @attribute [String] The path to the temporary directory root.
|
|
23
|
+
attr :root
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
data/lib/sus/fixtures.rb
CHANGED
data/lib/sus/have/all.rb
CHANGED
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
module Sus
|
|
7
7
|
module Have
|
|
8
|
+
# Represents a predicate that checks if the subject matches all of the given predicates.
|
|
8
9
|
class All
|
|
10
|
+
# Initialize a new All predicate.
|
|
11
|
+
# @parameter predicates [Array] The predicates to check.
|
|
9
12
|
def initialize(predicates)
|
|
10
13
|
@predicates = predicates
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
# Print a representation of this predicate.
|
|
17
|
+
# @parameter output [Output] The output target.
|
|
13
18
|
def print(output)
|
|
14
19
|
first = true
|
|
15
20
|
output.write("have {")
|
|
@@ -25,6 +30,9 @@ module Sus
|
|
|
25
30
|
output.write("}")
|
|
26
31
|
end
|
|
27
32
|
|
|
33
|
+
# Evaluate this predicate against a subject.
|
|
34
|
+
# @parameter assertions [Assertions] The assertions instance to use.
|
|
35
|
+
# @parameter subject [Object] The subject to evaluate.
|
|
28
36
|
def call(assertions, subject)
|
|
29
37
|
assertions.nested(self) do |assertions|
|
|
30
38
|
@predicates.each do |predicate|
|
data/lib/sus/have/any.rb
CHANGED
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
module Sus
|
|
7
7
|
module Have
|
|
8
|
+
# Represents a predicate that checks if the subject matches any of the given predicates.
|
|
8
9
|
class Any
|
|
10
|
+
# Initialize a new Any predicate.
|
|
11
|
+
# @parameter predicates [Array] The predicates to check.
|
|
9
12
|
def initialize(predicates)
|
|
10
13
|
@predicates = predicates
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
# Print a representation of this predicate.
|
|
17
|
+
# @parameter output [Output] The output target.
|
|
13
18
|
def print(output)
|
|
14
19
|
first = true
|
|
15
20
|
output.write("have any {")
|
|
@@ -25,6 +30,9 @@ module Sus
|
|
|
25
30
|
output.write("}")
|
|
26
31
|
end
|
|
27
32
|
|
|
33
|
+
# Evaluate this predicate against a subject.
|
|
34
|
+
# @parameter assertions [Assertions] The assertions instance to use.
|
|
35
|
+
# @parameter subject [Object] The subject to evaluate.
|
|
28
36
|
def call(assertions, subject)
|
|
29
37
|
assertions.nested(self) do |assertions|
|
|
30
38
|
@predicates.each do |predicate|
|
data/lib/sus/have.rb
CHANGED
|
@@ -7,17 +7,27 @@ require_relative "have/all"
|
|
|
7
7
|
require_relative "have/any"
|
|
8
8
|
|
|
9
9
|
module Sus
|
|
10
|
+
# Represents predicates for checking collections and object attributes.
|
|
10
11
|
module Have
|
|
12
|
+
# Represents a predicate that checks if a hash has a specific key.
|
|
11
13
|
class Key
|
|
14
|
+
# Initialize a new Key predicate.
|
|
15
|
+
# @parameter name [Object] The key name to check for.
|
|
16
|
+
# @parameter predicate [Object, nil] Optional predicate to apply to the key's value.
|
|
12
17
|
def initialize(name, predicate = nil)
|
|
13
18
|
@name = name
|
|
14
19
|
@predicate = predicate
|
|
15
20
|
end
|
|
16
21
|
|
|
22
|
+
# Print a representation of this predicate.
|
|
23
|
+
# @parameter output [Output] The output target.
|
|
17
24
|
def print(output)
|
|
18
25
|
output.write("key ", :variable, @name.inspect, :reset, " ", @predicate, :reset)
|
|
19
26
|
end
|
|
20
27
|
|
|
28
|
+
# Evaluate this predicate against a subject.
|
|
29
|
+
# @parameter assertions [Assertions] The assertions instance to use.
|
|
30
|
+
# @parameter subject [Object] The subject to evaluate (should be a hash).
|
|
21
31
|
def call(assertions, subject)
|
|
22
32
|
# We want to group all the assertions in to a distinct group:
|
|
23
33
|
assertions.nested(self, distinct: true) do |assertions|
|
|
@@ -29,16 +39,25 @@ module Sus
|
|
|
29
39
|
end
|
|
30
40
|
end
|
|
31
41
|
|
|
42
|
+
# Represents a predicate that checks if an object has a specific attribute.
|
|
32
43
|
class Attribute
|
|
44
|
+
# Initialize a new Attribute predicate.
|
|
45
|
+
# @parameter name [Symbol, String] The attribute name to check for.
|
|
46
|
+
# @parameter predicate [Object] The predicate to apply to the attribute's value.
|
|
33
47
|
def initialize(name, predicate)
|
|
34
48
|
@name = name
|
|
35
49
|
@predicate = predicate
|
|
36
50
|
end
|
|
37
51
|
|
|
52
|
+
# Print a representation of this predicate.
|
|
53
|
+
# @parameter output [Output] The output target.
|
|
38
54
|
def print(output)
|
|
39
55
|
output.write("attribute ", :variable, @name.to_s, :reset, " ", @predicate, :reset)
|
|
40
56
|
end
|
|
41
57
|
|
|
58
|
+
# Evaluate this predicate against a subject.
|
|
59
|
+
# @parameter assertions [Assertions] The assertions instance to use.
|
|
60
|
+
# @parameter subject [Object] The subject to evaluate.
|
|
42
61
|
def call(assertions, subject)
|
|
43
62
|
assertions.nested(self, distinct: true) do |assertions|
|
|
44
63
|
assertions.assert(subject.respond_to?(@name), "has attribute")
|
|
@@ -49,15 +68,23 @@ module Sus
|
|
|
49
68
|
end
|
|
50
69
|
end
|
|
51
70
|
|
|
71
|
+
# Represents a predicate that checks if a collection has a value matching a predicate.
|
|
52
72
|
class Value
|
|
73
|
+
# Initialize a new Value predicate.
|
|
74
|
+
# @parameter predicate [Object, nil] The predicate to apply to each value in the collection.
|
|
53
75
|
def initialize(predicate)
|
|
54
76
|
@predicate = predicate
|
|
55
77
|
end
|
|
56
78
|
|
|
79
|
+
# Print a representation of this predicate.
|
|
80
|
+
# @parameter output [Output] The output target.
|
|
57
81
|
def print(output)
|
|
58
82
|
output.write("value ", @predicate, :reset)
|
|
59
83
|
end
|
|
60
84
|
|
|
85
|
+
# Evaluate this predicate against a subject.
|
|
86
|
+
# @parameter assertions [Assertions] The assertions instance to use.
|
|
87
|
+
# @parameter subject [Object] The subject to evaluate (should be enumerable).
|
|
61
88
|
def call(assertions, subject)
|
|
62
89
|
index = 0
|
|
63
90
|
|
|
@@ -73,10 +100,16 @@ module Sus
|
|
|
73
100
|
end
|
|
74
101
|
|
|
75
102
|
class Base
|
|
103
|
+
# Create a predicate that checks if the subject has all of the given predicates.
|
|
104
|
+
# @parameter predicates [Array] The predicates to check.
|
|
105
|
+
# @returns [Have::All] A Have::All predicate.
|
|
76
106
|
def have(*predicates)
|
|
77
107
|
Have::All.new(predicates)
|
|
78
108
|
end
|
|
79
109
|
|
|
110
|
+
# Create a predicate that checks if the subject (hash) has the specified keys.
|
|
111
|
+
# @parameter keys [Array] Keys to check for. Can be symbols/strings or hashes with key-predicate pairs.
|
|
112
|
+
# @returns [Have::All] A Have::All predicate.
|
|
80
113
|
def have_keys(*keys)
|
|
81
114
|
predicates = []
|
|
82
115
|
|
|
@@ -93,6 +126,9 @@ module Sus
|
|
|
93
126
|
Have::All.new(predicates)
|
|
94
127
|
end
|
|
95
128
|
|
|
129
|
+
# Create a predicate that checks if the subject has the specified attributes with matching values.
|
|
130
|
+
# @parameter attributes [Hash] A hash of attribute names to predicates.
|
|
131
|
+
# @returns [Have::All] A Have::All predicate.
|
|
96
132
|
def have_attributes(**attributes)
|
|
97
133
|
predicates = attributes.map do |key, value|
|
|
98
134
|
Have::Attribute.new(key, value)
|
|
@@ -101,10 +137,16 @@ module Sus
|
|
|
101
137
|
Have::All.new(predicates)
|
|
102
138
|
end
|
|
103
139
|
|
|
140
|
+
# Create a predicate that checks if the subject matches any of the given predicates.
|
|
141
|
+
# @parameter predicates [Array] The predicates to check.
|
|
142
|
+
# @returns [Have::Any] A Have::Any predicate.
|
|
104
143
|
def have_any(*predicates)
|
|
105
144
|
Have::Any.new(predicates)
|
|
106
145
|
end
|
|
107
146
|
|
|
147
|
+
# Create a predicate that checks if the subject (collection) has any value matching the predicate.
|
|
148
|
+
# @parameter predicate [Object] The predicate to apply to each value.
|
|
149
|
+
# @returns [Have::Any] A Have::Any predicate.
|
|
108
150
|
def have_value(predicate)
|
|
109
151
|
Have::Any.new([Have::Value.new(predicate)])
|
|
110
152
|
end
|
data/lib/sus/have_duration.rb
CHANGED
|
@@ -4,16 +4,24 @@
|
|
|
4
4
|
# Copyright, 2021-2024, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
module Sus
|
|
7
|
+
# Represents a predicate that measures the duration of a block execution.
|
|
7
8
|
class HaveDuration
|
|
9
|
+
# Initialize a new HaveDuration predicate.
|
|
10
|
+
# @parameter predicate [Object] The predicate to apply to the measured duration.
|
|
8
11
|
def initialize(predicate)
|
|
9
12
|
@predicate = predicate
|
|
10
13
|
end
|
|
11
14
|
|
|
15
|
+
# Print a representation of this predicate.
|
|
16
|
+
# @parameter output [Output] The output target.
|
|
12
17
|
def print(output)
|
|
13
18
|
output.write("have duration ")
|
|
14
19
|
@predicate.print(output)
|
|
15
20
|
end
|
|
16
21
|
|
|
22
|
+
# Evaluate this predicate against a subject (block).
|
|
23
|
+
# @parameter assertions [Assertions] The assertions instance to use.
|
|
24
|
+
# @parameter subject [Proc] The block to measure.
|
|
17
25
|
def call(assertions, subject)
|
|
18
26
|
assertions.nested(self) do |assertions|
|
|
19
27
|
Expect.new(assertions, measure(subject)).to(@predicate)
|
|
@@ -22,6 +30,9 @@ module Sus
|
|
|
22
30
|
|
|
23
31
|
private
|
|
24
32
|
|
|
33
|
+
# Measure the duration of executing a block.
|
|
34
|
+
# @parameter subject [Proc] The block to measure.
|
|
35
|
+
# @returns [Float] The duration in seconds.
|
|
25
36
|
def measure(subject)
|
|
26
37
|
start_time = now
|
|
27
38
|
|
|
@@ -30,12 +41,17 @@ module Sus
|
|
|
30
41
|
return now - start_time
|
|
31
42
|
end
|
|
32
43
|
|
|
44
|
+
# Get the current monotonic time.
|
|
45
|
+
# @returns [Float] The current time in seconds.
|
|
33
46
|
def now
|
|
34
47
|
::Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
35
48
|
end
|
|
36
49
|
end
|
|
37
50
|
|
|
38
51
|
class Base
|
|
52
|
+
# Create a predicate that measures the duration of a block execution.
|
|
53
|
+
# @parameter predicate [Object] The predicate to apply to the measured duration.
|
|
54
|
+
# @returns [HaveDuration] A new HaveDuration predicate.
|
|
39
55
|
def have_duration(...)
|
|
40
56
|
HaveDuration.new(...)
|
|
41
57
|
end
|
data/lib/sus/identity.rb
CHANGED
|
@@ -4,22 +4,42 @@
|
|
|
4
4
|
# Copyright, 2021-2024, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
module Sus
|
|
7
|
+
# Represents a unique identity for a test or context, used for identification and location tracking.
|
|
7
8
|
class Identity
|
|
9
|
+
# Create an identity for a file.
|
|
10
|
+
# @parameter parent [Identity, nil] The parent identity.
|
|
11
|
+
# @parameter path [String] The file path.
|
|
12
|
+
# @parameter name [String] The name (defaults to path).
|
|
13
|
+
# @parameter options [Hash] Additional options.
|
|
14
|
+
# @returns [Identity] A new Identity instance.
|
|
8
15
|
def self.file(parent, path, name = path, **options)
|
|
9
16
|
self.new(path, name, nil, nil, **options)
|
|
10
17
|
end
|
|
11
18
|
|
|
19
|
+
# Create a nested identity.
|
|
20
|
+
# @parameter parent [Identity, nil] The parent identity.
|
|
21
|
+
# @parameter name [String] The name of this identity.
|
|
22
|
+
# @parameter location [Thread::Backtrace::Location, nil] Optional location (defaults to caller location).
|
|
23
|
+
# @parameter options [Hash] Additional options.
|
|
24
|
+
# @returns [Identity] A new Identity instance.
|
|
12
25
|
def self.nested(parent, name, location = nil, **options)
|
|
13
26
|
location ||= caller_locations(3...4).first
|
|
14
27
|
|
|
15
28
|
self.new(location.path, name, location.lineno, parent, **options)
|
|
16
29
|
end
|
|
17
30
|
|
|
31
|
+
# Create an identity for the current location.
|
|
32
|
+
# @returns [Identity] A new Identity instance for the current caller location.
|
|
18
33
|
def self.current
|
|
19
34
|
self.nested(nil, nil, caller_locations(1...2).first)
|
|
20
35
|
end
|
|
21
36
|
|
|
22
|
-
#
|
|
37
|
+
# Initialize a new Identity.
|
|
38
|
+
# @parameter path [String] The file path.
|
|
39
|
+
# @parameter name [String, nil] Optional name.
|
|
40
|
+
# @parameter line [Integer, nil] Optional line number.
|
|
41
|
+
# @parameter parent [Identity, nil] Optional parent identity.
|
|
42
|
+
# @parameter unique [Boolean, Symbol] Whether this identity is unique or needs a unique key/line number suffix.
|
|
23
43
|
def initialize(path, name = nil, line = nil, parent = nil, unique: true)
|
|
24
44
|
@path = path
|
|
25
45
|
@name = name
|
|
@@ -30,20 +50,34 @@ module Sus
|
|
|
30
50
|
@key = nil
|
|
31
51
|
end
|
|
32
52
|
|
|
53
|
+
# Create a new identity with a different line number.
|
|
54
|
+
# @parameter line [Integer] The line number.
|
|
55
|
+
# @returns [Identity] A new Identity instance.
|
|
33
56
|
def with_line(line)
|
|
34
57
|
self.class.new(@path, @name, line, @parent, unique: @unique)
|
|
35
58
|
end
|
|
36
59
|
|
|
60
|
+
# @attribute [String] The file path.
|
|
37
61
|
attr :path
|
|
62
|
+
|
|
63
|
+
# @attribute [String, nil] The name.
|
|
38
64
|
attr :name
|
|
65
|
+
|
|
66
|
+
# @attribute [Integer, nil] The line number.
|
|
39
67
|
attr :line
|
|
68
|
+
|
|
69
|
+
# @attribute [Identity, nil] The parent identity.
|
|
40
70
|
attr :parent
|
|
71
|
+
|
|
72
|
+
# @attribute [Boolean, Symbol] Whether this identity is unique.
|
|
41
73
|
attr :unique
|
|
42
74
|
|
|
75
|
+
# @returns [String] A string representation of this identity (the key).
|
|
43
76
|
def to_s
|
|
44
77
|
self.key
|
|
45
78
|
end
|
|
46
79
|
|
|
80
|
+
# @returns [Hash] A hash containing the path and line number.
|
|
47
81
|
def to_location
|
|
48
82
|
{
|
|
49
83
|
path: ::File.expand_path(@path),
|
|
@@ -51,10 +85,14 @@ module Sus
|
|
|
51
85
|
}
|
|
52
86
|
end
|
|
53
87
|
|
|
88
|
+
# @returns [String] An inspect representation of this identity.
|
|
54
89
|
def inspect
|
|
55
90
|
"\#<#{self.class} #{self.to_s}>"
|
|
56
91
|
end
|
|
57
92
|
|
|
93
|
+
# Check if this identity matches another.
|
|
94
|
+
# @parameter other [Identity] The identity to match against.
|
|
95
|
+
# @returns [Boolean] Whether the identities match.
|
|
58
96
|
def match?(other)
|
|
59
97
|
if path = other.path
|
|
60
98
|
return false unless path === @path
|
|
@@ -69,12 +107,15 @@ module Sus
|
|
|
69
107
|
end
|
|
70
108
|
end
|
|
71
109
|
|
|
110
|
+
# Iterate over this identity and all its parents.
|
|
111
|
+
# @yields {|identity| ...} Each identity in the chain.
|
|
72
112
|
def each(&block)
|
|
73
113
|
@parent&.each(&block)
|
|
74
114
|
|
|
75
115
|
yield self
|
|
76
116
|
end
|
|
77
117
|
|
|
118
|
+
# @returns [String] A unique key for this identity.
|
|
78
119
|
def key
|
|
79
120
|
unless @key
|
|
80
121
|
key = Array.new
|
|
@@ -89,6 +130,8 @@ module Sus
|
|
|
89
130
|
end
|
|
90
131
|
|
|
91
132
|
# Given a set of locations, find the first one which matches this identity and return a new identity with the updated line number. This can be used to extract a location from a backtrace.
|
|
133
|
+
# @parameter locations [Array(Thread::Backtrace::Location), nil] Optional locations to search (defaults to caller locations).
|
|
134
|
+
# @returns [Identity] A new identity with updated line number if a match is found, otherwise returns self.
|
|
92
135
|
def scoped(locations = nil)
|
|
93
136
|
if locations
|
|
94
137
|
# This code path is normally taken if we've got an exception with a backtrace:
|
data/lib/sus/integrations.rb
CHANGED
data/lib/sus/it.rb
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
require_relative "context"
|
|
7
7
|
|
|
8
8
|
module Sus
|
|
9
|
+
# Represents an individual test case.
|
|
9
10
|
module It
|
|
11
|
+
# Build a new test case class.
|
|
12
|
+
# @parameter parent [Class] The parent context class.
|
|
13
|
+
# @parameter description [String | Nil] Optional description of the test.
|
|
14
|
+
# @parameter unique [Boolean] Whether the identity should be unique.
|
|
15
|
+
# @yields {...} Optional block containing the test code.
|
|
16
|
+
# @returns [Class] A new test case class.
|
|
10
17
|
def self.build(parent, description = nil, unique: true, &block)
|
|
11
18
|
base = Class.new(parent)
|
|
12
19
|
base.extend(It)
|
|
@@ -21,20 +28,26 @@ module Sus
|
|
|
21
28
|
return base
|
|
22
29
|
end
|
|
23
30
|
|
|
31
|
+
# @returns [Boolean] Always returns true, as test cases are leaf nodes.
|
|
24
32
|
def leaf?
|
|
25
33
|
true
|
|
26
34
|
end
|
|
27
35
|
|
|
36
|
+
# Print a representation of this test case.
|
|
37
|
+
# @parameter output [Output] The output target.
|
|
28
38
|
def print(output)
|
|
29
39
|
self.superclass.print(output)
|
|
30
40
|
|
|
31
41
|
output.write(" it ", :it, self.description, :reset, " ", :identity, self.identity.to_s, :reset)
|
|
32
42
|
end
|
|
33
43
|
|
|
44
|
+
# @returns [String] A string representation of this test case.
|
|
34
45
|
def to_s
|
|
35
46
|
"it #{description}"
|
|
36
47
|
end
|
|
37
48
|
|
|
49
|
+
# Execute this test case.
|
|
50
|
+
# @parameter assertions [Assertions] The assertions instance to use.
|
|
38
51
|
def call(assertions)
|
|
39
52
|
assertions.nested(self, identity: self.identity, isolated: true, measure: true) do |assertions|
|
|
40
53
|
instance = self.new(assertions)
|
|
@@ -45,6 +58,10 @@ module Sus
|
|
|
45
58
|
end
|
|
46
59
|
end
|
|
47
60
|
|
|
61
|
+
# Handle skip logic for the test case.
|
|
62
|
+
# @parameter instance [Base] The test instance.
|
|
63
|
+
# @parameter assertions [Assertions] The assertions instance.
|
|
64
|
+
# @returns [Object] The result of calling the test instance.
|
|
48
65
|
def handle_skip(instance, assertions)
|
|
49
66
|
catch(:skip) do
|
|
50
67
|
return instance.call
|
|
@@ -53,6 +70,10 @@ module Sus
|
|
|
53
70
|
end
|
|
54
71
|
|
|
55
72
|
module Context
|
|
73
|
+
# Define a new test case.
|
|
74
|
+
# @parameter description [String] The description of the test.
|
|
75
|
+
# @parameter options [Hash] Additional options.
|
|
76
|
+
# @yields {...} The test code.
|
|
56
77
|
def it(...)
|
|
57
78
|
add It.build(self, ...)
|
|
58
79
|
end
|
|
@@ -66,30 +87,42 @@ module Sus
|
|
|
66
87
|
throw :skip, reason
|
|
67
88
|
end
|
|
68
89
|
|
|
90
|
+
# Skip the test unless a method is defined on the target.
|
|
91
|
+
# @parameter method [Symbol] The method name to check.
|
|
92
|
+
# @parameter target [Module, Class] The target class or module to check.
|
|
69
93
|
def skip_unless_method_defined(method, target)
|
|
70
94
|
unless target.method_defined?(method)
|
|
71
95
|
skip "Method #{method} is not defined in #{target}!"
|
|
72
96
|
end
|
|
73
97
|
end
|
|
74
98
|
|
|
99
|
+
# Skip the test unless a constant is defined.
|
|
100
|
+
# @parameter constant [Symbol, String] The constant name to check.
|
|
101
|
+
# @parameter target [Module, Class] The target class or module to check.
|
|
75
102
|
def skip_unless_constant_defined(constant, target = Object)
|
|
76
103
|
unless target.const_defined?(constant)
|
|
77
104
|
skip "Constant #{constant} is not defined in #{target}!"
|
|
78
105
|
end
|
|
79
106
|
end
|
|
80
107
|
|
|
108
|
+
# Skip the test unless the Ruby version meets the minimum requirement.
|
|
109
|
+
# @parameter version [String] The minimum Ruby version required.
|
|
81
110
|
def skip_unless_minimum_ruby_version(version)
|
|
82
111
|
unless RUBY_VERSION >= version
|
|
83
112
|
skip "Ruby #{version} is required, but running #{RUBY_VERSION}!"
|
|
84
113
|
end
|
|
85
114
|
end
|
|
86
115
|
|
|
116
|
+
# Skip the test if the Ruby version exceeds the maximum supported version.
|
|
117
|
+
# @parameter version [String] The maximum Ruby version supported.
|
|
87
118
|
def skip_if_maximum_ruby_version(version)
|
|
88
119
|
if RUBY_VERSION >= version
|
|
89
120
|
skip "Ruby #{version} is not supported, but running #{RUBY_VERSION}!"
|
|
90
121
|
end
|
|
91
122
|
end
|
|
92
123
|
|
|
124
|
+
# Skip the test if the Ruby platform matches the pattern.
|
|
125
|
+
# @parameter pattern [Regexp] The platform pattern to match against.
|
|
93
126
|
def skip_if_ruby_platform(pattern)
|
|
94
127
|
if match = RUBY_PLATFORM.match(pattern)
|
|
95
128
|
skip "Ruby platform #{match} is not supported!"
|
data/lib/sus/it_behaves_like.rb
CHANGED
|
@@ -6,11 +6,20 @@
|
|
|
6
6
|
require_relative "context"
|
|
7
7
|
|
|
8
8
|
module Sus
|
|
9
|
+
# Represents a test context that behaves like a shared context.
|
|
9
10
|
module ItBehavesLike
|
|
10
11
|
extend Context
|
|
11
12
|
|
|
13
|
+
# @attribute [Shared] The shared context being used.
|
|
12
14
|
attr_accessor :shared
|
|
13
15
|
|
|
16
|
+
# Build a new ItBehavesLike context.
|
|
17
|
+
# @parameter parent [Class] The parent context class.
|
|
18
|
+
# @parameter shared [Shared] The shared context to use.
|
|
19
|
+
# @parameter arguments [Array | Nil] Optional arguments to pass to the shared context.
|
|
20
|
+
# @parameter unique [Boolean] Whether the identity should be unique.
|
|
21
|
+
# @yields {...} Optional block to execute before the shared context.
|
|
22
|
+
# @returns [Class] A new test class that behaves like the shared context.
|
|
14
23
|
def self.build(parent, shared, arguments = nil, unique: false, &block)
|
|
15
24
|
base = Class.new(parent)
|
|
16
25
|
base.singleton_class.prepend(ItBehavesLike)
|
|
@@ -28,6 +37,8 @@ module Sus
|
|
|
28
37
|
return base
|
|
29
38
|
end
|
|
30
39
|
|
|
40
|
+
# Print a representation of this context.
|
|
41
|
+
# @parameter output [Output] The output target.
|
|
31
42
|
def print(output)
|
|
32
43
|
self.superclass.print(output)
|
|
33
44
|
output.write(" it behaves like ", :describe, self.description, :reset)
|
|
@@ -35,6 +46,11 @@ module Sus
|
|
|
35
46
|
end
|
|
36
47
|
|
|
37
48
|
module Context
|
|
49
|
+
# Define a test context that behaves like a shared context.
|
|
50
|
+
# @parameter shared [Shared] The shared context to use.
|
|
51
|
+
# @parameter arguments [Array] Optional arguments to pass to the shared context.
|
|
52
|
+
# @parameter options [Hash] Additional options.
|
|
53
|
+
# @yields {...} Optional block to execute before the shared context.
|
|
38
54
|
def it_behaves_like(shared, *arguments, **options, &block)
|
|
39
55
|
add ItBehavesLike.build(self, shared, arguments, **options, &block)
|
|
40
56
|
end
|
data/lib/sus/let.rb
CHANGED
|
@@ -7,6 +7,9 @@ require_relative "context"
|
|
|
7
7
|
|
|
8
8
|
module Sus
|
|
9
9
|
module Context
|
|
10
|
+
# Define a lazy variable that is evaluated when first accessed.
|
|
11
|
+
# @parameter name [Symbol] The name of the variable.
|
|
12
|
+
# @yields {...} The block that computes the variable value.
|
|
10
13
|
def let(name, &block)
|
|
11
14
|
instance_variable = :"@#{name}"
|
|
12
15
|
|