Utah Valley University teaches D (using TDPL)

Jonathan M Davis jmdavisProg at gmx.com
Fri Nov 26 20:05:43 PST 2010


On Friday 26 November 2010 18:52:59 Walter Bright wrote:
> Jonathan M Davis wrote:
> > I'm a firm believer that D unit tests should not change how they
> > fundamentally work at this point. I don't _want_ it to report the number
> > of tests that passed.
> 
> That's right. The number that fail is completely useless window dressing.
> 
> > That information is not at all useful. And if I want to do something like
> > set up something to run dmd periodically and report only when the tests
> > start failing, then the current situation is perfect.
> > 
> > I _do_ think that the unit testing stuff should be expanded to have named
> > unit tests and do whatever is necessary to make it possible for external
> > tools to run the unit tests. Then you could have an external tool that
> > did things like only run specific unit tests and report which tests
> > succeeded and which failed.
> > 
> > So, I do think that it should be possible to build external tools on D's
> > unit testing framework, but I think that it's quite good as it is now,
> > and I wouldn't want to see it drastically changed.
> > 
> > The two changes that I want to see to the current framework are
> > 
> > 1. Make it so that every unittest block runs in a module instead of them
> > stopping once one failed. The current situation is better than stopping
> > _all_ unittest blocks once a single test fails, but it's still not
> > granular enough. I believe that this change is planned, but work has to
> > be done to make it possible, and that work hasn't been done yet.
> 
> I believe that is the current behavior. For a time, it kept getting broken
> by changes to druntime, so it may be broken yet again, but that is the way
> it is supposed to work.

It's not. This program

import std.stdio;

bool func()
{
    return false;
}

void main()
{
}

unittest
{
    writeln("test 1");
    assert(func());
}

unittest
{
    writeln("test 2");
}

prints

test 1
core.exception.AssertError at test(15): unittest failure
----------------
./test(onAssertErrorMsg+0x34) [0x808c0f4]
./test(onUnittestErrorMsg+0x18) [0x807f3b8]
./test(_d_unittestm+0x22) [0x807d252]
./test(void test.__unittest_fail(int)) [0x807acfa]
./test(void test.__unittest1()) [0x807ac3b]
./test(void test.__modtest()) [0x807ace0]
./test(extern (C) bool core.runtime.runModuleUnitTests()) [0x807f56c]
./test(_D6object10ModuleInfo7opApplyFMDFKPS6object10ModuleInfoZiZi+0x41) 
[0x807cd7d]
./test(runModuleUnitTests+0x87) [0x807f487]
./test(extern (C) int rt.dmain2.main(int, char**)) [0x807d448]
./test(extern (C) int rt.dmain2.main(int, char**)) [0x807d370]
./test(main+0x96) [0x807d316]
/usr/lib32/libc.so.6(__libc_start_main+0xe6) [0xf75b7c76]
./test() [0x807ab51]

