Running specifications
A file with ruby, a project with fix, and the status that says whether it is met.
This page runs a project of three specification files: two are met, one is not.
A file
A specification file is a Ruby program that ends with the test of a subject. test writes one line per expectation and returns whether all of them pass; exit makes it the status of the process, 0 or 1. A file that tests several specifications tests them all, then exits once, as fix/shapes_fix.rb does:
require "fix"
Square = Data.define(:side) do
def area = side * side
end
Circle = Data.define(:radius) do
def area = Math::PI * radius**2
end
Fix :Square do
on :area do
it MUST eq 4
end
end
Fix :Circle do
on :area do
it MUST be_within(0.01).of(3.14)
end
end
results = [
Fix[:Square].test { Square.new(side: 2) },
Fix[:Circle].test { Circle.new(radius: 1) }
]
exit(results.all?)
ruby fix/shapes_fix.rb fix/shapes_fix.rb:13: Success: expected 4 to eq 4 fix/shapes_fix.rb:19: Success: expected 3.141592653589793 to be within 0.01 of 3.14
Keep the parentheses of exit(…): without them, in exit Fix[:Square].test do … end, the block goes to exit, and test raises ArgumentError for want of one.
A project
The fix command runs the files whose names end with _fix.rb in the fix directory, at any depth, in the order of their names, each in a process of its own, with the Ruby that runs fix. It lets their lines through, and ends with how many files were met:
fix fix/shapes_fix.rb:13: Success: expected 4 to eq 4 fix/shapes_fix.rb:19: Success: expected 3.141592653589793 to be within 0.01 of 3.14 fix/tax_fix.rb:5: Failure: expected 20.0 to eq 20 fix/text/slug_fix.rb:6: Success: expected "hello-world" to eq "hello-world" 3 files, 1 not met: fix/tax_fix.rb
The file not met is fix/tax_fix.rb:
require "fix"
Fix :Tax do
on :*, 0.2 do
it MUST eq 20
end
end
exit(Fix[:Tax].test { 100 })
100 * 0.2 is the Float 20.0, which is not eq 20. Files and directories given as arguments narrow the run, in the order given:
fix fix/text fix/text/slug_fix.rb:6: Success: expected "hello-world" to eq "hello-world" 1 file met
The status of fix is 0 when every file was met, 1 when one was not, a file that raised when loaded or a process that died included, and 2 for a usage error, such as a path that does not exist. fix --help lists the options.
A random order
Each file is a process, and each context a child of it: what a subject changes in its process cannot reach another file. What it changes outside, in a database or a file, can. --random runs the files in a random order, and writes first the command that runs them in the same order again:
fix --random fix --random=2 fix/text/slug_fix.rb:6: Success: expected "hello-world" to eq "hello-world" fix/tax_fix.rb:5: Failure: expected 20.0 to eq 20 fix/shapes_fix.rb:13: Success: expected 4 to eq 4 fix/shapes_fix.rb:19: Success: expected 3.141592653589793 to be within 0.01 of 3.14 3 files, 1 not met: fix/tax_fix.rb
A file met in one order and not in another depends on what another file left behind.
Environment
The processes inherit the environment of fix. In a project with a Gemfile, bundle exec fix gives every file the gems of the bundle. RUBYOPT=-w fix runs the files with warnings, and RUBYOPT=-Ilib fix lets them require the files of lib by name.
Anything that reads an exit status can run Fix. A task in a Rakefile, task(:fix) { sh "fix" }, fails when a file is not met; so does a CI step that runs it, such as run: bundle exec fix in a GitHub Actions workflow.
Results as data
For a tool of your own, run takes the place of test: it writes nothing, and gives the results, one Fix::Result per expectation, in the order of the lines:
require "fix"
Fix :Tax do
on :*, 0.2 do
it MUST eq 20
end
end
results = Fix[:Tax].run { 100 }
results.map(&:outcome) # => [:failure]
result = results.first
result.passed? # => false
result.chain # => ".*(0.2)"
result.summary # => "expected 20.0 to eq 20"
A result also has its location, its outcome and the result of Spectus behind it. The README of fix lists them.