Thread overview
Allow `-version=unittest` flag
Apr 08
Ilya
Apr 12
Ilya
Apr 14
Ilya
Apr 14
kinke
Apr 30
Ilya
May 02
kinke
April 08

Currently D compilers don't allow passing -version=unittest flag explicitly, requiring to pass -unittest instead. But -unittest has a additional meaning: it will compile UTs as well!

So, if I have two modules A.d and B.d, both define some UTs, B imports A and A has some version(unittest) blocks in non-templated code, there is no way to build just UTs from B.

I can build B using dmd -c -unittest B.d, but there is no way to build A correctly:

  • if I build it without -unittest, conditional compilation for version(unittest) blocks won't trigger;
  • and if I build it with -unittest, I get also UTs from A.

It's a simplified example, instead of two modules in reality it's a dependency chain of groups of modules, where each group is compiled in one step, but we still want to get separate UTs for every group, instead of getting everything in the root group.

I get it, in D world people are usually compiling everything in one shot, so that's not a problem. But unfortunately this approach doesn't scale very well.

How bad it will be to stop failing on -version=unittest? Seems like compiling UTs dependencies is a valid use case for it.

April 10

On Tuesday, 8 April 2025 at 13:26:42 UTC, Ilya wrote:

>

How bad it will be to stop failing on -version=unittest? Seems like compiling UTs dependencies is a valid use case for it.

What you want is to be able to select the code to include using a version statement, and also select it when you compile with unittests:

version(testing)
void utThing() {
   ...
}

version(unittest) version = testing; // also enable for unittests

Now, you can build with unittests or with -version=testing, and the function is included.

-Steve

April 12

On Thursday, 10 April 2025 at 19:13:18 UTC, Steven Schveighoffer wrote:

>

On Tuesday, 8 April 2025 at 13:26:42 UTC, Ilya wrote:

>

How bad it will be to stop failing on -version=unittest? Seems like compiling UTs dependencies is a valid use case for it.

What you want is to be able to select the code to include using a version statement, and also select it when you compile with unittests:

version(testing)
void utThing() {
   ...
}

version(unittest) version = testing; // also enable for unittests

Now, you can build with unittests or with -version=testing, and the function is included.

Thanks, what works indeed, I think we even use this pattern in some of our code. But it also feels like a workaround. So, I think my question still stands, why version(unittest) has to be special? Why can't we simply allow users set it without -unittest?

April 14

On Saturday, 12 April 2025 at 10:46:37 UTC, Ilya wrote:

>

Thanks, what works indeed, I think we even use this pattern in some of our code. But it also feels like a workaround. So, I think my question still stands, why version(unittest) has to be special? Why can't we simply allow users set it without -unittest?

The version(unittest) means "the compiler was invoked with the -unittest flag". It shouldn't mean anything else.

If you want to include more code in a related feature (such as custom-defined test functions), use a different version, and apply a version statement like I mentioned.

Allowing specifying predefined versions on the command line loses the authority of the version statement. Should I be able to switch to BigEndian mode whenever I want?

-Steve

April 14

On Monday, 14 April 2025 at 03:31:15 UTC, Steven Schveighoffer wrote:

>

The version(unittest) means "the compiler was invoked with the -unittest flag". It shouldn't mean anything else.

Ok, that's a fair point. If someone actually needs to know if the compiler was invoked with -unittest, than yes, we definitely shouldn't allow setting it explicitly. But how useful is that?

I was assuming version(unittest) means "I'm building unit tests" and then it's seems natural to extend it to "I'm building unit tests or UTs' dependencies".

>

If you want to include more code in a related feature (such as custom-defined test functions), use a different version, and apply a version statement like I mentioned.

Yes, the trick with another version works. And in the long run we should get rid of version(unittest) altogether, I think.

>

Allowing specifying predefined versions on the command line loses the authority of the version statement. Should I be able to switch to BigEndian mode whenever I want?

Ok, I see your point, even though I don't think it's a fair analogy.

