Postmortem: Template unittests are bad & you shouldn't catch Error
Mathias LANG
geod24 at gmail.com
Thu Oct 22 16:05:51 UTC 2020
On Thursday, 22 October 2020 at 14:36:11 UTC, Steven
Schveighoffer wrote:
> On 10/22/20 12:04 AM, Mathias LANG 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 used it in dcollections quite a bit as a way to ensure all
> integer based types were tested.
>
> The advantage here is, I wanted to run the same tests on every
> integer type. But I wanted the tests to be close to the
> functions they were testing.
>
> But I think this can be done a different way, and it is
> *really* hard to get this right. Plus, they are going to be run
> when you instantiate them elsewhere, which is a problem.
The point was, if the unittest does not depend on the template
parameters, it shouldn't be in the template.
So the following is a correct use of the feature:
```
struct Nullable (T)
{
unittest
{
Nullable inst;
assert(inst.isNull());
}
}
```
While this is not:
```
struct Nullable (T)
{
unittest
{
Nullable!int inst;
assert(inst.isNull());
}
}
```
Because the first one will run for the user instantiation while
the second one is independent on the user instantiation (but will
run anyway).
>>
>> Second, as the title of the post says, *do not catch Error*.
>> Catching `Error` is a sin, a crime, and every catch of `Error`
>> derivative should have you outraged. It is in direct violation
>> of the specs. And obviously, it's also incompatible with
>> scheme that do not throw an `Error`.
>
> I disagree with this. How would you test that the assert error
> is thrown? It seems that your specific case is being hampered
> by this, but can't you just look at the parameters to see
> whether you should handle it or not? Like check the file/line
> and do the normal thing if it's from phobos?
You do not test for assert error. Assert errors are logic errors,
and if they get triggered, all bets are off. Of course, one might
find this impractical, because `get` cannot be tested this way.
Perhaps that's a sign that `Nullable` should use exceptions, and
not `assert`, for this.
Regarding the workaround: We currently do this, however, testing
for it in a cross-platform and generic way is non-trivial. We can
do `endsWith` but we have to account for different paths. And
there's no guarantee that we won't have false-positive (e.g.
ourcustomlib.std.typecons).
>> In any case, there's two things I really wish we fixed:
>> - Either ban unittests in templates, deprecate them, or find a
>> way to make this mistake hard to do;
>
> What we need is a way to make a static unittest that serves to
> provide documentation, but is run as if it were outside the
> template.
That's something that Andrej Mitrovic brought up during the
following discussion. While it would fix this specific instance,
the default would still be bad, so I'm not really convinced.
>> - Deprecate the ability to catch `Error` in code. If it's not
>> supposed to be caught, it shouldn't be so easy to do it;
>
> Disagree. This use case you have is very obscure, and it's much
> less obscure to verify an Error is thrown in a unittest.
>
> -Steve
You disagree with the spec then. The spec says that the stack
might not be unwound if an `Error` is thrown. Walter even raised
a PR to actually do the optimization the spec promises. In his
words:
> This PR is still the right thing to do. Attempting to unwind
> when an Error is thrown is a bad idea, and leads to undefined
> behavior. Essentially, code which relies on such unwinding
> needs to be fixed.
Source:
https://github.com/dlang/dmd/pull/6896#issuecomment-362855793
More information about the Digitalmars-d
mailing list