Home > Back-end >  Is there a programmatic approach to check for unused unit test files in a crate?
Is there a programmatic approach to check for unused unit test files in a crate?

Time:10-05

The documentation for Rust tests indicates that unit tests are run when the module that defines them is in scope, with the test configuration flag activated:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        let result = 2   2;
        assert_eq!(result, 4);
    }
}

In a large project, however, it is frequent to confine a test module to a different file, so that the test module is pointed to differently:

#[cfg(test)]
#[path = "unit_tests/tests.rs"]
mod tests;

With this setup, in a large project, it may happen that someone deletes the reference to src/unit_tests/tests.rs (i.e. the code block above), without deleting the test file itself. This usually indicates a bug:

  • either the removal was intentional, and the file src/unit_tests/tests.rs should not remain in the repository,
  • or it wasn't, in which case the tests in src/unit_tests/tests.rs are meant to be run and aren't, and CI should be vocal about that.

I'd like to add a verification that there are no such unmoored test files in my crate, to the CI of my project. Is there any tooling in the rust ecosystem that could help me detect those unlinked test files programmatically?

(integration tests are automatically compiled if found in the tests/ repository of the crate, but IIUC, that does not apply to unit tests)

CodePudding user response:

There is a very old rustc issue on the subject, which remains open.

However one of the commenters provides a hack:

One possible way to implement this (including as a third-party tool) is to parse the .d files rustc emits for cargo, and look for any .rs files that aren't mentioned.

rg --no-filename '^[^/].*\.rs:$' target/debug/deps/*.d | sed 's/:$//' | sort -u | diff - <(fd '\.rs$' | sort -u)

Apparently the .d files are

makefile-compatible dependency lists

and so I assume they should list the relationship between all rust files in the project. Which is why if one is missing, it's not seen by cargo / rustc.

CodePudding user response:

There is an issue asking for cargo to do this check, cargo (publish) should complain about unused source files, and from that found the cargo-modules crate can do this. For example:

//! src/lib.rs
#[cfg(test)]
mod tests;
//! src/tests.rs
#[test]
fn it_works() {
    let result = 2   2;
    assert_eq!(result, 4);
}
> cargo modules generate tree --lib --with-orphans --cfg-test

crate mycrate
└── mod tests: pub(crate) #[cfg(test)]

However, if I remove the #[cfg(test)] mod tests; the output would look like this:

> cargo modules generate tree --lib --with-orphans --cfg-test

crate mycrate
└── mod tests: orphan

You would be able to grep for "orphan" and fail your check based on that.

I will say though that this tool seems very slow from my small tests. The discussion in the linked issue indicate that a better way would have to be used if implemented in cargo.

You will also have to keep in mind any other conditional compilation may yield false positives (i.e. you legitimately include or exclude certain modules based on architecture, OS, or features). There is an --all-features flag that could help with some of that.

  • Related