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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e44836a2e867dcb513bc3daffa30c6c319e18602be1c4cf0e098d14f7ae4f9b
4
- data.tar.gz: 70029c70b7efd078c5d4cafdcdacd1921a2c081e1637f7f2cbc867bfde839781
3
+ metadata.gz: cb6b67aefa0647614856cce193a81deb17a4310561e48c3d4184b0b1856b0bdc
4
+ data.tar.gz: a66456067810448477560424cd6f51b23ab9768013f344b598da3a61cc856d2b
5
5
  SHA512:
6
- metadata.gz: f18450b4cc887e269471c529e95816537f620e342733a3441091b01709feaf44d7f1fe46e7ea2b228f5eca154f410de0e03456f238aa642e2a35ff1d55aa5376
7
- data.tar.gz: 3854f42a81ddece26f7e9b7d33d347caf37c885cb65ea1e17910032c279c249aefa6ff73d3b7dccfd205d5824812d72564c79d2b76cc7d0afd2e2c51ad08428e
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
- # @return [Array] A list of specifications.
14
- attr_reader :specs
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
- # Load specifications from a constant name.
77
+ # Initialize a new Set with the given contexts.
17
78
  #
18
- # @param name [String, Symbol] The constant name of the specifications.
19
- # @return [Set] A new Set instance containing the loaded specifications.
79
+ # @param contexts [Array<Fix::Dsl>] List of specification contexts
20
80
  #
21
- # @api public
22
- def self.load(name)
23
- new(*Doc.fetch(name))
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
- # Initialize a new Set with given contexts.
90
+ # Checks if the subject matches all specifications without exiting.
27
91
  #
28
- # @param contexts [Array<::Fix::Dsl>] The list of contexts document.
29
- def initialize(*contexts)
30
- @specs = Doc.extract_specifications(*contexts).shuffle
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
- # Run the test suite against the provided subject.
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, exits with false otherwise
38
- # @raise [::SystemExit] The test set failed!
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 test(&)
42
- suite_passed?(&) || ::Kernel.exit(false)
144
+ def to_s
145
+ "fix #{expected.inspect}"
43
146
  end
44
147
 
45
148
  private
46
149
 
47
- def suite_passed?(&subject)
48
- specs.all? { |spec| run_spec(*spec, &subject) }
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 { lab(env, location, requirement, challenges, &subject) }
53
- ::Process.wait
54
- $CHILD_STATUS.success?
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
- def lab(env, location, requirement, challenges, &)
58
- result = Run.new(env, requirement, *challenges).test(&)
59
- report!(location, result)
60
- ::Kernel.exit(result.passed?)
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
- def report!(path, result)
64
- puts "#{path} #{result.colored_string}"
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.19'
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-01 00:00:00.000000000 Z
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