mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Enhance the test runner: regex, --list, --path (#3945)
This commit is contained in:
parent
c5c73ec931
commit
1b091d628d
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2780,6 +2780,7 @@ dependencies = [
|
|||||||
"oxipng",
|
"oxipng",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"regex",
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"typst",
|
"typst",
|
||||||
|
@ -25,6 +25,7 @@ once_cell = { workspace = true }
|
|||||||
oxipng = { workspace = true }
|
oxipng = { workspace = true }
|
||||||
parking_lot = { workspace = true }
|
parking_lot = { workspace = true }
|
||||||
rayon = { workspace = true }
|
rayon = { workspace = true }
|
||||||
|
regex = { workspace = true }
|
||||||
tiny-skia = { workspace = true }
|
tiny-skia = { workspace = true }
|
||||||
ttf-parser = { workspace = true }
|
ttf-parser = { workspace = true }
|
||||||
unscanny = { workspace = true }
|
unscanny = { workspace = true }
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
## Directory structure
|
## Directory structure
|
||||||
Top level directory structure:
|
Top level directory structure:
|
||||||
- `src`: Testing code.
|
- `src`: Testing code.
|
||||||
- `suite`: Input files. Mostly organize in parallel to the code.
|
- `suite`: Input files. Mostly organized in parallel to the code. Each file can
|
||||||
|
contain multiple tests, each of which is a section of Typst code
|
||||||
|
following `--- {name} ---`.
|
||||||
- `ref`: Reference images which the output is compared with to determine whether
|
- `ref`: Reference images which the output is compared with to determine whether
|
||||||
a test passed or failed.
|
a test passed or failed.
|
||||||
- `store`: Store for PNG, PDF, and SVG output files produced by the tests.
|
- `store`: Store for PNG, PDF, and SVG output files produced by the tests.
|
||||||
@ -19,17 +21,27 @@ Running just the integration tests (the tests in this directory):
|
|||||||
cargo test --workspace --test tests
|
cargo test --workspace --test tests
|
||||||
```
|
```
|
||||||
|
|
||||||
You may want to [make yourself an alias](#making-an-alias) like:
|
You may want to [make yourself an alias](#making-an-alias) `testit` so that you can
|
||||||
|
write shorter commands. In the examples below, we will use this alias.
|
||||||
|
|
||||||
|
Running all tests with the given name pattern. You can use
|
||||||
|
[regular expression](https://docs.rs/regex/latest/regex/)s.
|
||||||
```bash
|
```bash
|
||||||
testit
|
testit math # The name has "math" anywhere
|
||||||
|
testit math page # The name has "math" or "page" anywhere
|
||||||
|
testit "^math" "^page" # The name begins with "math" or "page"
|
||||||
|
testit "^(math|page)" # Same as above.
|
||||||
```
|
```
|
||||||
|
|
||||||
Running all tests whose names contain the string `page` or `stack`. Note each
|
Running all tests discovered under given paths:
|
||||||
`.typ` file in this directory can contain multiple tests, each of which is a
|
|
||||||
section of Typst code following `--- {name} ---`.
|
|
||||||
```bash
|
```bash
|
||||||
# Add --verbose to list which tests were run.
|
testit -p tests/suite/math/attach.typ
|
||||||
testit page stack
|
testit -p tests/suite/model -p tests/suite/text
|
||||||
|
```
|
||||||
|
|
||||||
|
Running tests that begin with `issue` under a given path:
|
||||||
|
```bash
|
||||||
|
testit "^issue" -p tests/suite/model
|
||||||
```
|
```
|
||||||
|
|
||||||
Running a test with the exact test name `math-attach-mixed`.
|
Running a test with the exact test name `math-attach-mixed`.
|
||||||
@ -37,6 +49,11 @@ Running a test with the exact test name `math-attach-mixed`.
|
|||||||
testit --exact math-attach-mixed
|
testit --exact math-attach-mixed
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You may find more options in the help message:
|
||||||
|
```bash
|
||||||
|
testit --help
|
||||||
|
```
|
||||||
|
|
||||||
To make the integration tests go faster they don't generate PDFs by default.
|
To make the integration tests go faster they don't generate PDFs by default.
|
||||||
Pass the `--pdf` flag to generate those. Mind that PDFs are not tested
|
Pass the `--pdf` flag to generate those. Mind that PDFs are not tested
|
||||||
automatically at the moment, so you should always check the output manually when
|
automatically at the moment, so you should always check the output manually when
|
||||||
@ -56,12 +73,12 @@ There are, broadly speaking, three kinds of tests:
|
|||||||
use of `test` or `assert.eq` (both are very similar, `test` is just shorter)
|
use of `test` or `assert.eq` (both are very similar, `test` is just shorter)
|
||||||
to ensure certain properties hold when executing the Typst code.
|
to ensure certain properties hold when executing the Typst code.
|
||||||
|
|
||||||
- Tests that ensure the code fails with a particular error: Those have inline
|
- Tests that ensure the code emits particular diagnostic messages: Those have
|
||||||
annotations like `// Error: 2-7 thing was wrong`. An annotation can be
|
inline annotations like `// Error: 2-7 thing was wrong`. An annotation can
|
||||||
either an "Error", a "Warning", or a "Hint". The range designates where
|
start with either "Error", "Warning", or "Hint". The range designates the
|
||||||
in the next non-comment line the error is and after it follows the message.
|
code span the diagnostic message refers to in the first non-comment line
|
||||||
If you the error is in a line further below, you can also write ranges like
|
below. If the code span is in a line further below, you can write ranges
|
||||||
`3:2-3:7` to indicate the 2-7 column in the 3rd non-comment line.
|
like `3:2-3:7` to indicate the 2-7 column in the 3rd non-comment line.
|
||||||
|
|
||||||
- Tests that ensure certain visual output is produced: Those render the result
|
- Tests that ensure certain visual output is produced: Those render the result
|
||||||
of the test with the `typst-render` crate and compare against a reference
|
of the test with the `typst-render` crate and compare against a reference
|
||||||
@ -82,7 +99,7 @@ If you created a new test or fixed a bug in an existing test, you need to update
|
|||||||
the reference image used for comparison. For this, you can use the `--update`
|
the reference image used for comparison. For this, you can use the `--update`
|
||||||
flag:
|
flag:
|
||||||
```bash
|
```bash
|
||||||
testit mytest --update
|
testit --exact my-test-name --update
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use the VS Code test helper extension (see the `tools` folder), you can
|
If you use the VS Code test helper extension (see the `tools` folder), you can
|
||||||
@ -92,7 +109,7 @@ alternatively use the save button to update the reference image.
|
|||||||
If you want to have a quicker way to run the tests, consider adding a shortcut
|
If you want to have a quicker way to run the tests, consider adding a shortcut
|
||||||
to your shell profile so that you can simply write something like:
|
to your shell profile so that you can simply write something like:
|
||||||
```bash
|
```bash
|
||||||
testit empty.typ
|
testit --exact my-test-name
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bash
|
### Bash
|
||||||
|
@ -1,19 +1,30 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
/// Typst's test runner.
|
/// Typst's test runner.
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
|
#[command(bin_name = "cargo test --workspace --test tests --")]
|
||||||
#[clap(name = "typst-test", author)]
|
#[clap(name = "typst-test", author)]
|
||||||
pub struct CliArguments {
|
pub struct CliArguments {
|
||||||
/// The command to run.
|
/// The command to run.
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Option<Command>,
|
pub command: Option<Command>,
|
||||||
/// All the tests that contain the filter string will be run.
|
/// All the tests whose names match the test name pattern will be run.
|
||||||
pub filter: Vec<String>,
|
#[arg(value_parser = Regex::new)]
|
||||||
/// Runs only the tests with the exact specified `filter` names.
|
pub pattern: Vec<Regex>,
|
||||||
|
/// Restricts test selection within the given path.
|
||||||
|
#[arg(short, long, value_parser = |s: &str| PathBuf::from(s).canonicalize())]
|
||||||
|
pub path: Vec<PathBuf>,
|
||||||
|
/// Only selects the test that matches with the test name verbatim.
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub exact: bool,
|
pub exact: bool,
|
||||||
/// Whether to update the reference images of non-passing tests.
|
/// Lists what tests will be run, without actually running them.
|
||||||
#[arg(short, long)]
|
#[arg(long, group = "action")]
|
||||||
|
pub list: bool,
|
||||||
|
/// Updates the reference images of non-passing tests.
|
||||||
|
#[arg(short, long, group = "action")]
|
||||||
pub update: bool,
|
pub update: bool,
|
||||||
/// The scaling factor to render the output image with.
|
/// The scaling factor to render the output image with.
|
||||||
///
|
///
|
||||||
@ -26,7 +37,7 @@ pub struct CliArguments {
|
|||||||
/// Exports SVG outputs into the artifact store.
|
/// Exports SVG outputs into the artifact store.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub svg: bool,
|
pub svg: bool,
|
||||||
/// Whether to display the syntax tree.
|
/// Displays the syntax tree.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub syntax: bool,
|
pub syntax: bool,
|
||||||
/// Prevents the terminal from being cleared of test names.
|
/// Prevents the terminal from being cleared of test names.
|
||||||
|
@ -257,7 +257,7 @@ impl<'a> Parser<'a> {
|
|||||||
self.collector.large.insert(name.clone());
|
self.collector.large.insert(name.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filtered(&name) {
|
if !selected(&name, self.path.canonicalize().unwrap()) {
|
||||||
self.collector.skipped += 1;
|
self.collector.skipped += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -383,14 +383,23 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a test is within the filtered set.
|
/// Whether a test is within the selected set to run.
|
||||||
fn filtered(name: &str) -> bool {
|
fn selected(name: &str, abs: PathBuf) -> bool {
|
||||||
|
let paths = &crate::ARGS.path;
|
||||||
|
if !paths.is_empty() && !paths.iter().any(|path| abs.starts_with(path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let exact = crate::ARGS.exact;
|
let exact = crate::ARGS.exact;
|
||||||
let filter = &crate::ARGS.filter;
|
let patterns = &crate::ARGS.pattern;
|
||||||
filter.is_empty()
|
patterns.is_empty()
|
||||||
|| filter
|
|| patterns.iter().any(|pattern: ®ex::Regex| {
|
||||||
.iter()
|
if exact {
|
||||||
.any(|v| if exact { name == v } else { name.contains(v) })
|
name == pattern.as_str()
|
||||||
|
} else {
|
||||||
|
pattern.is_match(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error in a test file.
|
/// An error in a test file.
|
||||||
|
@ -6,7 +6,7 @@ use crate::run::TestResult;
|
|||||||
|
|
||||||
/// Receives status updates by individual test runs.
|
/// Receives status updates by individual test runs.
|
||||||
pub struct Logger<'a> {
|
pub struct Logger<'a> {
|
||||||
filtered: usize,
|
selected: usize,
|
||||||
passed: usize,
|
passed: usize,
|
||||||
failed: usize,
|
failed: usize,
|
||||||
skipped: usize,
|
skipped: usize,
|
||||||
@ -19,9 +19,9 @@ pub struct Logger<'a> {
|
|||||||
|
|
||||||
impl<'a> Logger<'a> {
|
impl<'a> Logger<'a> {
|
||||||
/// Create a new logger.
|
/// Create a new logger.
|
||||||
pub fn new(filtered: usize, skipped: usize) -> Self {
|
pub fn new(selected: usize, skipped: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
filtered,
|
selected,
|
||||||
passed: 0,
|
passed: 0,
|
||||||
failed: 0,
|
failed: 0,
|
||||||
skipped,
|
skipped,
|
||||||
@ -86,10 +86,10 @@ impl<'a> Logger<'a> {
|
|||||||
|
|
||||||
/// Prints a summary and returns whether the test suite passed.
|
/// Prints a summary and returns whether the test suite passed.
|
||||||
pub fn finish(&self) -> bool {
|
pub fn finish(&self) -> bool {
|
||||||
let Self { filtered, passed, failed, skipped, .. } = *self;
|
let Self { selected, passed, failed, skipped, .. } = *self;
|
||||||
|
|
||||||
eprintln!("{passed} passed, {failed} failed, {skipped} skipped");
|
eprintln!("{passed} passed, {failed} failed, {skipped} skipped");
|
||||||
assert_eq!(filtered, passed + failed, "not all tests were executed succesfully");
|
assert_eq!(selected, passed + failed, "not all tests were executed succesfully");
|
||||||
|
|
||||||
if self.mismatched_image {
|
if self.mismatched_image {
|
||||||
eprintln!(" pass the --update flag to update the reference images");
|
eprintln!(" pass the --update flag to update the reference images");
|
||||||
@ -121,7 +121,7 @@ impl<'a> Logger<'a> {
|
|||||||
|
|
||||||
// Print the status line.
|
// Print the status line.
|
||||||
let done = self.failed + self.passed;
|
let done = self.failed + self.passed;
|
||||||
if done < self.filtered {
|
if done < self.selected {
|
||||||
if self.last_change.elapsed() > Duration::from_secs(2) {
|
if self.last_change.elapsed() > Duration::from_secs(2) {
|
||||||
for test in &self.active {
|
for test in &self.active {
|
||||||
writeln!(out, "⏰ {test} is taking a long time ...")?;
|
writeln!(out, "⏰ {test} is taking a long time ...")?;
|
||||||
@ -131,7 +131,7 @@ impl<'a> Logger<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.terminal {
|
if self.terminal {
|
||||||
writeln!(out, "💨 {done} / {}", self.filtered)?;
|
writeln!(out, "💨 {done} / {}", self.selected)?;
|
||||||
self.temp_lines += 1;
|
self.temp_lines += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,14 +72,20 @@ fn test() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let filtered = tests.len();
|
let selected = tests.len();
|
||||||
if filtered == 0 {
|
if ARGS.list {
|
||||||
|
for test in tests.iter() {
|
||||||
|
println!("{test}");
|
||||||
|
}
|
||||||
|
eprintln!("{selected} selected, {skipped} skipped");
|
||||||
|
return;
|
||||||
|
} else if selected == 0 {
|
||||||
eprintln!("no test selected");
|
eprintln!("no test selected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the tests.
|
// Run the tests.
|
||||||
let logger = Mutex::new(Logger::new(filtered, skipped));
|
let logger = Mutex::new(Logger::new(selected, skipped));
|
||||||
std::thread::scope(|scope| {
|
std::thread::scope(|scope| {
|
||||||
let logger = &logger;
|
let logger = &logger;
|
||||||
let (sender, receiver) = std::sync::mpsc::channel();
|
let (sender, receiver) = std::sync::mpsc::channel();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user