This is why I don't use D.

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sat Sep 8 18:05:44 UTC 2018


On Saturday, September 8, 2018 7:44:09 AM MDT Nicholas Wilson via 
Digitalmars-d wrote:
> On Friday, 7 September 2018 at 19:15:21 UTC, Jonathan M Davis
>
> wrote:
> > Honestly, I wouldn't rely on anything beyond dub build working
> > in a consistent manner across projects. As far as I can tell,
> > you can't actually do anything properly custom with dub test,
> > and I'm inclined to think that how it approaches things is
> > outright wrong.
>
> For Dcompute I define a unittest configuration that has a main
> that runs tests, and the (default) library configuration excludes
> source files in the test directory. It seems to work fine.
> Admittedly it is a little different to you usual package, and
> there are no test specific symbols and no unit tests, and I
> haven't tested anything that has dcompute as a dependancy.
>
> Are you suggesting this will break users of Dcompute that run dub
> test in their code? If so that truly does suck, because regular
> unittests will not cut the mustard.

Okay. It's been several months since I dug into this, so hopefully I have
the details straight.

Unless no one using dcompute uses dub test on their own projects, you
probably don't have the worst part of this problem, because if you did,
they'd be complaining about linker errors.

Honestly, unittest blocks in general are somewhat broken, but what really
doesn't work correctly is code that's versioned for unit tests. e.g. if
you've declared some types or functions to use specifically in your unit
tests and which have no business being in the actual library (e.g. dxml has
dxml/internal.d which includes stuff specifically to help the unit tests and
is not part of the public API). The core problem is caused by the language,
but the way dub handles things negates the workarounds for the problem.

When you import a module that includes unittest blocks, and you're compiling
your module with -unittest, the version identifier unittest is defined when
importing the module, meaning that all unittest blocks are processed, and
everything inside a version(unittest) block is considered to be part of the
module. With regards to the unittest blocks themselves, this is nothing but
negative. It means that they have to compile regardless of whether
everything they use is available to the module importing them, and it's a
pointless waste of compiler resources. This is even worse when a unittest
block is inside a template, because than anyone using that template then
also gets the unittest blocks compiled into their code (once for each
instantation of the template), meaning that the unittest blocks in the
libraries you use can actually be compiled into your program and get run
when you run your own unit tests. IMHO, when importing a module, unittest
blocks should be treated as version(none) rather than version(unittest), but
that's not currently the case.

The less straightforward problem is the helpers that are compiled with
version(unittest). We can't treat everything that's version(unittest) as
version(none) when importing, because that would make it impossible to share
unit test helpers between modules. The cleanest way to deal with those is
therefore to put them in a separate module that gets imported locally by the
unittest blocks. That way, you don't accidentally mark anything in the
module with the unittest blocks as version(unittest), and it allows you to
hide the symbols from any modules that import your module. However, because
the unittest blocks themselves are still processed when importing, it
doesn't actually work to hide the symbols being imported like it should. The
compiler still processes them when it imports the module. This causes
problems when the modules being imported weren't actually compiled into the
library that you're linking against (since normally, libraries aren't
compiled with -unittest) - hence the linker errors. If you use unit test
helpers that are version(unittest) in a library, you risk linker errors in
any project that imports that library. Not marking them with
version(unittest) can fix that problem, but it then pollutes projects using
the library.

While it's certainly ugly, the easiest way to solve the problem at present
is to version all of your unittest blocks with a version identifier specific
to your project. That way, any projects depending on your library won't
actually end up compiling the unittest blocks, because while the unittest
version identifier will have been declared, the version identifier specific
to your project will not have been. There was actually a PR for Phobos a few
months back that tried to do this to all of Phobos to fix this problem with
Phobos, but it was rejected on the grounds that it was hideous and that we
should solve it in the language - which I agree with, but in the interim, we
still have to deal with the problem - hence why dxml has the dxmlTests
version identifier. Without it, any projects using dxml would get linker
errors when running their own tests.

Where dub makes this whole situation worse is that for some reason, the
build configuration for your library that's used when you run dub test is
also used by anything that pulls in your library via dub. I had made it so
that dub test declared dxmlTests so that dub test would work properly for
dxml but with the idea that then any projects depending on dxml would not
declare dxmlTests. However, because dub pulls in the dependencies' unit test
configurations instead of just using the non-unit test one like it should,
that results in any projects that depend on dxml having dxmlTests declared,
so the problem isn't fixed, and all of those annoying version(dxmlTests)
blocks were a waste of time.

To workaround this stupidity of dub, I was forced to create a separate set
of buildTypes for running the unit tests in debug, release, or with code
coverage. Those buildTypes must be used, or there are no unit tests, meaning
that dub test goes really fast and does nothing useful whatsoever. And
because of this whole issue, I avoid dub test like the plague.

So, dub needs to fix what it's doing (I can't remember what I reported at
the time - I really should make sure that I have all of the facts straight
and clearly explained in a bug report for dub), and once it is fixed, then
dub test can be used again by projects that need to do custom stuff with
their unit test configurations. But that still leaves the compiler problem,
and a DIP needs to written to fix that. IIRC, Jonathan Marler was trying to
fix it several months ago, but I think that he was mucking around with
version(unittest) in general, and he ran into all kinds of weird problems.
So, I don't know what it would really take to fix the problem in the
compiler, but I think that it's pretty clear that unittest blocks should be
utterly ignored when the module they're in is imported, and right now,
they're not.

- Jonathan M Davis





More information about the Digitalmars-d mailing list