fix 0.19 → 0.20

Sign up to get free protection for your applications and to get access to all the features.
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