Problem statement
Whitaker currently fails to recognize test functions declared with #[rstest] or #[rstest_parametrize] as test-only code. That causes false positives in lints that intentionally forbid patterns such as .expect() or .unwrap() outside tests.
This is a Whitaker bug or gap in test-context detection, not an rstest bug.
Current behavior
Given code like this:
#[rstest]
#[case::syntactic(LockFailureKind::Syntactic, "syntax error")]
#[case::semantic(LockFailureKind::Semantic, "type error")]
fn content_transaction_rejects_lock_failure(
#[case] kind: LockFailureKind,
#[case] message: &str,
) {
let dir = TempDir::new().expect("temp dir");
// ...
}
Whitaker reports .expect() as if it were being called outside test-only code.
Example diagnostic:
error: Avoid calling expect on `std::result::Result<tempfile::TempDir, std::io::Error>` outside test-only code.
--> crates/weaverd/src/safety_harness/transaction/content_transaction_tests.rs:62:15
|
62 | let dir = TempDir::new().expect("temp dir");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: The call originates within function `content_transaction_rejects_lock_failure` which is not recognised as a test.
That diagnosis is wrong. The function is test code.
Root cause
Whitaker’s current test-detection logic appears to rely on patterns such as:
#[test]
- well-known async test attributes such as
#[tokio::test]
- possibly naming heuristics such as
test_*
That misses rstest, because #[rstest] and related forms are procedural macro attributes that imply test semantics without necessarily exposing a plain #[test] attribute at the AST stage Whitaker inspects.
In other words, Whitaker currently treats “is this syntactically a standard Rust test?” as too close to “is this test-only code?”. Those are not equivalent.
Expected behavior
Whitaker should classify functions as test-only code when they are annotated with rstest test macros, including at least:
-
#[rstest]
-
#[rstest_parametrize]
It may also be worth considering whether supporting associated rstest patterns such as #[case::...] improves robustness, but the primary detection should sit on the function-level attributes.
For the example above, Whitaker should not emit no_expect_outside_tests or similar diagnostics.
Scope
This affects any Whitaker lint that needs to distinguish production code from test-only code, including but not limited to:
no_expect_outside_tests
no_unwrap_outside_tests
- any future lint that grants more latitude inside tests
It also matters for projects using:
rstest
rstest-bdd
- other proc-macro-based test frameworks with nonstandard test markers
Why Whitaker should handle this
It is reasonable for lint tools to recognize widely used test frameworks directly. Requiring every upstream test framework to emit custom tooling hints, or requiring every downstream project to configure special cases, creates unnecessary friction.
rstest is established enough that first-class support in Whitaker makes sense.
More importantly, the semantic question Whitaker is trying to answer is not “does this function have a literal #[test] attribute?” but “is this function test-only code?”. #[rstest] clearly satisfies that criterion.
Workarounds in the meantime
Projects can work around the issue, but all current workarounds are inferior to fixing detection in Whitaker.
Return Result from tests
#[rstest]
#[case::syntactic(LockFailureKind::Syntactic, "syntax error")]
fn content_transaction_rejects_lock_failure(
#[case] kind: LockFailureKind,
#[case] message: &str,
) -> Result<(), String> {
let dir = TempDir::new().map_err(|e| format!("temp dir: {e}"))?;
// ...
Ok(())
}
This avoids .expect(), but it changes test style and pushes projects toward ceremony they may not want.
Add redundant attributes
#[test]
#[rstest]
#[case::foo(1)]
fn my_test(#[case] x: i32) { ... }
This is clumsy, may be fragile, and should not be necessary merely to satisfy a lint tool.
Add framework-specific configuration
Whitaker could support configurable test attributes, for example:
[no_expect_outside_tests]
test_attributes = ["test", "rstest", "rstest_parametrize"]
That is better than nothing, but rstest is common enough that built-in support is preferable.
Suggested fix
Extend Whitaker’s existing “is this test-only code?” detection to recognize rstest attributes directly.
At minimum:
- treat a function annotated with
#[rstest] as test-only
- treat a function annotated with
#[rstest_parametrize] as test-only
If Whitaker already has shared test-detection logic used by multiple lints, this should go there rather than being implemented ad hoc in one lint.
It would also be sensible to structure the detection as a broader framework mechanism, so additional test frameworks can be supported consistently.
Acceptance criteria
- A function annotated with
#[rstest] is recognized as test-only code.
- A function annotated with
#[rstest_parametrize] is recognized as test-only code.
no_expect_outside_tests does not fire for .expect() calls inside such functions.
- Existing support for
#[test] and async test frameworks continues to work.
- The test-detection logic remains reusable across Whitaker lints.
Example regression test
#[rstest]
#[case(1)]
fn rstest_allows_expect_in_test_context(#[case] value: i32) {
let parsed = Some(value).expect("value should be present");
assert_eq!(parsed, 1);
}
Whitaker should not report no_expect_outside_tests here.
Environment
Investigation in Whitaker
I reviewed the current test-detection path before assuming the recent Tokio work had dropped rstest support.
This does not look like a simple matcher regression. The shared test-like attribute registry in common/src/attributes/mod.rs still includes:
rstest
rstest::rstest
case
rstest::case
The HIR adapter in src/hir.rs still delegates to that shared matcher, and no_expect_outside_tests still uses that path in crates/no_expect_outside_tests/src/driver/mod.rs.
That suggests the recent Tokio work did not simply remove rstest from the recognised attribute list.
Likely culprit: hollow rstest regression coverage
The more likely problem is that Whitaker’s existing rstest coverage is too weak to catch a real regression.
The positive rstest UI fixture is:
crates/no_expect_outside_tests/ui/pass_expect_in_rstest.rs
That fixture depends on the auxiliary proc-macro:
crates/no_expect_outside_tests/ui/auxiliary/rstest.rs
The auxiliary macro simply prepends a literal #[test] to the item. That means the current UI test proves only that “a proc macro which emits #[test] is accepted”. It does not prove that real rstest is recognised correctly by Whitaker.
In other words, if native rstest detection were broken, this test could still pass.
Contrast with the recent Tokio work
The recent Tokio fix explicitly hardened coverage with a real example-based regression:
crates/no_expect_outside_tests/src/lib_ui_tests.rs
crates/no_expect_outside_tests/examples/pass_expect_in_tokio_test_harness.rs
That regression compiles a real #[tokio::test] example under --test, precisely because the earlier proc-macro fixture only covered token shape rather than the real lowering and harness path.
Whitaker currently lacks an equivalent end-to-end regression for real rstest usage.
Implication
The safest reading is:
- the recent Tokio work may have exposed the gap, but
- the underlying problem is more likely that Whitaker has a hollow rstest regression test which masks a real detection failure.
So the issue is probably not “Tokio support removed rstest from the matcher”. It is more likely “Whitaker does not currently prove that real rstest tests are classified as test-only code”.
Proposed follow-up
-
Add a real rstest example-based regression using the actual rstest crate rather than the current auxiliary proc-macro that injects #[test].
-
If that regression fails, inspect the lowered or HIR shape to determine whether rstest needs:
- only direct source-level attribute recognition, or
- an additional fallback path analogous to the Tokio harness-descriptor recovery.
-
Treat the current pass_expect_in_rstest fixture as insufficient on its own for future regressions.
Problem statement
Whitaker currently fails to recognize test functions declared with
#[rstest]or#[rstest_parametrize]as test-only code. That causes false positives in lints that intentionally forbid patterns such as.expect()or.unwrap()outside tests.This is a Whitaker bug or gap in test-context detection, not an rstest bug.
Current behavior
Given code like this:
Whitaker reports
.expect()as if it were being called outside test-only code.Example diagnostic:
That diagnosis is wrong. The function is test code.
Root cause
Whitaker’s current test-detection logic appears to rely on patterns such as:
#[test]#[tokio::test]test_*That misses rstest, because
#[rstest]and related forms are procedural macro attributes that imply test semantics without necessarily exposing a plain#[test]attribute at the AST stage Whitaker inspects.In other words, Whitaker currently treats “is this syntactically a standard Rust test?” as too close to “is this test-only code?”. Those are not equivalent.
Expected behavior
Whitaker should classify functions as test-only code when they are annotated with rstest test macros, including at least:
#[rstest]#[rstest_parametrize]It may also be worth considering whether supporting associated rstest patterns such as
#[case::...]improves robustness, but the primary detection should sit on the function-level attributes.For the example above, Whitaker should not emit
no_expect_outside_testsor similar diagnostics.Scope
This affects any Whitaker lint that needs to distinguish production code from test-only code, including but not limited to:
no_expect_outside_testsno_unwrap_outside_testsIt also matters for projects using:
rstestrstest-bddWhy Whitaker should handle this
It is reasonable for lint tools to recognize widely used test frameworks directly. Requiring every upstream test framework to emit custom tooling hints, or requiring every downstream project to configure special cases, creates unnecessary friction.
rstestis established enough that first-class support in Whitaker makes sense.More importantly, the semantic question Whitaker is trying to answer is not “does this function have a literal
#[test]attribute?” but “is this function test-only code?”.#[rstest]clearly satisfies that criterion.Workarounds in the meantime
Projects can work around the issue, but all current workarounds are inferior to fixing detection in Whitaker.
Return
Resultfrom testsThis avoids
.expect(), but it changes test style and pushes projects toward ceremony they may not want.Add redundant attributes
This is clumsy, may be fragile, and should not be necessary merely to satisfy a lint tool.
Add framework-specific configuration
Whitaker could support configurable test attributes, for example:
That is better than nothing, but rstest is common enough that built-in support is preferable.
Suggested fix
Extend Whitaker’s existing “is this test-only code?” detection to recognize rstest attributes directly.
At minimum:
#[rstest]as test-only#[rstest_parametrize]as test-onlyIf Whitaker already has shared test-detection logic used by multiple lints, this should go there rather than being implemented ad hoc in one lint.
It would also be sensible to structure the detection as a broader framework mechanism, so additional test frameworks can be supported consistently.
Acceptance criteria
#[rstest]is recognized as test-only code.#[rstest_parametrize]is recognized as test-only code.no_expect_outside_testsdoes not fire for.expect()calls inside such functions.#[test]and async test frameworks continues to work.Example regression test
Whitaker should not report
no_expect_outside_testshere.Environment
.unwrap_or_else(|| { panic!(...) })under limited circumstances #188Investigation in Whitaker
I reviewed the current test-detection path before assuming the recent Tokio work had dropped
rstestsupport.This does not look like a simple matcher regression. The shared test-like attribute registry in
common/src/attributes/mod.rsstill includes:rstestrstest::rstestcaserstest::caseThe HIR adapter in
src/hir.rsstill delegates to that shared matcher, andno_expect_outside_testsstill uses that path incrates/no_expect_outside_tests/src/driver/mod.rs.That suggests the recent Tokio work did not simply remove
rstestfrom the recognised attribute list.Likely culprit: hollow rstest regression coverage
The more likely problem is that Whitaker’s existing rstest coverage is too weak to catch a real regression.
The positive rstest UI fixture is:
crates/no_expect_outside_tests/ui/pass_expect_in_rstest.rsThat fixture depends on the auxiliary proc-macro:
crates/no_expect_outside_tests/ui/auxiliary/rstest.rsThe auxiliary macro simply prepends a literal
#[test]to the item. That means the current UI test proves only that “a proc macro which emits#[test]is accepted”. It does not prove that realrstestis recognised correctly by Whitaker.In other words, if native rstest detection were broken, this test could still pass.
Contrast with the recent Tokio work
The recent Tokio fix explicitly hardened coverage with a real example-based regression:
crates/no_expect_outside_tests/src/lib_ui_tests.rscrates/no_expect_outside_tests/examples/pass_expect_in_tokio_test_harness.rsThat regression compiles a real
#[tokio::test]example under--test, precisely because the earlier proc-macro fixture only covered token shape rather than the real lowering and harness path.Whitaker currently lacks an equivalent end-to-end regression for real rstest usage.
Implication
The safest reading is:
So the issue is probably not “Tokio support removed rstest from the matcher”. It is more likely “Whitaker does not currently prove that real rstest tests are classified as test-only code”.
Proposed follow-up
Add a real rstest example-based regression using the actual
rstestcrate rather than the current auxiliary proc-macro that injects#[test].If that regression fails, inspect the lowered or HIR shape to determine whether rstest needs:
Treat the current
pass_expect_in_rstestfixture as insufficient on its own for future regressions.