The second unittest block is never run. I believe that the last time that this 
got discussed on the Phobos list, Sean said that some fundamental changes had to 
be made to either dmd or druntime (I _think_ it was dmd, but I'm not sure) to 
make it possible to run subsequent unit tests in a module after one of them has 
failed. If a test in a module fails, then the tests in other modules run, but 
not the rest of the tests in the module that had the failure.

> > 2. Make it possible to name unittest blocks. This would make exceptions
> > that escape unittest blocks _far_ more useful. Names like unittest45
> > really aren't useful at all. I can't even figure out how it does the
> > numbering.
> 
> If the line numbers given out are incorrect, those are compiler bugs and
> I'd like to get them fixed.

Not the line number. I'm talking about the function name that you get stack 
traces. Take this program for instance:

void func()
{
    throw new Exception("An exception threw.");
}

void main()
{
}

unittest
{
    func();
}

If you run the unit tests, you get

object.Exception: An exception threw.
----------------
./test(void test.func()) [0x80599fe]
./test(void test.__unittest1()) [0x8059a10]
./test(void test.__modtest()) [0x8059a1c]
./test(extern (C) bool core.runtime.runModuleUnitTests()) [0x805dcdc]
./test(_D6object10ModuleInfo7opApplyFMDFKPS6object10ModuleInfoZiZi+0x41) 
[0x805ba9d]
./test(runModuleUnitTests+0x87) [0x805dbf7]
./test(extern (C) int rt.dmain2.main(int, char**)) [0x805c168]
./test(extern (C) int rt.dmain2.main(int, char**)) [0x805c090]
./test(main+0x96) [0x805c036]
/usr/lib32/libc.so.6(__libc_start_main+0xe6) [0xf7595c76]
./test() [0x8059921]


Notic __unittest1(). Here, there's only one unit test, so it's not a big deal, 
but once you have a lot of them, it's not really feasible to associate that 
function name with a unittest block. If the unittest blocks were named, 
something like this

unitest(mytest)
{
}

then you could get a stack trace like this

object.Exception: An exception threw.
----------------
./test(void test.func()) [0x80599fe]
./test(void test.__unittest_mytest()) [0x8059a10]
./test(void test.__modtest()) [0x8059a1c]
./test(extern (C) bool core.runtime.runModuleUnitTests()) [0x805dcdc]
./test(_D6object10ModuleInfo7opApplyFMDFKPS6object10ModuleInfoZiZi+0x41) 
[0x805ba9d]
./test(runModuleUnitTests+0x87) [0x805dbf7]
./test(extern (C) int rt.dmain2.main(int, char**)) [0x805c168]
./test(extern (C) int rt.dmain2.main(int, char**)) [0x805c090]
./test(main+0x96) [0x805c036]
/usr/lib32/libc.so.6(__libc_start_main+0xe6) [0xf7595c76]
./test() [0x8059921]

With that stack trace, I can easily determine which test threw the exception. 
It's not a problem with assertions within the test - since they'll give the file 
and line number of the assertion in the test - but if any function called by the 
test throws an exception for whatever reason (including assertions in 
contracts), then you get a stack trace that borders on useless, because you 
can't easily determine which unittest block threw the exception. With named 
unittest blocks, that problem can be fixed.

As it stands, I don't even know how the number for the unittest is picked. It 
does not appear that __unittestX() necessarily corresponds to the Xth unit test 
in a module lexographically. Named unit tests are really the way that the 
problem should be fixed.

I'm not saying that naming unittest blocks should be mandatory, but I think that 
it should be possible. Unnamed unittest blocks result in functions with names 
like __unittest1() like they do now, whereas named unittest blocks would  result 
in names like __unittest_testname() where testname is the name of the unittest 
block. It would make stack traces far easier to deal with, and if external tools 
are ever going to have any hope of calling unit test functions, then I think 
that it's a necessity

>  > Also, having named unittest blocks would be a necessity for properly
>  > allowing
> 
> external tools to run the unit tests.
> 
> I don't really know how that would work.

I'm not sure either. But ideally, it would be possible to have an external tool 
run a program which runs all of the static constructors and whatever else has to 
be run before the unittest blocks can run, and then specifically run each 
unittest block that it wants to, in whatever order it wants to, without 
necessarily running all of them.

For instance, if you use JUnit with Eclipse, you can tell it to specifically run 
all of the unit tests in a file. Or you can tell it to run only a single unit 
test. Or you can tell it to run them all. Right now, with D, you can only ever 
run them all, and you can't really integrate them into an IDE. I think that that 
should become possible at some point. Unfortunately, however, I don't know 
enough to say exactly how that would work. I don't even know how it works with 
JUnit and Eclipse. It is potentially a very important feature though.

> > I do _not_ want to see D programs printing out anything more than they do
> > when running unit tests. I'm totally open to external tools which do
> > more (it would particularly good for IDEs to be able to run unittest
> > blocks individually), but I do not want to see the basic framework
> > change how it prints feedback on test successes and failures. It works
> > great as it is now.
> 
> Yes, and if anyone wants more, a writefln("your message here") works just
> fine.

Yes. It is a bit of work to make all of your unit tests print success or 
failure, but it's not hard, just tedious. I see no reason to make everyone have 
to put up with extraneous output just because some people want it to print a 
list of successes and failures. If you really want that, you can do it yourself. 
The one part I can think of that you can't do yourself very well is print the 
total number of successes and failures (unless you print a running total with 
each test). But it's possible to build printing functionality on top of a 
framework that only prints out on failure, whereas it's not possible to get rid 
of printing with a framework that always prints.

- Jonathan M Davis


More information about the Digitalmars-d-announce mailing list