Running 0 Tests

I know that rustdoc deals with doc tests, and that it only does so for libraries. To me this misadvertised or misunderstood feature had been a major selling point for Rust. So I was dismayed when I learned that it wasn't for me, a lowly application writer, only for the important people writing libraries. But as Rust gains traction, we will likely have far more applications (including invisible in-house ones) than libraries. So more and more people miss out on this nifty feature.

I propose that (instead of this being bolted onto rustdoc) rustc itself, in a special test mode, would desugar and harness doc tests similar to what rustdoc does. But instead of main it would generate unique function names for each snippet. Should the many decisions about each comment slow the compiler down noticably, a variant compiled with #[cfg(doctest)] could do that.

  • Either it would directly call these functions itself, with panic demoted to error message.
  • Or it would collect all these names and call them from one on-the-fly generated test binary.
  • Or each module would get one generated public doctest function that likewise calls each snippet in that file. And the test binary would only call all of those.

The advantages are

  • it works the same for any kind of code
  • the test functions exist in the right context
  • linenumbers are correct
  • far less invocation of programs (both rustc and test executables)

Caveat: macros will be documented before their definition, but the test function must be defined after it. This needs extra care and messes with line numbers.

It is intentional that doc tests don't run in the context of the module in which they exist. Docs are meant to be shown to the user, so by having doc tests run inside a similar context to what the user would use, it is ensured that the user can actually copy the doc code and paste it in their own code. (you can hide lines from the code shown to the user but still include it in tests using a prefix #, but this is normally only used to hide some definitions (like defining the runtime in tokio) that the user would have done anyway)

Rustdoc should already be passing a special env var to rustc to remap filenames and linenumbers to match the docs.

Edit: Seems I misunderstood the issue OP has.

There's no hard technical limitation that means you can't run doctests from a bin, it's just implementation work required to set it up (compile the bin as an rlib instead, pass it into rustdoc like normal, figure out how to deal with cases where a crate has both a lib and a bin with the same name).

2 Likes

btw the issue for this is Doctests don't work in bin targets · Issue #5477 · rust-lang/cargo · GitHub

1 Like

This is an aside on your main point (which I agree with); however, as an application writer, you are welcome and able to write your application as a tiny "shim" binary that makes use of a library from the same crate.

I tend to do this for anything non-trivial, and have a binary that uses clap to parse arguments, sets up any event loops I might want (tokio, any GUI loops etc) and then calls my library to do the real work (put windows up, handle events etc)

4 Likes

I actually tried, even though I find that an annoying detour. For one thing I failed to figure out how to share macros. Probably solvable…

For another, I was having a hard time compiling. Our Artifactory team hasn't managed to provide a crates.io proxy. Plus their experiments ate up a lot of space, as they're still on an old version, with the old cargo protocol.

From home I could download, but due to GLIBC version tagging, my local compilate wouldn't start on our older data center boxes. Solution was to compile for target musl, which as a side effect builds a static executable.

1 Like

farnz was suggesting that you make the library inside the same crate. A single crate can contain both targets and the binary can depend on the library. See rust - Package with both a library and a binary? - Stack Overflow for an example.

3 Likes

farnz was suggesting that you make the library inside the same crate.

Oh, that's a misunderstanding. The proxy is for fetching other crates from behind a firewall.

I did put main.rs and lib.rs side by side. But then #[macro_use] (which I found to be the cleanest, easiest way to export macros) was no longer powerful enough to export them across the divide. I long for macros 2.0! (I saw that even some stable builtins are already declared as pub macro. Not sure if that's only pseudocode…)

1 Like

You can export macros from src/lib.rs. Starting with rust - Package with both a library and a binary? - Stack Overflow

// src/lib.rs

#[macro_export]
macro_rules! say_hello {
    () => {
        println!("Hello!")
    };
}
// src/main.rs

fn main() {
    example::say_hello!();
}
$ cargo run
Hello!

If this example still does not solve your problem, you may consider a post on https://users.rust-lang.org/ and folks can help you figure it out.

1 Like