Tooling
So far you have used three rux commands: run, check and build. A few more keep a package tidy once it is more than a single file — they format it, lint it, test it and document it. None of them changes what the program does; together they catch the mistakes the compiler lets through. This lesson has a small Calendar library, a program that uses it, and a test for it, so that every command has something to work on.
The pieces of this lesson
Tooling/
├── Rux.toml the program; depends on Calendar
├── Src/Main.rux
├── Calendar/ the code under test, a source library
│ ├── Rux.toml
│ └── Src/Calendar.rux
└── Tests/
└── LeapYear/ one test, an executable package
├── Rux.toml
└── Src/Main.rux
Calendar lives in its own library so that both the program and the test can depend on it. The program prints the length of February for five years:
for year in 1900..=1904 {
PrintLine("February {} has {} days", year, DaysInMonth(year, 2));
}
The commands
All of them run from the package folder, Tooling/:
| Command | What it does |
|---|---|
rux fmt | rewrites the sources and Rux.toml in the standard layout |
rux fmt --check | changes nothing; lists the files rux fmt would rewrite, and fails |
rux lint | warns about what compiles but is still wrong, such as missing docs |
rux test | builds and runs every executable package below Tests/ |
rux doc | writes reference pages for the pub items to Bin/Docs/ |
flowchart LR
edit["edit the code"] --> fmt["rux fmt"]
fmt --> lint["rux lint"]
lint --> test["rux test"]
test --> doc["rux doc"]
test -- "a test fails" --> editFormatting
rux fmt puts every file in the one standard layout, so a diff shows real changes and not someone's spacing habits. For Rux.toml that means the canonical order and spelling — sections in a fixed order, Description before Authors, spaces around every =. For source files in Rux 0.4.0 it is line-level clean-up: trailing spaces go and line endings are made consistent, while indentation is left as you wrote it.
--check turns formatting into a pass-or-fail question. It touches nothing, names each file that rux fmt would rewrite, and exits with status 1 if there is one. That is the form to use in a script or in continuous integration, where nobody wants the build to rewrite their files.
Testing
A test in Rux is an ordinary executable package below Tests/. rux test builds and runs each one, and a test passes when its Main returns 0. This lesson's test checks the leap-year rules with Assert from Core:
Assert(IsLeapYear(2024), "2024 is a leap year");
Assert(!IsLeapYear(2023), "2023 is not a leap year");
// A century is not a leap year, unless it divides by 400.
Assert(!IsLeapYear(1900), "1900 is not a leap year");
Assert(IsLeapYear(2000), "2000 is a leap year");
When an Assert condition is false, it prints its message and ends the program with a failing status, so a failed test says which check it was. Because a test is a package, it has its own Rux.toml, which depends on the code under test by path and on Core from the registry:
[Dependencies]
Calendar = { Path = "../../Calendar" }
Core = { Namespace = "Rux", Version = "*" }
Add more folders under Tests/ and rux test finds them by itself; there is no list to keep. If you break the rules in Calendar, the report shows which test failed and why — here, after an assertion that claims 2100 is a leap year (the timings and the exit code vary by machine):
Testing Tooling v0.1.0 (Debug, Windows x86-64)
Running 1 test
Failed LeapYear in 692 ms
note: test 'LeapYear' exited with code -1073741795
Output:
Assertion failed: 2100 is a leap year
at Main (Src/Main.rux:16:5)
Failed 1 test in 692 ms (0 passed, 1 failed)
Linting and documenting
rux lint and rux doc work as in Documentation. Calendar's functions are documented with @param and @returns, so its pages have something to show.
What this course never runs
rux pack and rux publish also exist, for sharing a source library through the package registry. They need an account and change something public, so this course stops short of them. The publishing guide covers them when you are ready.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// `rux run` and `rux check` are not the only commands. A few more keep a package tidy, and all of
// them are run from the package directory:
//
// rux fmt rewrite every source file and the manifest in the standard layout
// rux fmt --check change nothing; list the files that `rux fmt` would rewrite, and fail
// rux lint warn about what compiles but is still wrong, such as a `pub` item with no
// documentation or a misspelled documentation tag
// rux test build and run every executable package below `Tests/`
// rux doc generate reference pages for the `pub` items, in `Bin/Docs/`
//
// `--check` exists for scripts and continuous integration: it makes formatting a pass-or-fail
// question without touching anyone's files.
//
// This lesson has one test, in `Tests/LeapYear/`, for the small `Calendar` library beside `Src/`.
// The program below uses the same library, so a passing test says something about the program.
//
// `rux pack` and `rux publish` also exist, for sharing a source library through the registry. They
// need an account and change something public, so this course never runs them.
import Calendar::DaysInMonth;
import Io::PrintLine;
func Main() -> int {
for year in 1900..=1904 {
PrintLine("February {} has {} days", year, DaysInMonth(year, 2));
}
return 0;
}
Run it
cd Examples/Packages/Tooling
rux run
February 1900 has 28 days
February 1901 has 28 days
February 1902 has 28 days
February 1903 has 28 days
February 1904 has 29 days
The other commands, all run from this directory:
| Command | What it does |
|---|---|
rux fmt | Rewrites the sources and Rux.toml in the standard layout |
rux fmt --check | Changes nothing; fails if rux fmt would rewrite a file |
rux lint | Warns about what compiles but is still wrong, such as missing docs |
rux test | Builds and runs each package below Tests/; status 0 passes |
rux doc | Writes reference pages for the pub items to Bin/Docs/ |
rux test prints one line per test; the timings vary:
Testing Tooling v0.1.0 (Debug, Windows x86-64)
Running 1 test
Passed LeapYear in 427 ms
Passed 1 test in 427 ms (1 passed, 0 failed)
Common mistakes
A test passes when
Main returns 0, and nothing else is looked at. A test that prints "wrong!" and still returns 0 passes. Check with Assert, which ends the program with a failing status.A test is a package like any other. Remove the
Core line from Tests/LeapYear/Rux.toml and rux test reports Failed LeapYear, with the note "the test package did not compile" and the familiar package 'Core' is not listed in [Dependencies].rux fmt --check to fix anything.It only reports, for example
error: source file '…\Src\Main.rux' is not formatted, and exits with status 1. Run rux fmt to rewrite the files.Try it yourself
- Break
IsLeapYearinCalendar/Src/Calendar.ruxby removing theyear % 400 == 0part, and runrux test. Which assertion fails? Then runrux run— does the program's output show the bug? - Add a second test,
Tests/MonthLength/, that checksDaysInMonthfor January, April and December. Runrux testand count the tests. - Add a few spaces to the end of a line in
Src/Main.rux. Runrux fmt --check, thenecho $?, thenrux fmt, thenrux fmt --checkagain. - Run
rux doc --openinCalendar/. Does a source library get pages even though it cannot be built?
Learn more
rux fmt,rux lint,rux testandrux docin the CLI reference- Assert — the check every test is made of
- Publishing —
rux packandrux publish, for when you share a package - Source library — the kind of package
Calendaris
22.9 Documentation
Describe a public API with /// and /** */ comments and their tags, and generate reference pages from them with rux doc.
Overview
Decide what a program contains before it ever runs. Seven lessons on when, the target and build mode, source locations, #Error and #Warn, your own defines, and the intrinsic declarations behind Core.