Postmortem: Template unittests are bad & you shouldn't catch Error
H. S. Teoh
hsteoh at quickfur.ath.cx
Thu Oct 22 16:18:14 UTC 2020
On Thu, Oct 22, 2020 at 04:04:26AM +0000, Mathias LANG via Digitalmars-d wrote:
[...]
> First, why on god's green earth are we putting unittests with explicit
> template instantiation inside a *template* ? `unittest` inside
> templates are a terrible "feature", and I've never seen a correct
> usage of it, which would be a unittest that relies on the
> user-instantiated instance to test that its expectation still holds.
>
> I'm pretty sure the rationale is "for documentation", and we should
> really re-think this, because those unittests are being run from
> *every single module that instantiate Nullable*. Actually, they are
> being run for *every instantiation of Nullable*.
[...]
This is a known issue that has been brought up many times in the past.
Basically:
1) A unittest block inside a template is duplicated for *every*
instantiation of the template. To alleviate this, you'd need to do
something like this:
template MyTemplate(Args...) {
auto myFunc(...) { ... }
static if (Args == ...)
unittest { ... }
}
This is not a fool-proof solution, however, because if that specific
combination of Args isn't actually instantiated, the unittest is
silently ignored (even if it would have triggered a failure due to some
bug). To alleviate this, you'd have to insert something like this
outside the template body:
// Make sure unittests get instantiated.
version(unittest) alias _ = MyTemplate!(...);
Which is, of course, extremely ugly. And definitely not something
newcomers would think of.
Another way to solve this is to move the unittest outside the template
body. However:
2) Ddoc's unittests require unittest blocks to be attached to the symbol
they're documenting. So you pretty much have no choice, unless you use
Phobos StdDdoc hack (ugh):
struct PhobosTemplate {
auto phobosFunc(...) { ... }
version(StdDoc) // ugh
///
unittest { ... }
}
Jonathan Davis & myself have proposed in the past an enhancement to
solve this problem: have some way of marking a unittest block inside a
template as single-instance only. Something like this:
template MyTemplate(T)
{
auto myFunc(...) { ... }
/// This is instantiated once per template instantiation
unittest {
pragma(msg, T.stringof);
}
/// This is instantiated only once, ever
/// (The use of 'static' is hypothetical syntax, it can
/// be anything else that marks the unittest as
/// single-instance)
static unittest {
//pragma(msg, T.stringof); // Error: cannot reference T here
// Have to explicitly instantiate the template
// with the arguments you want to test for
alias U = MyTemplate!int;
}
}
However, so far no action has been taken on this front besides the
proposal.
T
--
"Maybe" is a strange word. When mom or dad says it it means "yes", but when my big brothers say it it means "no"! -- PJ jr.
More information about the Digitalmars-d
mailing list