refactor(traverse): speed up tests (#4538)

Closes #4537.

`oxc_traverse`'s tests to ensure code which would be undefined behavior will not compile were using `trybuild`. This is a thorough way to run these tests, but requires running a lengthy compilation each time tests run.

Implement these tests as `compile_fail` doc tests instead. This is not quite as thorough - now only testing that they don't compile, rather than also *why* they don't compile - but acceptable given the outsized cost of doing it "properly".
This commit is contained in:
overlookmotel 2024-07-29 21:30:13 +00:00
parent d6974d4ff7
commit e6a8af6112
12 changed files with 86 additions and 178 deletions

76
Cargo.lock generated
View file

@ -1847,7 +1847,6 @@ dependencies = [
"oxc_semantic",
"oxc_span",
"oxc_syntax",
"trybuild",
]
[[package]]
@ -2449,15 +2448,6 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
dependencies = [
"serde",
]
[[package]]
name = "sha2"
version = "0.10.8"
@ -2586,15 +2576,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.16.1"
@ -2713,40 +2694,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tower"
version = "0.4.13"
@ -2864,20 +2811,6 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "trybuild"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b55265878356bdd85c9baa15859c87de93b2bf1f33acf752040a561e4a228f62"
dependencies = [
"glob",
"serde",
"serde_derive",
"serde_json",
"termcolor",
"toml",
]
[[package]]
name = "tsify"
version = "0.4.5"
@ -3296,15 +3229,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
dependencies = [
"memchr",
]
[[package]]
name = "yansi"
version = "1.0.1"

View file

@ -178,7 +178,6 @@ textwrap = "0.16.1"
tokio = "1.38.0"
tower-lsp = "0.20.0"
tracing-subscriber = "0.3.18"
trybuild = "1.0.96"
tsify = "0.4.5"
unicode-id-start = "1" # Relaxed version so the user can decide which unicode version to use.
unicode-width = "0.1.13"

View file

@ -17,7 +17,9 @@ include = ["/src"]
workspace = true
[lib]
test = false
test = false
# Doctests must be enabled for this crate as they are used to run tests
# which check the soundness of its code
doctest = true
[dependencies]
@ -29,6 +31,3 @@ oxc_syntax = { workspace = true }
compact_str = { workspace = true }
memoffset = { workspace = true }
[dev-dependencies]
trybuild = { workspace = true }

View file

@ -0,0 +1,81 @@
#![cfg(doctest)]
//! Tests to ensure lifetimes prevent references to escape visitor functions.
//! If they could, it'd allow aliasing, which would be undefined behavior.
//!
//! These tests were originally implemented using `trybuild` crate,
//! but it disproportionately hurt time taken to run tests.
//! So using `compile_fail` doc tests instead.
//! <https://github.com/oxc-project/oxc/issues/4537>
/**
```compile_fail
use oxc_ast::ast::IdentifierReference;
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
struct Trans<'a, 'b> {
ancestor: Option<&'b Ancestor<'a>>,
}
impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
fn enter_identifier_reference(
&mut self,
_node: &mut IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.ancestor = Some(ctx.parent());
}
}
```
*/
const CANNOT_HOLD_ONTO_ANCESTOR: () = ();
/**
```compile_fail
use oxc_ast::ast::IdentifierReference;
use oxc_traverse::{ancestor::ProgramWithoutDirectives, Ancestor, Traverse, TraverseCtx};
struct Trans<'a, 'b> {
program: Option<&'b ProgramWithoutDirectives<'a>>,
}
impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
fn enter_identifier_reference(
&mut self,
_node: &mut IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if let Ancestor::ProgramDirectives(program) = ctx.parent() {
self.program = Some(program);
}
}
}
```
*/
const CANNOT_HOLD_ONTO_ANCESTOR_NODE: () = ();
/**
```compile_fail
use oxc_ast::ast::{IdentifierReference, Statement};
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
struct Trans<'a, 'b> {
stmt: Option<&'b Statement<'a>>,
}
impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
fn enter_identifier_reference(
&mut self,
_node: &mut IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if let Ancestor::ProgramDirectives(program) = ctx.parent() {
let body = program.body();
let stmt = &body[0];
self.stmt = Some(stmt);
}
}
}
```
*/
const CANNOT_HOLD_ONTO_AST_NODE: () = ();

View file

@ -73,6 +73,8 @@ mod traverse;
pub use traverse::Traverse;
mod walk;
mod compile_fail_tests;
/// Traverse AST with a [`Traverse`] impl.
///
/// This allows:

View file

@ -1,5 +0,0 @@
#[test]
fn compile_fail() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/*.rs");
}

View file

@ -1,18 +0,0 @@
use oxc_ast::ast::IdentifierReference;
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
struct Trans<'a, 'b> {
ancestor: Option<&'b Ancestor<'a>>,
}
impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
fn enter_identifier_reference(
&mut self,
_node: &mut IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.ancestor = Some(ctx.parent());
}
}
fn main() {}

View file

@ -1,11 +0,0 @@
error: lifetime may not live long enough
--> tests/compile_fail/ancestor_lifetime1.rs:14:9
|
8 | impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
| -- lifetime `'b` defined here
...
12 | ctx: &mut TraverseCtx<'a>,
| - let's call the lifetime of this reference `'1`
13 | ) {
14 | self.ancestor = Some(ctx.parent());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'b`

View file

@ -1,20 +0,0 @@
use oxc_ast::ast::IdentifierReference;
use oxc_traverse::{ancestor::ProgramWithoutDirectives, Ancestor, Traverse, TraverseCtx};
struct Trans<'a, 'b> {
program: Option<&'b ProgramWithoutDirectives<'a>>,
}
impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
fn enter_identifier_reference(
&mut self,
_node: &mut IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if let Ancestor::ProgramDirectives(program) = ctx.parent() {
self.program = Some(program);
}
}
}
fn main() {}

View file

@ -1,11 +0,0 @@
error: lifetime may not live long enough
--> tests/compile_fail/ancestor_lifetime2.rs:15:13
|
8 | impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
| -- lifetime `'b` defined here
...
12 | ctx: &mut TraverseCtx<'a>,
| - let's call the lifetime of this reference `'1`
...
15 | self.program = Some(program);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'b`

View file

@ -1,21 +0,0 @@
use oxc_allocator::Vec;
use oxc_ast::ast::{IdentifierReference, Statement};
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
struct Trans<'a, 'b> {
program_body: Option<&'b Vec<'a, Statement<'a>>>,
}
impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
fn enter_identifier_reference(
&mut self,
_node: &mut IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if let Ancestor::ProgramDirectives(program) = ctx.parent() {
self.program_body = Some(program.body());
}
}
}
fn main() {}

View file

@ -1,11 +0,0 @@
error: lifetime may not live long enough
--> tests/compile_fail/ancestor_lifetime3.rs:16:13
|
9 | impl<'a, 'b> Traverse<'a> for Trans<'a, 'b> {
| -- lifetime `'b` defined here
...
13 | ctx: &mut TraverseCtx<'a>,
| - let's call the lifetime of this reference `'1`
...
16 | self.program_body = Some(program.body());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'b`