#[cfg(doctest)] not set?

Hi. Why would the following fail during cargo test, please:

// src/lib.rs:
/// ```
/// #[cfg(not(doctest))]
/// compile_error!("NOT DOCTEST!");
/// ```
pub const _: () = {};

cargo test:

...
running 1 test
test src/lib.rs - _ (line 1) ... FAILED

failures:

---- src/lib.rs - _ (line 1) stdout ----
error: NOT DOCTEST!
 --> src/lib.rs:3:1
  |
4 | compile_error!("NOT DOCTEST!");
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

Couldn't compile the test.

And #[cfg(doctest)] is not set in the crate being compiled either:

// src/lib.rs:
#[cfg(not(doctest))]
compile_error!("NOT DOCTEST!");

cargo test:

Compiling cfg_doctest v0.1.0 (/tmp/cfg_doctest)
error: NOT DOCTEST!
 --> src/lib.rs:2:1
  |
2 | compile_error!("NOT DOCTEST!");

I can understand that one of them could be designed to fail - but both?!

The nearest open issue I can find is cfg(doctest) doesn't work as expected · Issue #67295 · rust-lang/rust · GitHub, but unsure if relevant.

#[cfg(doctest)] is only set in one specific condition: when collecting doctests. That is, when rustdoc is analyzing your crate, not for documenting it, but for pulling doctest code out of items’ documentation. It's more like #[cfg(doc)] than anything else — for both cfgs, no code is actually compiled/run under that cfg. For #[cfg(doc)], the only output is documentation text, and for #[cfg(doctest)], the only output is the source code of doctests.

This means that, if you want to modify your doctests when testing, you can do it, but you do it at the outer level, in the source of the containing Rust program:

/// ```
#[cfg_attr(not(doctest), doc = "compile_error!(\"NOT DOCTEST!\");")]
/// ```

That said, I recommend that you avoid or minimize the amount of cfg changes that apply to doctests. The value of doctests as opposed to other kinds of tests is that they are examples that are tested to work. As much as possible, users should be able to copy your example code into their own project and expect it to compile and run in exactly the same way. (Another recent thread on a similar topic, where I said much the same thing.)

Of course, there are other uses of doctests, like compile_fail tests not meant to be read by anyone. #[cfg(doctest)] is perfect for that if you apply it to a dummy item carrying the doctest:

#[cfg(doctest)]
/// ```compile_fail
/// my_lib::my_macro!(used, incorrectly);
/// ```
struct ExpectedMacroError;
1 Like

Thank you Kevin.
(Background/rant: Working around Forbidding lints doesn't really work in macros · Issue #110613 · rust-lang/rust · GitHub to have compile_fail doctests fail on violated lints that have deny (or forbid, which is not recommended) issued by macro_rules! defined in another crate. I ended up distributing "frontend" macros in a file, and loading it into client's crate.

The client/crate consumer may want to choose to run those extra lints only for doctests (since they don't compile), hence I hoped to use #[cfg(doctest)] (or #[cfg(any(doctest,test))] so that it covers uni tests, too (positive, of course) - but that doesn't work for doctests: `cfg(test)` is not set within the test code while compiling doctests · Issue #148942 · rust-lang/rust · GitHub).

So I'll workaround-yet-again by not using #[cfg(doctest)] and the client will indicate it by an optional flag passed to the macro-loading-macro. Otherwise (without the flag) it fails to use #[cfg(test)] for unit tests only.)