Yes, this is the source of the issue. As mention in the OP:
“Internal” unit tests have, in some cases, many usability advantages over “external” unit tests, the main one for me personally being that they can be generated from macros and procedural macros, to directly insert test for the functionality being added that exercise the actual types. For example, impl_x! derive(Foo) can insert #[cfg(test)] automatically.
Keeping the tests and the code close to each other is a property that I’d like to maintain.
Generating the tests in the way you propose, would require to either separate the tests from the impl code e.g. into a template that can generate tests/*.rs files, in which case, I might just as well put it directly in tests/rs files. Or parsing the rust source code, removing the test code while compiling the crate, and adding the test code to some tests/.rs.
Basically, if we remove the constraint that the implementation code and the test code must be close to each other, solving this problem is trivial. The interesting issue is solving the problem without lifting the constraint.
I have a new solution in which I added a new layer of indirection: a macro that expands the tests macros. The library crate defines those macros to expand to nothing, not generating any tests. Then I add multiple crates, that define those macros to expand the tests, so that the tests only get expanded there. Right now, these other crates still need to recompile the original crate, but I am tweaking that to add it via a dependency and using #[path = “…”] mod …; to only include the modules of the tests. All of this is just really painful.
There should be a way to keep tests close to the code, and still be able to generate different TUs for different test subsets.