about summary refs log tree commit diff
path: root/src/doc/guide-testing.md
diff options
context:
space:
mode:
Diffstat (limited to 'src/doc/guide-testing.md')
-rw-r--r--src/doc/guide-testing.md262
1 files changed, 262 insertions, 0 deletions
diff --git a/src/doc/guide-testing.md b/src/doc/guide-testing.md
new file mode 100644
index 00000000000..b8f7cf97412
--- /dev/null
+++ b/src/doc/guide-testing.md
@@ -0,0 +1,262 @@
+% The Rust Testing Guide
+
+# Quick start
+
+To create test functions, add a `#[test]` attribute like this:
+
+~~~
+fn return_two() -> int {
+    2
+}
+
+#[test]
+fn return_two_test() {
+    let x = return_two();
+    assert!(x == 2);
+}
+~~~
+
+To run these tests, use `rustc --test`:
+
+~~~ {.notrust}
+$ rustc --test foo.rs; ./foo
+running 1 test
+test return_two_test ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+~~~
+
+`rustc foo.rs` will *not* compile the tests, since `#[test]` implies
+`#[cfg(test)]`. The `--test` flag to `rustc` implies `--cfg test`.
+
+
+# Unit testing in Rust
+
+Rust has built in support for simple unit testing. Functions can be
+marked as unit tests using the `test` attribute.
+
+~~~
+#[test]
+fn return_none_if_empty() {
+    // ... test code ...
+}
+~~~
+
+A test function's signature must have no arguments and no return
+value. To run the tests in a crate, it must be compiled with the
+`--test` flag: `rustc myprogram.rs --test -o myprogram-tests`. Running
+the resulting executable will run all the tests in the crate. A test
+is considered successful if its function returns; if the task running
+the test fails, through a call to `fail!`, a failed `check` or
+`assert`, or some other (`assert_eq`, ...) means, then the test fails.
+
+When compiling a crate with the `--test` flag `--cfg test` is also
+implied, so that tests can be conditionally compiled.
+
+~~~
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn return_none_if_empty() {
+      // ... test code ...
+    }
+}
+~~~
+
+Additionally `#[test]` items behave as if they also have the
+`#[cfg(test)]` attribute, and will not be compiled when the `--test` flag
+is not used.
+
+Tests that should not be run can be annotated with the `ignore`
+attribute. The existence of these tests will be noted in the test
+runner output, but the test will not be run. Tests can also be ignored
+by configuration so, for example, to ignore a test on windows you can
+write `#[ignore(cfg(target_os = "win32"))]`.
+
+Tests that are intended to fail can be annotated with the
+`should_fail` attribute. The test will be run, and if it causes its
+task to fail then the test will be counted as successful; otherwise it
+will be counted as a failure. For example:
+
+~~~
+#[test]
+#[should_fail]
+fn test_out_of_bounds_failure() {
+    let v: [int] = [];
+    v[0];
+}
+~~~
+
+A test runner built with the `--test` flag supports a limited set of
+arguments to control which tests are run: the first free argument
+passed to a test runner specifies a filter used to narrow down the set
+of tests being run; the `--ignored` flag tells the test runner to run
+only tests with the `ignore` attribute.
+
+## Parallelism
+
+By default, tests are run in parallel, which can make interpreting
+failure output difficult. In these cases you can set the
+`RUST_TEST_TASKS` environment variable to 1 to make the tests run
+sequentially.
+
+## Benchmarking
+
+The test runner also understands a simple form of benchmark execution.
+Benchmark functions are marked with the `#[bench]` attribute, rather
+than `#[test]`, and have a different form and meaning. They are
+compiled along with `#[test]` functions when a crate is compiled with
+`--test`, but they are not run by default. To run the benchmark
+component of your testsuite, pass `--bench` to the compiled test
+runner.
+
+The type signature of a benchmark function differs from a unit test:
+it takes a mutable reference to type `test::BenchHarness`. Inside the
+benchmark function, any time-variable or "setup" code should execute
+first, followed by a call to `iter` on the benchmark harness, passing
+a closure that contains the portion of the benchmark you wish to
+actually measure the per-iteration speed of.
+
+For benchmarks relating to processing/generating data, one can set the
+`bytes` field to the number of bytes consumed/produced in each
+iteration; this will used to show the throughput of the benchmark.
+This must be the amount used in each iteration, *not* the total
+amount.
+
+For example:
+
+~~~
+extern mod extra;
+use std::vec;
+
+#[bench]
+fn bench_sum_1024_ints(b: &mut extra::test::BenchHarness) {
+    let v = vec::from_fn(1024, |n| n);
+    b.iter(|| {v.iter().fold(0, |old, new| old + *new);} );
+}
+
+#[bench]
+fn initialise_a_vector(b: &mut extra::test::BenchHarness) {
+    b.iter(|| {vec::from_elem(1024, 0u64);} );
+    b.bytes = 1024 * 8;
+}
+~~~
+
+The benchmark runner will calibrate measurement of the benchmark
+function to run the `iter` block "enough" times to get a reliable
+measure of the per-iteration speed.
+
+Advice on writing benchmarks:
+
+  - Move setup code outside the `iter` loop; only put the part you
+    want to measure inside
+  - Make the code do "the same thing" on each iteration; do not
+    accumulate or change state
+  - Make the outer function idempotent too; the benchmark runner is
+    likely to run it many times
+  - Make the inner `iter` loop short and fast so benchmark runs are
+    fast and the calibrator can adjust the run-length at fine
+    resolution
+  - Make the code in the `iter` loop do something simple, to assist in
+    pinpointing performance improvements (or regressions)
+
+To run benchmarks, pass the `--bench` flag to the compiled
+test-runner. Benchmarks are compiled-in but not executed by default.
+
+## Examples
+
+### Typical test run
+
+~~~ {.notrust}
+> mytests
+
+running 30 tests
+running driver::tests::mytest1 ... ok
+running driver::tests::mytest2 ... ignored
+... snip ...
+running driver::tests::mytest30 ... ok
+
+result: ok. 28 passed; 0 failed; 2 ignored
+~~~ {.notrust}
+
+### Test run with failures
+
+~~~ {.notrust}
+> mytests
+
+running 30 tests
+running driver::tests::mytest1 ... ok
+running driver::tests::mytest2 ... ignored
+... snip ...
+running driver::tests::mytest30 ... FAILED
+
+result: FAILED. 27 passed; 1 failed; 2 ignored
+~~~
+
+### Running ignored tests
+
+~~~ {.notrust}
+> mytests --ignored
+
+running 2 tests
+running driver::tests::mytest2 ... failed
+running driver::tests::mytest10 ... ok
+
+result: FAILED. 1 passed; 1 failed; 0 ignored
+~~~
+
+### Running a subset of tests
+
+~~~ {.notrust}
+> mytests mytest1
+
+running 11 tests
+running driver::tests::mytest1 ... ok
+running driver::tests::mytest10 ... ignored
+... snip ...
+running driver::tests::mytest19 ... ok
+
+result: ok. 11 passed; 0 failed; 1 ignored
+~~~
+
+### Running benchmarks
+
+~~~ {.notrust}
+> mytests --bench
+
+running 2 tests
+test bench_sum_1024_ints ... bench: 709 ns/iter (+/- 82)
+test initialise_a_vector ... bench: 424 ns/iter (+/- 99) = 19320 MB/s
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured
+~~~
+
+## Saving and ratcheting metrics
+
+When running benchmarks or other tests, the test runner can record
+per-test "metrics". Each metric is a scalar `f64` value, plus a noise
+value which represents uncertainty in the measurement. By default, all
+`#[bench]` benchmarks are recorded as metrics, which can be saved as
+JSON in an external file for further reporting.
+
+In addition, the test runner supports _ratcheting_ against a metrics
+file. Ratcheting is like saving metrics, except that after each run,
+if the output file already exists the results of the current run are
+compared against the contents of the existing file, and any regression
+_causes the testsuite to fail_. If the comparison passes -- if all
+metrics stayed the same (within noise) or improved -- then the metrics
+file is overwritten with the new values. In this way, a metrics file
+in your workspace can be used to ensure your work does not regress
+performance.
+
+Test runners take 3 options that are relevant to metrics:
+
+  - `--save-metrics=<file.json>` will save the metrics from a test run
+    to `file.json`
+  - `--ratchet-metrics=<file.json>` will ratchet the metrics against
+    the `file.json`
+  - `--ratchet-noise-percent=N` will override the noise measurements
+    in `file.json`, and consider a metric change less than `N%` to be
+    noise. This can be helpful if you are testing in a noisy
+    environment where the benchmark calibration loop cannot acquire a
+    clear enough signal.