-
Notifications
You must be signed in to change notification settings - Fork 543
created compiletest.md #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
f79e0d0
59485d2
5723bce
fc83b6f
84c2cd5
8751dfe
572eaa6
b1c4662
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
# `compiletest` | ||
## 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 failure or in some cases, even failure to compile. 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`](https://github.com/rust-lang-nursery/rustc-guide/blob/master/src/tests/intro.md) for additional background. | ||
|
||
The tests themselves are typically (but not always) organized into "suites"--for example, `run-pass`, a folder | ||
representing tests that should succeed, `run-fail`, a folder holding tests that should compile successfully, but return | ||
a failure (non-zero status), `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](https://github.com/rust-lang/rust/tree/master/src/tools/compiletest/src/common.rs) in the `pub struct Config` declaration. And a very good | ||
introduction to the different suites of compiler tests along with details about them can be found | ||
[here, at Brian Anderson's blog](https://brson.github.io/2017/07/10/how-rust-is-tested#s-ct). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should also link to the "Adding tests" chapter here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems a bit weird to me to provide the same link that we provide in the next paragraph... Plus, we're not even talking about adding tests in this paragraph. LMK if you'd still like to link to Adding tests here, and if you'd like it alongside or instead of the link to @brson's blog. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I think about it more, I suppose it's also weird to provide two different authoritative sources on a topic... ;) I think I'll just replace the brson link. LMK if you have other thoughts... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there sounds reasonable. There is already a link to brsons post in the other chapter, so let's just point to the other chapter everywhere. |
||
|
||
## Adding a new test file | ||
Briefly, simply create your new test in the appropriate location under [src/test](https://github.com/rust-lang/rust/tree/master/src/test). No registration of test files is necessary as | ||
`compiletest` will scan the [src/test](https://github.com/rust-lang/rust/tree/master/src/test) subfolder recursively, and will execute any Rust source files it finds as tests. | ||
See [`Adding new tests`](https://github.com/rust-lang-nursery/rustc-guide/blob/master/src/tests/adding.md) for a complete guide on how to adding 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 their inline counterparts, | ||
Error Info commands) are described more fully | ||
[here](https://github.com/rust-lang/rust/blob/master/src/test/COMPILER_TESTS.md). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be a link to the chapter from #47 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want a link in the doc to the PR or to wait until it lands and can be linked to under There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this would be best, unless you are under a time constraint. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you missed this one |
||
|
||
### Adding a new header command | ||
Header commands are defined in [src/tools/compiletest/src/header.rs](https://github.com/rust-lang/rust/tree/master/src/tools/compiletest/src/header.rs). At a high level, dozens of test properties are defined | ||
in the `TestProps` struct. These are all set to default values in the struct's `impl` block and any test can override this | ||
default value by specifying the property 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 at the time of this | ||
writing). The header command and the argument list (if present) are typically separated by a colon: | ||
``` | ||
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// 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](https://github.com/rust-lang/rust/tree/master/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](https://github.com/rust-lang/rust/tree/master/src/tools/compiletest/src/header.rs) (note the `Config` struct's declaration | ||
block is found in [src/tools/compiletest/src/common.rs](https://github.com/rust-lang/rust/tree/master/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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lol! I have never heard it called kebab-case before 😂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :) |
||
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 unneccessarily. | ||
|
||
As a concrete example, here is the implementation for the `parse_failure_status()` parser, in | ||
[src/tools/compiletest/src/header.rs](https://github.com/rust-lang/rust/tree/master/src/tools/compiletest/src/header.rs): | ||
```diff | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is kind of a cool use for diff formatting 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's nice, eh? |
||
@@ -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 hange implementation of one case, simply as an example. To implement `failure-status`, the `check_correct_failure_status()` function found in the `TestCx` implementation block, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/hange/change/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow. Something was definitely wrong with my 'c' finger that day! :) |
||
located in [src/tools/compiletest/src/runtest.rs](https://github.com/rust-lang/rust/tree/master/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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should also link to the chapter that will be added in #47
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want a link in the doc to the PR or to wait until it lands and can be linked to under
master
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be best, unless you are under a time constraint.