Conditional compilation for test

I have a function and a test for it. I want the function to return additional information to be printed in case the test fails. Collecting this information is performance heavy and I want it only for this particular test. That means that I do not want to enable it always when test configuration is enabled.

I invented something like this, but I do not like this debug_info parameter that I have to pass even outside tests. Also, I do not like the if let Some(debug_info) = debug_info line: I would like my debug lines to be as small as possible.

fn foo(debug_info: Option<&mut String>) -> bool {
    if cfg!(test) {
        if let Some(debug_info) = debug_info {
            *debug_info += "some debug stuff";
        }
    }

    false
}

fn main() {
    // normal operation
    foo(None);

    // test debug_info not used if not in tests
    let mut debug_info = String::new();
    foo(Some(&mut debug_info));
    assert_eq!(debug_info, ""); // debug_info ignored
}

#[cfg(test)]
mod tests {
    use crate::foo;

    #[test]
    fn test() {
        let mut debug_info = String::new();
        assert!(
            foo(Some(&mut debug_info)),
            "failed with info: {}",
            debug_info
        );
    }
}

You could use a thread local instead of a parameter

You could also use

#[cfg(test)]
if let Some(debug_info) = debug_info {
    *debug_info += "some debug stuff";
}

Instead of the if cfg! so you don't compile the debug code at all in non-test builds

1 Like

It is not mentioned where I looked, that it is possible to use cfg attribute inside a function body:

After reading these docs I assumed that if cfg! is the way and then dead code elimination will do its work.

That's an interesting idea.

Thank you!

The reference does mention it briefly here but it only shows examples on the item level. The fact that you can use it on expressions is only implied by the line at the end

The cfg attribute is allowed anywhere attributes are allowed.


Yeah there's a good chance it does get eliminated in a later pass, but not compiling it at all can help compilation times if you have big complicated expressions that need to be type checked, for example.

In this case it probably wouldn't make a meaningful difference though, just wanted to point out that it was possible!

That was really helpful. I would find out it myself sometimes, but you made me know it right now.

Also, is there any analog for else?

	#[cfg(test)]
	{
               //actual work
	}
	#[cfg(not(test))]
	{
		R::default()
	}

The cfg-if crate may be helpful. For example

cfg_if::cfg_if! {
    if #[cfg(test)] {
        // actual work
    } else {
        R::default()
    }
}
1 Like