fix 1.0.0.beta12 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d32cb2da3e2823f71175074536af7f55c981cba88f1fc39952f5c91ecc27756
4
- data.tar.gz: bfbf3da66b20085030ecffadbfe908dfe90ea3fe5620fdfaceeedbb5eaec0e9e
3
+ metadata.gz: 56afffa92641798956f66d8015ffaa394b229ca6a7a1af4f73aef233089b3d94
4
+ data.tar.gz: b6aa5f4f2cd7674d38e08ac78b3f53def1996760274c478676e1619dae5e809a
5
5
  SHA512:
6
- metadata.gz: 1e66633ce3e37380f20f83f3747ef158e9baadc0853898850972c7e119fe7615eadd53c3197c9bfbd8e48f341d38991060f8dfba16070f34953a08208eefa5bf
7
- data.tar.gz: 798abf5941f2fbac379d6b19622d1dfe8fd278c81a715a8e1f55a0f8c91aa6b0b1febfd2a60241ddb9a12f5320ed112a77a0a39f860d3e26e43985277f4d0e99
6
+ metadata.gz: e50bb8c771b9bed55871df7d4115e7a0a50c3661ee09e86a82e6cc3def766c867f964e4f949c75d1a8191345918629ace15fad600d9906d6bf829ac9d31b3be5
7
+ data.tar.gz: 969531acec4e9f78a0df7641bfde3ca272bccc7cc038b2866e6ce406c9f6b3e339a73889a4ace65dd281d4663610a7559bcca289b1578c757e509e2e254f4ffd
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
- # The MIT License
1
+ The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014-2024 Cyril Kato
3
+ Copyright (c) 2014-2026 Cyril Kato
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,318 +1,291 @@
1
- # Fix Framework
1
+ # Fix
2
2
 