I can try to re-state my point. I think the -unittest flag should actually be ternary, not binary: apart from no-UT and UT options, there should also be a dep-UT option, that allows building dependencies of UTs with version(unittest), but without building the UTs of dependencies. Otherwise people have to use the "custom-UT version" trick to build UTs with separate compilation. So I suggested to allow explicit -d-version=unittest as an easy way to enable that third option.

But anyway, if people have strong opinions here, I'm not going to insist.

April 14

On Monday, 14 April 2025 at 09:03:20 UTC, Ilya wrote:

>

I can try to re-state my point. I think the -unittest flag should actually be ternary, not binary: apart from no-UT and UT options, there should also be a dep-UT option, that allows building dependencies of UTs with version(unittest), but without building the UTs of dependencies. Otherwise people have to use the "custom-UT version" trick to build UTs with separate compilation.

You haven't really stated what the problem with compiling those 'UT deps' with -unittest (instead of just your proposed -version=unittest) is:

  • Just that those unittests are run by default too? The test runner can be customized, to only run the unittests of a subset of the linked modules. The druntime/Phobos unittest runners do just that, optionally allowing to specify the modules to be tested as cmdline options: https://github.com/dlang/dmd/blob/master/druntime/src/test_runner.d
  • The compilation overhead for those unittest functions? I'd say it's better to recompile a dirty module once in -unittest mode and using it for its own unittest runner, as well as those other unittest runners which require the module to be compiled with -version=unittest - instead of having to compile twice, once with -unittest and once with -version=unittest alone.
>

I get it, in D world people are usually compiling everything in one shot, so that's not a problem.

No, dub defaults to compiling each (non-source) (sub-)package in one shot. So if you replace your term 'group of modules' by 'dub packages/libraries', then that's exactly what most D users are building. The dub package dependencies can be tightly coupled as well (e.g., subpackages depending on one another), so this need to compile deps with -version=unittest for a parent project's unittest runner doesn't seem very popular.

Out of interest, what's behind those version (unittest) blocks? Extra checks, or test helpers that dependents need?

April 30

On Monday, 14 April 2025 at 12:06:38 UTC, kinke wrote:

>

You haven't really stated what the problem with compiling those

That's not really a problem, just a minor inconvenience, so I just wanted to check what's the reason to this limitation.

>

'UT deps' with -unittest (instead of just your proposed -version=unittest) is:

  • Just that those unittests are run by default too? The test runner can be customized, to only run the unittests of a subset of the linked modules. The druntime/Phobos unittest runners do just that, optionally allowing to specify the modules to be tested as cmdline options: https://github.com/dlang/dmd/blob/master/druntime/src/test_runner.d

Yes, not running unrelated UTs in not an issue, we do have a custom runner with filtering. Unrelated UTs do contribute to the test binary size, but that's a minor issue.

>
  • The compilation overhead for those unittest functions? I'd say it's better to recompile a dirty module once in -unittest mode and using it for its own unittest runner, as well as those other unittest runners which require the module to be compiled with -version=unittest - instead of having to compile twice, once with -unittest and once with -version=unittest alone.

Sure, the compilation overhead is usually not huge and as you mention, the resulting object could be re-used if we want to link and run UTs for the dependency.

The compilation overhead of having to recompile all the transitive deps with -unittest is non-trivial, but that has nothing to do with this thread. We just have to limit our version(unittest) usage.

>

Out of interest, what's behind those version (unittest) blocks? Extra checks, or test helpers that dependents need?

Oh no, it's much worse: the whole branches of logic are behind version(unittest) {} else guards...

May 02

On Wednesday, 30 April 2025 at 12:05:06 UTC, Ilya wrote:

>

That's not really a problem, just a minor inconvenience, so I just wanted to check what's the reason to this limitation.

To me, it's mainly about not violating the principle that (optionally) predefined versions cannot be defined manually. This makes sure one doesn't have to study the compiler command lines or build recipes to rule out any weird behavior. In the worst case, something like a version(AArch64) block being unexpectedly compiled for another target because someone thought -version=AArch64 was okay as a workaround.

That said, there were very few occasions where I would have wished for being able to do just that - e.g., for first cross-compilation attempts to a totally new target without any compiler support (wrt. predefined versions), where it would have been nice to predefine Posix etc., to get a bit more druntime support.