blob: 2e0bcec538bda72ebd42197ffb972970179bddb9 [file] [log] [blame] [view]
# Using `compiletest` commands to control test execution
## Introduction
`compiletest` is the main test harness of the Rust test suite. It allows
test authors to organize large numbers of tests (the Rust compiler has many
thousands), efficient test execution (parallel execution is supported), and
allows the test author to configure behavior and expected results of both
individual and groups of tests.
`compiletest` tests may check test code for success, for runtime failure, or for
compile-time failure. Tests are typically organized as a Rust source file with
annotations in comments before and/or within the test code, which serve to
direct `compiletest` on if or how to run the test, what behavior to expect,
and more. If you are unfamiliar with the compiler testing framework,
see [this chapter](./tests/intro.md) for additional background.
The tests themselves are typically (but not always) organized into
"suites" for example, `run-fail`, a folder holding tests that should compile
successfully, but return a failure (non-zero status) at runtime, `compile-fail`,
a folder holding tests that should fail to compile, and many more. The various
suites are defined in [`src/tools/compiletest/src/common.rs`] in the
`pub enum Mode` declaration. And a good introduction to the different
suites of compiler tests along with details about them can be found in
[Adding new tests](./tests/adding.md).
## Adding a new test file
Briefly, simply create your new test in the appropriate location under
[`src/test`]. No registration of test files is necessary as `compiletest`
will scan the [`src/test`] subfolder recursively, and will execute any
Rust source files it finds as tests.
See [Adding new tests](./tests/adding.md) for a complete guide on how to add
new tests.
## Header Commands
Source file annotations which appear in comments near the top of the source
file *before* any test code are known as header commands. These commands can
instruct `compiletest` to ignore this test, set expectations on whether it is
expected to succeed at compiling, or what the test's return code is expected to
be. Header commands and inline `//~ ERROR` commands are described more fully
[here](./tests/adding.md#header-commands-configuring-rustc).
### Adding a new header command
Header commands are defined in the `TestProps` struct in
[`src/tools/compiletest/src/header.rs`]. At a high level, there are
dozens of test properties defined here, all set to default values in the
`TestProp` struct's `impl` block. Any test can override this default value by
specifying the property in question as header command as a comment (`//`) in
the test source file, before any source code.
#### Using a header command
Here is an example, specifying the `must-compile-successfully` header command,
which takes no arguments, followed by the `failure-status` header command,
which takes a single argument (which, in this case is a value of 1).
`failure-status` is instructing `compiletest` to expect a failure status of 1
(rather than the current Rust default of 101). The header command and
the argument list (if present) are typically separated by a colon:
```rust,ignore
// must-compile-successfully
// failure-status: 1
#![feature(termination_trait)]
use std::io::{Error, ErrorKind};
fn main() -> Result<(), Box<Error>> {
Err(Box::new(Error::new(ErrorKind::Other, "returned Box<Error> from main()")))
}
```
#### Adding a new header command property
One would add a new header command if there is a need to define some test
property or behavior on an individual, test-by-test basis. A header command
property serves as the header command's backing store (holds the command's
current value) at runtime.
To add a new header command property:
1. Look for the `pub struct TestProps` declaration in
[`src/tools/compiletest/src/header.rs`] and add the new public property to
the end of the declaration.
2. Look for the `impl TestProps` implementation block immediately following
the struct declaration and initialize the new property to its default
value.
#### Adding a new header command parser
When `compiletest` encounters a test file, it parses the file a line at a time
by calling every parser defined in the `Config` struct's implementation block,
also in [`src/tools/compiletest/src/header.rs`][] (note that the `Config`
struct's declaration block is found in [`src/tools/compiletest/src/common.rs`]).
`TestProps`'s `load_from()` method will try passing the current line of text to
each parser, which, in turn typically checks to see if the line begins with a
particular commented (`//`) header command such as `// must-compile-successfully`
or `// failure-status`. Whitespace after the comment marker is optional.
Parsers will override a given header command property's default value merely by
being specified in the test file as a header command or by having a parameter
value specified in the test file, depending on the header command.
Parsers defined in `impl Config` are typically named `parse_<header_command>`
(note kebab-case `<header-command>` transformed to snake-case
`<header_command>`). `impl Config` also defines several 'low-level' parsers
which make it simple to parse common patterns like simple presence or not
(`parse_name_directive()`), header-command:parameter(s)
(`parse_name_value_directive()`), optional parsing only if a particular `cfg`
attribute is defined (`has_cfg_prefix()`) and many more. The low-level parsers
are found near the end of the `impl Config` block; be sure to look through them
and their associated parsers immediately above to see how they are used to
avoid writing additional parsing code unnecessarily.
As a concrete example, here is the implementation for the
`parse_failure_status()` parser, in [`src/tools/compiletest/src/header.rs`]:
```diff
@@ -232,6 +232,7 @@ pub struct TestProps {
// customized normalization rules
pub normalize_stdout: Vec<(String, String)>,
pub normalize_stderr: Vec<(String, String)>,
+ pub failure_status: i32,
}
impl TestProps {
@@ -260,6 +261,7 @@ impl TestProps {
run_pass: false,
normalize_stdout: vec![],
normalize_stderr: vec![],
+ failure_status: 101,
}
}
@@ -383,6 +385,10 @@ impl TestProps {
if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stderr") {
self.normalize_stderr.push(rule);
}
+
+ if let Some(code) = config.parse_failure_status(ln) {
+ self.failure_status = code;
+ }
});
for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
@@ -488,6 +494,13 @@ impl Config {
self.parse_name_directive(line, "pretty-compare-only")
}
+ fn parse_failure_status(&self, line: &str) -> Option<i32> {
+ match self.parse_name_value_directive(line, "failure-status") {
+ Some(code) => code.trim().parse::<i32>().ok(),
+ _ => None,
+ }
+ }
```
## Implementing the behavior change
When a test invokes a particular header command, it is expected that some
behavior will change as a result. What behavior, obviously, will depend on the
purpose of the header command. In the case of `failure-status`, the behavior
that changes is that `compiletest` expects the failure code defined by the
header command invoked in the test, rather than the default value.
Although specific to `failure-status` (as every header command will have a
different implementation in order to invoke behavior change) perhaps it is
helpful to see the behavior change implementation of one case, simply as an
example. To implement `failure-status`, the `check_correct_failure_status()`
function found in the `TestCx` implementation block, located in
[`src/tools/compiletest/src/runtest.rs`], was modified as per below:
```diff
@@ -295,11 +295,14 @@ impl<'test> TestCx<'test> {
}
fn check_correct_failure_status(&self, proc_res: &ProcRes) {
- // The value the rust runtime returns on failure
- const RUST_ERR: i32 = 101;
- if proc_res.status.code() != Some(RUST_ERR) {
+ let expected_status = Some(self.props.failure_status);
+ let received_status = proc_res.status.code();
+
+ if expected_status != received_status {
self.fatal_proc_rec(
- &format!("failure produced the wrong error: {}", proc_res.status),
+ &format!("Error: expected failure status ({:?}) but received status {:?}.",
+ expected_status,
+ received_status),
proc_res,
);
}
@@ -320,7 +323,6 @@ impl<'test> TestCx<'test> {
);
let proc_res = self.exec_compiled_test();
-
if !proc_res.status.success() {
self.fatal_proc_rec("test run failed!", &proc_res);
}
@@ -499,7 +501,6 @@ impl<'test> TestCx<'test> {
expected,
actual
);
- panic!();
}
}
```
Note the use of `self.props.failure_status` to access the header command
property. In tests which do not specify the failure status header command,
`self.props.failure_status` will evaluate to the default value of 101 at the
time of this writing. But for a test which specifies a header command of, for
example, `// failure-status: 1`, `self.props.failure_status` will evaluate to
1, as `parse_failure_status()` will have overridden the `TestProps` default
value, for that test specifically.
[`src/test`]: https://github.com/rust-lang/rust/tree/master/src/test
[`src/tools/compiletest/src/header.rs`]: https://github.com/rust-lang/rust/tree/master/src/tools/compiletest/src/header.rs
[`src/tools/compiletest/src/common.rs`]: https://github.com/rust-lang/rust/tree/master/src/tools/compiletest/src/common.rs
[`src/tools/compiletest/src/runtest.rs`]: https://github.com/rust-lang/rust/tree/master/src/tools/compiletest/src/runtest.rs