3
- [![Home](https://img.shields.io/badge/Home-fixrb.dev-00af8b)](https://fixrb.dev/)
4
3
  [![Version](https://img.shields.io/github/v/tag/fixrb/fix?label=Version&logo=github)](https://github.com/fixrb/fix/tags)
5
4
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/fix/main)
6
5
  [![License](https://img.shields.io/github/license/fixrb/fix?label=License&logo=github)](https://github.com/fixrb/fix/raw/main/LICENSE.md)
7
6
 
8
- ## Introduction
9
-
10
- Fix is a modern Ruby testing framework that emphasizes clear separation between specifications and examples. Unlike traditional testing frameworks, Fix focuses on creating pure specification documents that define expected behaviors without mixing in implementation details.
7
+ Fix writes a specification: what a subject MUST, SHOULD or MAY do, with the words of [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119), and nothing about how the subject is made. The specification is then tested against a subject, each context in a child process, and every expectation gets a result.
11
8
 
12
9
  ## Installation
13
10
 
14
- Add to your Gemfile:
15
-
16
11
  ```ruby
17
- gem "fix", ">= 1.0.0.beta12"
12
+ # Gemfile
13
+ gem "fix"
18
14
  ```
19
15
 
20
- Then execute:
16
+ Fix requires Ruby 3.3 or later on a system with `fork`, and depends on [Aw](https://github.com/fixrb/aw), [Defi](https://github.com/fixrb/defi), [Matchi](https://github.com/fixrb/matchi) and [Spectus](https://github.com/fixrb/spectus).
21
17
 
22
- ```sh
23
- bundle install
24
- ```
18
+ ## Usage
25
19
 
26
- Or install it yourself:
20
+ A file writes a specification, then tests a subject against it:
27
21
 
28
- ```sh
29
- gem install fix --pre
30
- ```
22
+ ```ruby
23
+ # duck_fix.rb
24
+ require "fix"
31
25
 
32
- ## Core Principles
26
+ class Duck
27
+ def walk = "Klop klop!"
28
+ def swim = "Swoosh..."
29
+ end
33
30
 
34
- - **Specifications vs Examples**: Fix makes a clear distinction between specifications (what is expected) and examples (how it's demonstrated). This separation leads to cleaner, more maintainable test suites.
31
+ Fix :Duck do
32
+ on :walk do
33
+ it MUST eq "Klop klop!"
34
+ end
35
35
 
36
- - **Logic-Free Specifications**: Your specification documents remain pure and focused on defining behaviors, without getting cluttered by implementation logic.
36
+ on :swim do
37
+ it MUST eq "Swoosh..."
38
+ end
37
39
 
38
- - **Rich Semantic Language**: Following RFC 2119 conventions, Fix uses precise language with keywords like MUST, SHOULD, and MAY to clearly define different requirement levels in specifications.
40
+ on :speak do
41
+ it MAY eq "Quaaaaaack!"
42
+ end
43
+ end
39
44
 
40
- - **Fast Individual Testing**: Tests execute quickly and independently, providing rapid feedback on specification compliance.
45
+ exit(Fix[:Duck].test { Duck.new })
46
+ ```
41
47
 
42
- ## Framework Features
48
+ ```text
49
+ duck_fix.rb:11: Success: expected "Klop klop!" to eq "Klop klop!"
50
+ duck_fix.rb:15: Success: expected "Swoosh..." to eq "Swoosh..."
51
+ duck_fix.rb:19: Info: NoMethodError: undefined method 'speak' for an instance of Duck
52
+ ```
43
53
 
44
- ### Property Definition with `let`
54
+ `Fix :Duck do … end` builds the specification and registers it under its name; `Fix[:Duck]` finds it again. `test` builds a new subject with the block for every expectation, sends it the methods of the `on` around it, judges what comes out, writes one line per expectation, the backtrace of an error below it, and returns whether all of them pass (`true` when there is nothing to test). `exit` turns that into the status of the process: 0 when the specification is met, 1 otherwise.
45
55
 
46
- Define reusable properties across your specifications:
56
+ Keep the parentheses: `exit Fix[:Duck].test do … end` gives the block to `exit`, and `test` raises `ArgumentError` for want of one. A file with several specifications tests them all before it exits, as `exit([Fix[:A].test { … }, Fix[:B].test { … }].all?)` does.
47
57
 
48
- ```ruby
49
- Fix do
50
- let(:name) { "Bob" }
51
- let(:age) { 42 }
58
+ ## Writing a specification
52
59
 
53
- it MUST eq name
54
- end
55
- ```
60
+ The block of `Fix` is a document, read once when the file is loaded, with four words: `let`, `with`, `on` and `it`. Each `with`, each `on` opens a context, which inherits from the one around it.
56
61
 
57
- ### Context Creation with `with`
62
+ ### `on`
58
63
 
59
- Test behavior under different conditions:
64
+ `on :name, *arguments, **keywords` sends a method to the subject, and the expectations inside judge what it returns. The `on` inside an `on` send their method to that value, and so on; with no `on` around, an expectation judges the subject itself.
60
65
 
61
66
  ```ruby
62
- Fix do
63
- with name: "Alice", role: "admin" do
64
- it MUST be_allowed
67
+ require "fix"
68
+
69
+ Fix :Answer do
70
+ it MUST be_an_instance_of Integer
71
+
72
+ on :+, 2 do
73
+ it MUST eq 44
74
+ end
75
+
76
+ on :to_s do
77
+ on :size do
78
+ it MUST eq 2
79
+ end
65
80
  end
66
81
 
67
- with name: "Bob", role: "guest" do
68
- it MUST_NOT be_allowed
82
+ on :digits do
83
+ it MUST eq [2, 4]
69
84
  end
70
85
  end
86
+
87
+ exit(Fix[:Answer].test { 42 })
71
88
  ```
72
89
 
73
- ### Method Testing with `on`
90
+ ```text
91
+ example.rb:4: Success: expected 42 to be an instance of Integer
92
+ example.rb:7: Success: expected 44 to eq 44
93
+ example.rb:12: Success: expected 2 to eq 2
94
+ example.rb:17: Success: expected [2, 4] to eq [2, 4]
95
+ ```
96
+
97
+ A method that raises ends the chain there, and the expectation judges the exception: an error for most matchers, what `raise_exception` is for. There is no helper for a predicate: `on(:empty?) { it MUST be_true }` asks the question. The method is sent without a block, since the block of `on` is the context: a method that needs one is called in the subject block, or tested with `satisfy`. A `BasicObject` has no `public_send`, so an `on` sent to it is an error.
98
+
99
+ ### `let` and `with`
74
100
 
75
- Test how objects respond to specific messages:
101
+ A `let` is a parameter of the subject: a name and a block, for the block of `test` to call when it builds the subject. Its value is computed the first time it is called and kept for the rest of the expectation, and a `let` may call another. `with name: value` opens a context where the parameter has that value.
76
102
 
77
103
  ```ruby
78
- Fix do
79
- on :upcase do
80
- it MUST eq "HELLO"
81
- end
104
+ require "fix"
82
105
 
83
- on :+, 2 do
84
- it MUST eq 42
106
+ Fix :Greeting do
107
+ let(:name) { "Bob" }
108
+
109
+ it MUST eq "Hello, Bob!"
110
+
111
+ with name: "Alice" do
112
+ it MUST eq "Hello, Alice!"
85
113
  end
86
114
  end
115
+
116
+ exit(Fix[:Greeting].test { "Hello, #{name}!" })
87
117
  ```
88
118
 
89
- ### Requirement Levels
119
+ ```text
120
+ example.rb:6: Success: expected "Hello, Bob!" to eq "Hello, Bob!"
121
+ example.rb:9: Success: expected "Hello, Alice!" to eq "Hello, Alice!"
122
+ ```
90
123
 
91
- Fix provides three levels of requirements, each with clear semantic meaning:
124
+ The block of `test` runs on a fresh object for every expectation, where the lets of its context are methods: it sees them, and sees `Kernel` and the methods of the top level as any code does, but not the instance variables of the file. The value of a `with` is the object given, shared by the expectations of its context: `with items: []` gives the same array to each of them, where `let(:items) { [] }` gives a new one to each.
92
125
 
93
- - **MUST/MUST_NOT**: Absolute requirements or prohibitions
94
- - **SHOULD/SHOULD_NOT**: Recommended practices with valid exceptions
95
- - **MAY**: Optional features
126
+ A let is visible from the subject and from the other lets only. Named in an expectation, where it would be read before any subject exists, it raises `NameError` at load, with the remedy: `name is a let, visible in the subject block and in other lets only`. Its name cannot be a method of the object the subject is built on, public or private, nor a word of the language, so that nothing is redefined in silence: `let(:format)`, `let(:p)` or `let(:it)` raise `ArgumentError` at load, naming the owner (`format is already a method of Kernel`). A `with` has the same rule, and declares the lets it names: a name no `let` declared is a new parameter, not a mistake. A context may declare a name once, inherits the lets declared above it in the contexts around it, and may give a new value to one of them.
96
127
 
97
- ```ruby
98
- Fix do
99
- it MUST be_valid # Required
100
- it SHOULD be_optimized # Recommended
101
- it MAY include_metadata # Optional
102
- end
103
- ```
128
+ ### `it`
129
+
130
+ `it` takes a requirement, not a block: a level, then a matcher, as in `it MUST eq 42`. The levels are `MUST`, `MUST_NOT`, `SHOULD`, `SHOULD_NOT` and `MAY`, the words of RFC 2119, which has no negative MAY. The matchers are those of Matchi:
104
131
 
105
- ## Quick Start
132
+ | Word | Meets when the actual value… |
133
+ |------|------------------------------|
134
+ | `eq(expected)` | is `eql?` to the expected one |
135
+ | `be(expected)` | is the very same object (`equal?`) |
136
+ | `be_true`, `be_false`, `be_nil` | is `true`, `false`, `nil` |
137
+ | `be_within(delta).of(expected)` | is a number within the delta |
138
+ | `match(regexp)` | is a String or a Symbol that matches |
139
+ | `be_an_instance_of(klass)` | is an instance of that very class |
140
+ | `be_a_kind_of(mod)` | is an instance of that module or of a descendant |
141
+ | `raise_exception(klass, message = nil)` | is an exception of that class or of a subclass, with that message (a String, or a Regexp to match) when given |
142
+ | `change(object, :method)`, `.by(n)`, `.by_at_least(n)`, `.by_at_most(n)`, `.from(a).to(b)`, `.to(b)` | makes that method of that object change, so when refined |
143
+ | `satisfy { \|value\| … }` | makes the block answer `true`; an answer that is neither `true` nor `false` is an error |
144
+ | `fix(:Name)`, `fix { … }` | meets that specification, by name or written here |
106
145
 
107
- Create your first test file:
146
+ Any other object with `match?` and `to_s` is a matcher too. The object of a `change` must exist when the file is loaded, since the matcher is built there: a let, which lives with the subject, cannot be observed. A specification is a matcher that tests its expectations against the actual value, so one specification may require another:
108
147
 
109
148
  ```ruby
110
- # first_test.rb
111
149
  require "fix"
112
150
 
113
- Fix :HelloWorld do
114
- it MUST eq "Hello, World!"
151
+ Fix :Positive do
152
+ on :positive? do
153
+ it MUST be_true
154
+ end
115
155
  end
116
156
 
117
- Fix[:HelloWorld].test { "Hello, World!" }
118
- ```
157
+ Fix :Score do
158
+ it MUST be_a_kind_of Numeric
159
+ it MUST fix(:Positive)
160
+ end
119
161
 
120
- Run it:
162
+ exit(Fix[:Score].test { 7 })
163
+ ```
121
164
 
122
- ```sh
123
- ruby first_test.rb
165
+ ```text
166
+ example.rb:10: Success: expected 7 to be a kind of Numeric
167
+ example.rb:11: Success: expected 7 to fix Positive
124
168
  ```
125
169
 
126
- ## Real-World Examples
170
+ A failed `fix` says that the specification is not met, not which of its expectations failed: test the specification on its own for that.
127
171
 
128
- Fix is designed to work with real-world applications of any complexity. Here are some examples demonstrating how Fix can be used in different scenarios:
172
+ In Ruby 3.4 and later, a bare `it` in a block is the parameter of the block, while `it` with a requirement is the word of Fix, as before; on Ruby 3.3 the bare `it` is the word of Fix too. A `satisfy` meant for both names its parameter: `satisfy { |value| … }`.
129
173
 
130
- ### Example 1: User Account Management
174
+ The document runs on an instance of `Fix::Dsl`, and a gem may add a word to it that composes `on` and `it`: fix-its adds `its`. Such a word gives `it` the `location:` of its own caller, what `caller_locations(1, 1).first` gives, an object with `path` and `lineno`, so that the result points at the line of the specification.
131
175
 
132
- Here's a comprehensive example showing how to specify a user account system:
176
+ ### Loading
133
177
 
134
- ```ruby
135
- Fix :UserAccount do
136
- # Define reusable properties
137
- let(:admin) { User.new(role: "admin") }
138
- let(:guest) { User.new(role: "guest") }
178
+ The specification is built when its block runs, before any subject exists, and nothing in it waits for a test: a name that is not a Symbol, a name already registered, a `let`, a `with` or an `on` without a block, a `with` without a value, an `it` with a block or without a requirement, a matcher without `match?` or whose `to_s` is not a `String`, raise then. A block that raises registers nothing. `Fix[:Name]` raises `KeyError` for a name it does not know, and `fix(:Name)` finds its specification then: write it first.
139
179
 
140
- # Test basic instance properties
141
- it MUST be_an_instance_of User
180
+ `Fix.spec(:Name) { … }` is the method `Fix` calls; without a name, both give a specification that is not registered. The registry is written by the thread that loads the file, read afterwards, and never emptied: a runner runs each file in a process of its own.
142
181
 
143
- # Test with different contexts
144
- with role: "admin" do
145
- it MUST be_admin
182
+ ## Outcomes
146
183
 
147
- on :can_access?, "settings" do
148
- it MUST be_true
149
- end
150
- end
184
+ Two questions tell the levels apart. Does the method exist? MUST and SHOULD require it, MAY does not. Does it do what is specified? MUST and MAY require it, SHOULD recommends it. Hence five outcomes:
151
185
 
152
- with role: "guest" do
153
- it MUST_NOT be_admin
186
+ | Level | Match is true | Match is false | Missing method | Other exception |
187
+ |--------|---------------|----------------|----------------|-----------------|
188
+ | MUST | success | failure | error | error |
189
+ | SHOULD | success | warning | error | error |
190
+ | MAY | success | failure | info | error |
154
191
 
155
- on :can_access?, "settings" do
156
- it MUST be_false
157
- end
158
- end
159
-
160
- # Test specific methods
161
- on :full_name do
162
- with first_name: "John", last_name: "Doe" do
163
- it MUST eq "John Doe"
164
- end
165
- end
166
-
167
- on :update_password, "new_password" do
168
- it MUST change(admin, :password_hash)
169
- it MUST be_true # Return value check
170
- end
171
- end
172
- ```
173
-
174
- This example demonstrates:
175
- - Using `let` to define test fixtures
176
- - Context-specific testing with `with`
177
- - Method behavior testing with `on`
178
- - Different requirement levels with `MUST`/`MUST_NOT`
179
- - Testing state changes with the `change` matcher
180
- - Nested contexts for complex scenarios
181
-
182
- ### Example 2: Duck Specification
183
-
184
- Here's how Fix can be used to specify a Duck class:
192
+ A negation swaps the first two columns. A success, a warning and an info pass; a failure and an error do not. The missing method is the one named by the `on` of the context, when sending it raises `NoMethodError` for that method on that object, as the duck has no `speak`; Spectus adds the method a matcher calls on the actual value. A private method counts as missing, as does a method that exists and raises `NoMethodError` for itself; a method that raises `NoMethodError` on another object is an error.
185
193
 
186
194
  ```ruby
187
- Fix :Duck do
188
- it SHOULD be_an_instance_of :Duck
195
+ # account_fix.rb
196
+ require "fix"
189
197
 
190
- on :swims do
191
- it MUST be_an_instance_of :String
192
- it MUST eql "Swoosh..."
198
+ class Account
199
+ def initialize(balance)
200
+ @balance = balance
193
201
  end
194
202
 
195
- on :speaks do
196
- it MUST raise_exception NoMethodError
197
- end
203
+ attr_reader :balance
204
+
205
+ def withdraw(amount)
206
+ raise ArgumentError, "insufficient funds" if amount > balance
198
207
 
199
- on :sings do
200
- it MAY eql "♪... ♫..."
208
+ @balance -= amount
201
209
  end
202
210
  end
203
- ```
204
211
 
205
- The implementation:
206
-
207
- ```ruby
208
- class Duck
209
- def walks
210
- "Klop klop!"
212
+ Fix :Account do
213
+ on :balance do
214
+ it MUST eq 100
215
+ it MUST eq 200
216
+ it SHOULD eq 200
211
217
  end
212
218
 
213
- def swims
214
- "Swoosh..."
219
+ on :currency do
220
+ it MAY eq "EUR"
215
221
  end
216
222
 
217
- def quacks
218
- puts "Quaaaaaack!"
223
+ on :withdraw, 500 do
224
+ it MUST eq(-400)
219
225
  end
220
226
  end
221
- ```
222
-
223
- Running the test:
224
227
 
225
- ```ruby
226
- Fix[:Duck].test { Duck.new }
228
+ exit(Fix[:Account].test { Account.new(100) })
227
229
  ```
228
230
 
229
- ## Available Matchers
230
-
231
- Fix includes a comprehensive set of matchers through its integration with the [Matchi library](https://github.com/fixrb/matchi):
232
-
233
- ### Basic Comparison Matchers
234
- - `eq(expected)` - Tests equality using `eql?`
235
- - `be(expected)` - Tests object identity using `equal?`
236
-
237
- ### Type Checking Matchers
238
- - `be_an_instance_of(class)` - Verifies exact class match
239
- - `be_a_kind_of(class)` - Checks class inheritance and module inclusion
240
-
241
- ### Change Testing Matchers
242
- - `change(object, method)` - Base matcher for state changes
243
- - `.by(n)` - Expects exact change by n
244
- - `.by_at_least(n)` - Expects minimum change by n
245
- - `.by_at_most(n)` - Expects maximum change by n
246
- - `.from(old).to(new)` - Expects change from old to new value
247
- - `.to(new)` - Expects change to new value
248
-
249
- ### Numeric Matchers
250
- - `be_within(delta).of(value)` - Tests if a value is within ±delta of expected value
251
-
252
- ### Pattern Matchers
253
- - `match(regex)` - Tests string against regular expression pattern
254
- - `satisfy { |value| ... }` - Custom matching with block
231
+ ```text
232
+ account_fix.rb:20: Success: expected 100 to eq 100
233
+ account_fix.rb:21: Failure: expected 100 to eq 200
234
+ account_fix.rb:22: Warning: expected 100 to eq 200
235
+ account_fix.rb:26: Info: NoMethodError: undefined method 'currency' for an instance of Account
236
+ account_fix.rb:30: Error: ArgumentError: insufficient funds
237
+ account_fix.rb:12:in 'Account#withdraw'
238
+ account_fix.rb:34:in '<main>'
239
+ ```
255
240
 
256
- ### State Matchers
257
- - `be_true` - Tests for true
258
- - `be_false` - Tests for false
259
- - `be_nil` - Tests for nil
241
+ ## Results
260
242
 
261
- ### Exception Matchers
262
- - `raise_exception(class)` - Tests if code raises specified exception
243
+ `test` writes on `$stdout`, for each expectation, one line: its location in the file, the outcome, and the summary of Spectus, `expected ACTUAL to DESCRIPTION`, with `not` for a negation, or `CLASS: MESSAGE`. A line break in either is written `\n`; a backslash stays as it is, the line being for reading, not for reading back. Below an error come the frames of its backtrace that are not in Fix, Aw, Defi, Matchi or Spectus, indented by two spaces. Nothing else is written: no letter, no colour, no count. What the subject writes on the standard output comes out as it is, before the lines of its context, so a text left without a line break joins the first of them.
263
244
 
264
- ### Dynamic Predicate Matchers
265
- - `be_*` - Dynamically matches `object.*?` methods (e.g., `be_empty` calls `empty?`)
266
- - `have_*` - Dynamically matches `object.has_*?` methods (e.g., `have_key` calls `has_key?`)
245
+ That grammar, `LOCATION: OUTCOME: SUMMARY` then indented continuation lines, is what a runner reads: the location up to the first `:LINE: OUTCOME: ` of the line, the outcome one of the five words. What the subject writes is beyond the runner's reach; the status of the process is not.
267
246
 
268
- Example usage:
247
+ `run` gives the same results as data, an `Array` of `Fix::Result` in the order of the contexts, a context before those inside it, and of the expectations within a context:
269
248
 
270
249
  ```ruby
271
- Fix :Calculator do
272
- it MUST be_an_instance_of Calculator
250
+ require "fix"
273
251
 
274
- on :add, 2, 3 do
275
- it MUST eq 5
276
- it MUST be_within(0.001).of(5.0)
277
- end
252
+ Fix :Size do
253
+ it MUST be_an_instance_of String
278
254
 
279
- on :divide, 1, 0 do
280
- it MUST raise_exception ZeroDivisionError
281
- end
282
-
283
- with numbers: [1, 2, 3] do
284
- it MUST_NOT be_empty
285
- it MUST satisfy { |result| result.all? { |n| n.positive? } }
255
+ on :size do
256
+ it MUST eq 3
286
257
  end
287
258
  end
259
+
260
+ results = Fix[:Size].run { "abcd" }
261
+ results.map(&:outcome) # => [:success, :failure]
262
+ results.map(&:passed?) # => [true, false]
263
+ result = results.last
264
+ result.location # => "example.rb:7"
265
+ result.chain # => ".size"
266
+ result.summary # => "expected 4 to eq 3"
267
+ result.to_s # => "example.rb:7: Failure: expected 4 to eq 3"
268
+ result.result.class # => Spectus::Result
269
+ Fix[:Size].match? { "abc" } # => true
288
270
  ```
289
271
 
290
- ## Why Choose Fix?
272
+ `location` is the file and the line of the `it`, relative to the current directory when under it; `chain` is the methods sent to the subject, as written after a dot with the arguments by their `inspect`, empty for the subject itself; `result` is the `Spectus::Result`, with its `level`, `negate`, `description`, `actual`, `error_class`, `error_message` and `backtrace`, unfiltered. `passed?`, `outcome` and `summary` are those of the result. A result is plain data, frozen.
291
273
 
292
- Fix brings several unique advantages to Ruby testing that set it apart from traditional testing frameworks:
274
+ `match?` tests a value, as a matcher does: the block is called once, where `match?` is called, and its value is the subject of every expectation, so the contexts cannot build it anew with their lets: a specification meant for `fix` is written without `with`.
293
275
 
294
- - **Clear Separation of Concerns**: Keep your specifications clean and your examples separate
295
- - **Semantic Precision**: Express requirements with different levels of necessity
296
- - **Fast Execution**: Get quick feedback on specification compliance
297
- - **Pure Specifications**: Write specification documents that focus on behavior, not implementation
298
- - **Rich Matcher Library**: Comprehensive set of matchers for different testing needs
299
- - **Modern Ruby**: Takes advantage of modern Ruby features and practices
276
+ ## Processes
300
277
 
301
- ## Get Started
278
+ Each context with expectations runs in a child process, forked from the one that tests: a subject that leaves something behind cannot touch the next context, and what it raises, an `exit` included, is an error of its expectation alone. The contexts run one after the other, a context before those inside it, and `test` writes the results of a context when its child returns them.
302
279
 
303
- Ready to write better specifications? Visit our [GitHub repository](https://github.com/fixrb/fix) to start using Fix in your Ruby projects.
280
+ Nothing is rescued around a child, and Fix never writes a result that did not come from an expectation. An `Interrupt`, or a signal the child could trap, is raised again in the parent and stops the run. So does `Aw::ChildError`, for a child that died, was killed or left by `exit!`, or that a `break`, a `return` or a `throw` in the subject block left without a result: the results of the other expectations of its context are lost with it. The children a `fix` matcher forks are inside a matcher, so what they raise is an error of that expectation. On a Ruby without `fork`, a context with expectations raises `NotImplementedError`.
304
281
 
305
- ## Community & Resources
282
+ Each child starts as a copy of the parent, with its memory, its signal handlers and its open files, and ends with `exit!`, which runs no `at_exit` handler. Hence a `Random` seeded in the file gives every context the same sequence, while Ruby reseeds `Kernel#rand` in each child; what a subject writes to an IO other than the standard output and error is lost unless it flushes; and what SimpleCov measures in a child is lost, unless the result is stored before `exit!`, as the tests of Fix do, or the run uses `fork: false`. A subject that forks ends its child with `exit!`, as test code does: a child that runs on into Fix instead, by an `exit` or by nothing at all, is ended by Fix without a result. A `Timeout` or a signal in the parent ends the child at once, but not the children of that child, those a `fix` matcher forks or a subject's own, which run on until their work ends. A specification that requires itself at run time, through `satisfy` say, forks without end, and at the process limit a fork waits for a free slot rather than failing. The cost of a context is one fork of the testing process, a few milliseconds.
306
283
 
307
- - [Blog](https://fixrb.dev/) - Related articles
308
- - [Bluesky](https://bsky.app/profile/fixrb.dev) - Latest updates and discussions
309
- - [Documentation](https://www.rubydoc.info/gems/fix) - Comprehensive guides and API reference
310
- - [Source Code](https://github.com/fixrb/fix) - Contribute and report issues
311
- - [asciinema](https://asciinema.org/~fix) - Watch practical examples in action
284
+ `test(fork: false) { … }` and `run(fork: false) { … }` run the contexts in the process itself, for a debugger: an `exit!` of the subject then ends the process, what one context leaves behind reaches the next, and a `fix` matcher still forks.
312
285
 
313
286
  ## Versioning
314
287
 
315
- __Fix__ follows [Semantic Versioning 2.0](https://semver.org/).
288
+ Fix follows [Semantic Versioning 2.0](https://semver.org/).
316
289
 
317
290
  ## License
318
291