fix 0.19 → 0.20
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
- data/lib/fix/error/missing_subject_block.rb +15 -0
- data/lib/fix/set.rb +174 -31
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb6b67aefa0647614856cce193a81deb17a4310561e48c3d4184b0b1856b0bdc
|
4
|
+
data.tar.gz: a66456067810448477560424cd6f51b23ab9768013f344b598da3a61cc856d2b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7cf39f73a43eaee6b19de405903c8421707f35fd7c3ccf06ff0395c434a3e9f701c596f5ba1ced62fa7edaef80fe17cfbfd6d275ab2716330c3feccfa6a9695c
|
7
|
+
data.tar.gz: 0b23915ab51f8c6d34a013188a07130c6d86eab5e128ddaeae4ef53b3952257ff5e49ef729fae5aaa49064cdb2647d4cd37ddd1fbaed2189170a1ac8834d65c4
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fix
|
4
|
+
module Error
|
5
|
+
# Error raised when attempting to test a specification without providing a subject block
|
6
|
+
class MissingSubjectBlock < ::ArgumentError
|
7
|
+
MISSING_BLOCK_ERROR = "Subject block is required for testing a specification. " \
|
8
|
+
"Use: test { subject } or match? { subject }"
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super(MISSING_BLOCK_ERROR)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/fix/set.rb
CHANGED
@@ -1,67 +1,210 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "English"
|
4
|
-
|
5
3
|
require_relative "doc"
|
6
4
|
require_relative "run"
|
5
|
+
require_relative "error/missing_subject_block"
|
7
6
|
|
8
7
|
module Fix
|
9
|
-
# Collection of specifications.
|
8
|
+
# Collection of specifications that can be executed as a test suite.
|
9
|
+
#
|
10
|
+
# The Set class is a central component in Fix's architecture that handles:
|
11
|
+
# - Loading and organizing test specifications
|
12
|
+
# - Managing test execution and isolation
|
13
|
+
# - Reporting test results
|
14
|
+
# - Handling process management for test isolation
|
15
|
+
#
|
16
|
+
# It supports both named specifications (loaded via Fix[name]) and anonymous
|
17
|
+
# specifications (created directly via Fix blocks).
|
18
|
+
#
|
19
|
+
# @example Running a simple named specification
|
20
|
+
# Fix[:Calculator].test { Calculator.new }
|
21
|
+
#
|
22
|
+
# @example Running a complex specification with multiple contexts
|
23
|
+
# Fix[:UserSystem] do
|
24
|
+
# with(role: "admin") do
|
25
|
+
# on :access?, :settings do
|
26
|
+
# it MUST be_true
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# with(role: "guest") do
|
31
|
+
# on :access?, :settings do
|
32
|
+
# it MUST be_false
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# end.test { UserSystem.new(role:) }
|
36
|
+
#
|
37
|
+
# @example Using match? for conditional testing
|
38
|
+
# if Fix[:EmailValidator].match? { email }
|
39
|
+
# puts "Email is valid"
|
40
|
+
# end
|
10
41
|
#
|
11
42
|
# @api private
|
12
43
|
class Set
|
13
|
-
#
|
14
|
-
|
44
|
+
# List of specifications to be tested.
|
45
|
+
# Each specification is an array containing:
|
46
|
+
# - The test environment
|
47
|
+
# - The source location (file:line)
|
48
|
+
# - The requirement (MUST, SHOULD, or MAY)
|
49
|
+
# - The challenges to apply
|
50
|
+
#
|
51
|
+
# @return [Array] List of specifications
|
52
|
+
attr_reader :expected
|
53
|
+
|
54
|
+
class << self
|
55
|
+
# Loads specifications from a registered constant name.
|
56
|
+
#
|
57
|
+
# This method retrieves previously registered specifications and creates
|
58
|
+
# a new Set instance ready for testing. It's typically used in conjunction
|
59
|
+
# with Fix[name] syntax.
|
60
|
+
#
|
61
|
+
# @param name [String, Symbol] The constant name of the specifications
|
62
|
+
# @return [Set] A new Set instance containing the loaded specifications
|
63
|
+
# @raise [Fix::Error::SpecificationNotFound] If specification doesn't exist
|
64
|
+
#
|
65
|
+
# @example Loading a named specification
|
66
|
+
# Fix::Set.load(:Calculator)
|
67
|
+
#
|
68
|
+
# @example Loading and testing in one go
|
69
|
+
# Fix::Set.load(:EmailValidator).test { email }
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
def load(name)
|
73
|
+
new(*Doc.fetch(name))
|
74
|
+
end
|
75
|
+
end
|
15
76
|
|
16
|
-
#
|
77
|
+
# Initialize a new Set with the given contexts.
|
17
78
|
#
|
18
|
-
# @param
|
19
|
-
# @return [Set] A new Set instance containing the loaded specifications.
|
79
|
+
# @param contexts [Array<Fix::Dsl>] List of specification contexts
|
20
80
|
#
|
21
|
-
# @
|
22
|
-
|
23
|
-
|
81
|
+
# @example Creating a set with a single context
|
82
|
+
# Fix::Set.new(calculator_context)
|
83
|
+
#
|
84
|
+
# @example Creating a set with multiple contexts
|
85
|
+
# Fix::Set.new(base_context, admin_context, guest_context)
|
86
|
+
def initialize(*contexts)
|
87
|
+
@expected = randomize_specs(Doc.extract_specifications(*contexts))
|
24
88
|
end
|
25
89
|
|
26
|
-
#
|
90
|
+
# Checks if the subject matches all specifications without exiting.
|
27
91
|
#
|
28
|
-
#
|
29
|
-
|
30
|
-
|
92
|
+
# Unlike #test, this method:
|
93
|
+
# - Returns a boolean instead of exiting
|
94
|
+
# - Can be used in conditional logic
|
95
|
+
#
|
96
|
+
# @yield The block of code to be tested
|
97
|
+
# @yieldreturn [Object] The result of the code being tested
|
98
|
+
# @return [Boolean] true if all tests pass, false otherwise
|
99
|
+
#
|
100
|
+
# @example Basic usage
|
101
|
+
# set.match? { Calculator.new } #=> true
|
102
|
+
#
|
103
|
+
# @example Conditional usage
|
104
|
+
# if set.match? { user_input }
|
105
|
+
# save_to_database(user_input)
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# @api public
|
109
|
+
def match?(&subject)
|
110
|
+
raise Error::MissingSubjectBlock unless subject
|
111
|
+
|
112
|
+
expected.all? { |spec| run_spec(*spec, &subject) }
|
31
113
|
end
|
32
114
|
|
33
|
-
#
|
115
|
+
# Runs the test suite against the provided subject.
|
116
|
+
#
|
117
|
+
# This method:
|
118
|
+
# - Executes all specifications in random order
|
119
|
+
# - Runs each test in isolation using process forking
|
120
|
+
# - Reports results for each specification
|
121
|
+
# - Exits with failure if any test fails
|
34
122
|
#
|
35
123
|
# @yield The block of code to be tested
|
36
124
|
# @yieldreturn [Object] The result of the code being tested
|
37
|
-
# @return [Boolean] true if all tests pass
|
38
|
-
# @raise [
|
125
|
+
# @return [Boolean] true if all tests pass
|
126
|
+
# @raise [SystemExit] When any test fails (exit code: 1)
|
127
|
+
#
|
128
|
+
# @example Basic usage
|
129
|
+
# set.test { Calculator.new }
|
130
|
+
#
|
131
|
+
# @example Testing with parameters
|
132
|
+
# set.test { Game.new(south_variant:, north_variant:) }
|
133
|
+
#
|
134
|
+
# @api public
|
135
|
+
def test(&subject)
|
136
|
+
match?(&subject) || exit_with_failure
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns a string representing the matcher.
|
140
|
+
#
|
141
|
+
# @return [String] a human-readable description of the matcher
|
39
142
|
#
|
40
143
|
# @api public
|
41
|
-
def
|
42
|
-
|
144
|
+
def to_s
|
145
|
+
"fix #{expected.inspect}"
|
43
146
|
end
|
44
147
|
|
45
148
|
private
|
46
149
|
|
47
|
-
|
48
|
-
|
150
|
+
# Randomizes the order of specifications for better isolation
|
151
|
+
#
|
152
|
+
# @param specifications [Array] The specifications to randomize
|
153
|
+
# @return [Array] Randomized specifications
|
154
|
+
def randomize_specs(specifications)
|
155
|
+
specifications.shuffle
|
49
156
|
end
|
50
157
|
|
158
|
+
# Runs a single specification in a forked process
|
159
|
+
#
|
160
|
+
# @param env [Fix::Dsl] The test environment
|
161
|
+
# @param location [String] The source location of the spec
|
162
|
+
# @param requirement [Object] The test requirement
|
163
|
+
# @param challenges [Array] The test challenges
|
164
|
+
# @yield The subject block to test against
|
165
|
+
# @return [Boolean] true if spec passed
|
51
166
|
def run_spec(env, location, requirement, challenges, &subject)
|
52
|
-
::Process.fork {
|
53
|
-
::Process.
|
54
|
-
|
167
|
+
child_pid = ::Process.fork { execute_spec(env, location, requirement, challenges, &subject) }
|
168
|
+
_pid, process_status = ::Process.wait2(child_pid)
|
169
|
+
process_status.success?
|
170
|
+
end
|
171
|
+
|
172
|
+
# Executes a specification in its own process
|
173
|
+
#
|
174
|
+
# @param env [Fix::Dsl] The test environment
|
175
|
+
# @param location [String] The source location of the spec
|
176
|
+
# @param requirement [Object] The test requirement
|
177
|
+
# @param challenges [Array] The test challenges
|
178
|
+
# @yield The subject block to test against
|
179
|
+
def execute_spec(env, location, requirement, challenges, &subject)
|
180
|
+
result = Run.new(env, requirement, *challenges).test(&subject)
|
181
|
+
report_result(location, result)
|
182
|
+
exit_with_status(result.passed?)
|
55
183
|
end
|
56
184
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
185
|
+
# Reports the result of a specification
|
186
|
+
#
|
187
|
+
# @param location [String] The source location of the spec
|
188
|
+
# @param result [Object] The test result
|
189
|
+
def report_result(location, result)
|
190
|
+
puts "#{location} #{result.colored_string}"
|
61
191
|
end
|
62
192
|
|
63
|
-
|
64
|
-
|
193
|
+
# Exits the process with a failure status
|
194
|
+
#
|
195
|
+
# @return [void]
|
196
|
+
# @raise [SystemExit] Always
|
197
|
+
def exit_with_failure
|
198
|
+
::Kernel.exit(false)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Exits the process with the given status
|
202
|
+
#
|
203
|
+
# @param status [Boolean] The exit status
|
204
|
+
# @return [void]
|
205
|
+
# @raise [SystemExit] Always
|
206
|
+
def exit_with_status(status)
|
207
|
+
::Kernel.exit(status)
|
65
208
|
end
|
66
209
|
end
|
67
210
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fix
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.20'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: defi
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- lib/fix/dsl.rb
|
71
71
|
- lib/fix/error/invalid_specification_name.rb
|
72
72
|
- lib/fix/error/missing_specification_block.rb
|
73
|
+
- lib/fix/error/missing_subject_block.rb
|
73
74
|
- lib/fix/error/specification_not_found.rb
|
74
75
|
- lib/fix/matcher.rb
|
75
76
|
- lib/fix/requirement.rb
|