Fixrb

Fix guide, version 1

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.