August 27, 2021

On Friday, 27 August 2021 at 10:30:58 UTC, deadalnix wrote:

>

This is the wrong question. When one runs the unittests, they don't care about main. The existance of the main flag is dubious to begin with.

Saying "-unittest should have been designed differently" doesn't help us forward. Currently it's possible to have a -betterC test runner in main, or set UnitTestResult.runmain = true from core.runtime, or to create a unittest build with incremental separate compilation, so changing the semantics of the -unittest flag is a very disruptive change.

However, like you said, there is no use case for having two main functions in one build, so I was looking into improving that (see https://github.com/dlang/dmd/pull/13026), which is how I got to that very real implementation question.

jfondren pointed out that the default test runner doesn't run main, so I think it's best to make -main only insert main when there isn't already one being compiled in. Then you should be able to do dmd -unittest -main -run somemodule.d on any (self-contained) d file with consistent results.

August 27, 2021

On 8/27/21 7:19 AM, Johan wrote:

>

On Friday, 27 August 2021 at 10:59:54 UTC, Mathias LANG wrote:

>

On Friday, 27 August 2021 at 10:33:02 UTC, Johan wrote:

>

On Friday, 27 August 2021 at 07:48:40 UTC, Mathias LANG wrote:

>

But when I throw in the -unittest switch (or the -cov or -profile for that matter), I want all the bells and whistles. Give me colors, an ncurses interface, an HTTP server even! Give me the full battery pack. Don't expect everyone and their mother to implement their own testing framework, that's just bonkers.

Why would you expect a compiler to implement a testing framework?

I don't. I expect the runtime to do it. And to give me the tool to override it if I have needs that aren't covered by it. Currently it only does the later.

For this discussion I consider the compiler+runtime to be a single entity, but OK: same question, why should the language runtime do it? I find it a large stretch to include full unittesting framework inside the scope of a language runtime's core functionality.
What is so special about unittesting that requires the implementation to be inside the runtime? The downsides are clear to me (and large), what advantage is gained by having it in the language runtime?

separate compilation. The compiler doesn't know all the modules that have been built with unittests. Only the runtime does.

The compiler embeds unittests into the ModuleInfo, which means after the linking is done, then you can run unittests if there are any.

But the unittest "framework" right now is really simple (it's in fact this function. I think it should stay that way, and if you want it more complex, use your own test system. I spent a good deal of time making it easier to install your own handler (in addition to NOT running main after unittests by default).

One thing that could be useful is a user-specified way to generate unittest metadata. Right now, you just get a single function pointer in ModuleInfo. You lose any UDAs or references to individual unittest functions. Allowing one to specify a metadata collector on unittests would be very useful in separating the unittest framework from the dependencies, and making it much easier to select a different one, regardless of your dependency preferences.

-Steve

August 27, 2021

On 8/27/21 9:35 AM, Dennis wrote:

>

On Friday, 27 August 2021 at 10:30:58 UTC, deadalnix wrote:

>

This is the wrong question. When one runs the unittests, they don't care about main. The existance of the main flag is dubious to begin with.

Saying "-unittest should have been designed differently" doesn't help us forward. Currently it's possible to have a -betterC test runner in main, or set UnitTestResult.runmain = true from core.runtime, or to create a unittest build with incremental separate compilation, so changing the semantics of the -unittest flag is a very disruptive change.

However, like you said, there is no use case for having two main functions in one build, so I was looking into improving that (see https://github.com/dlang/dmd/pull/13026), which is how I got to that very real implementation question.

jfondren pointed out that the default test runner doesn't run main, so I think it's best to make -main only insert main when there isn't already one being compiled in. Then you should be able to do dmd -unittest -main -run somemodule.d on any (self-contained) d file with consistent results.

The default test runner doesn't run main by default, but it also can run main if passed a runtime option. So it still needs a main to run if requested.

I think a "-nomain" option is something worth looking at (basically, the generated extern(C) main function doesn't call a missing dmain function). It can't be tied to -unittest though.

-Steve

August 27, 2021
On 8/26/2021 2:38 AM, deadalnix wrote:
> I can work with having to pass the -main flag. This is not ideal, because what else could I possibly want?

Then we'll get a bug report where the user compiled a module, and it compiled and linked without error, then he runs the program and nothing happens because the compiler inserted an empty main().
August 31, 2021
On Saturday, 28 August 2021 at 00:47:44 UTC, Walter Bright wrote:
> On 8/26/2021 2:38 AM, deadalnix wrote:
>> I can work with having to pass the -main flag. This is not ideal, because what else could I possibly want?
>
> Then we'll get a bug report where the user compiled a module, and it compiled and linked without error, then he runs the program and nothing happens because the compiler inserted an empty main().

No, because it'll run the unittests, and the user will see "X test passed" or something similar when running the executable, understand that they ran the uni tests and go ahead and run the executable that has been built without the unitests flag.

If the user doesn't have any unitests in the executable, then saying "0 test run, 0 passed, 0 failed" is enough to indicated to the user that this was a unitests build.
August 31, 2021

On Friday, 27 August 2021 at 14:13:56 UTC, Steven Schveighoffer wrote:

>

separate compilation. The compiler doesn't know all the modules that have been built with unittests. Only the runtime does.

The is another extremely weird design.

When you run the unitests, you want to run the unitests for the module you specifically asked for, not for half the galaxy.

August 31, 2021
On Tue, Aug 31, 2021 at 11:08:46AM +0000, deadalnix via Digitalmars-d wrote:
> On Friday, 27 August 2021 at 14:13:56 UTC, Steven Schveighoffer wrote:
> > separate compilation. The compiler doesn't know all the modules that have been built with unittests. Only the runtime does.
> > 
> 
> The is another extremely weird design.
> 
> When you run the unitests, you want to run the unitests for the module you specifically asked for, not for half the galaxy.

Yeah, this problem has been brought up before, but nobody had the solution.

The problem is that when you compile with -unittest, it turns on unittests for ALL modules, including Phobos, 3rd party libraries, *everything*.  This is rarely what you want -- Phobos, for example, contains a bunch of extremely heavy duty unittests that end users don't ever want to run.  Because of this, the version=StdUnittest hack was implemented in Phobos.  But this is not scalable: every library and his neighbour's dog would have to implement their own unittest hack version identifier for this scheme to work.  And if some 3rd party library author neglected to do this, you're left out in the cold.

I think it makes much more sense to only compile unittests for modules explicitly specified on the command-line. After all, the user is unlikely to be interested in unittesting 3rd party libraries; his primary interest is to unittest his *own* code.  However, this simplistic approach falls flat in the face of `dmd -i`.  Schemes like `-unittest=mod1,mod2,...` have been proposed before, but AFAIK never implemented.


T

-- 
If it's green, it's biology, If it stinks, it's chemistry, If it has numbers it's math, If it doesn't work, it's technology.
August 31, 2021

On 8/31/21 12:22 PM, H. S. Teoh wrote:

>

On Tue, Aug 31, 2021 at 11:08:46AM +0000, deadalnix via Digitalmars-d wrote:

>

On Friday, 27 August 2021 at 14:13:56 UTC, Steven Schveighoffer wrote:

>

separate compilation. The compiler doesn't know all the modules that
have been built with unittests. Only the runtime does.

The is another extremely weird design.

When you run the unitests, you want to run the unitests for the module
you specifically asked for, not for half the galaxy.

This is exactly how it works. You ask to build unittests for a module by specifying -unittest on the build line for that module. The linker hooks up the module infos into a data segment, and then the runtime looks through those looking for ones with unittests to run.

So for instance, if you link against library X, and you didn't actually build library X with -unittest, then you won't run library X's unittests, only the ones in your project.

But my point is, the reason it's done this way is because D uses the build system that C uses -- which has a compiler and a linker as separate steps.

It's also the reason that cycle detection can't be run until program startup.

>

Yeah, this problem has been brought up before, but nobody had the
solution.

The problem is that when you compile with -unittest, it turns on
unittests for ALL modules, including Phobos, 3rd party libraries,
everything. This is rarely what you want -- Phobos, for example,
contains a bunch of extremely heavy duty unittests that end users don't
ever want to run. Because of this, the version=StdUnittest hack was
implemented in Phobos. But this is not scalable: every library and his
neighbour's dog would have to implement their own unittest hack version
identifier for this scheme to work. And if some 3rd party library
author neglected to do this, you're left out in the cold.

There is some misunderstandings with what you say here.

When you compile with -unittest, all unittests are turned on BUT only unittests in modules given on the command line are semantically analyzed and put into object files. So you don't get all the unittests from 3rd party libraries in your build. However, it does turn on the version(unittest), which might make some libraries alter how their types are defined.

It also will include unittests inside templates that you instantiate (because it doesn't know if those were already done somewhere). This is why I very much dislike unittests inside templates.

The version(StdUnittest) was added so that extra imports, altered and extra types were not included, as well as removing unittests inside templates. It wasn't added to exclude standard unittests (which was already happening).

-Steve

August 31, 2021
On Tuesday, 31 August 2021 at 16:22:17 UTC, H. S. Teoh wrote:
> On Tue, Aug 31, 2021 at 11:08:46AM +0000, deadalnix via Digitalmars-d wrote:
>> On Friday, 27 August 2021 at 14:13:56 UTC, Steven Schveighoffer wrote:
>> > separate compilation. The compiler doesn't know all the modules that have been built with unittests. Only the runtime does.
>> > 
>> 
>> The is another extremely weird design.
>> 
>> When you run the unitests, you want to run the unitests for the module you specifically asked for, not for half the galaxy.
>
> Yeah, this problem has been brought up before, but nobody had the solution.
>
> The problem is that when you compile with -unittest, it turns on unittests for ALL modules, including Phobos, 3rd party libraries, *everything*.  This is rarely what you want -- Phobos, for example, contains a bunch of extremely heavy duty unittests that end users don't ever want to run.  Because of this, the version=StdUnittest hack was implemented in Phobos.  But this is not scalable: every library and his neighbour's dog would have to implement their own unittest hack version identifier for this scheme to work.  And if some 3rd party library author neglected to do this, you're left out in the cold.
>
> I think it makes much more sense to only compile unittests for modules explicitly specified on the command-line. After all, the user is unlikely to be interested in unittesting 3rd party libraries; his primary interest is to unittest his *own* code.  However, this simplistic approach falls flat in the face of `dmd -i`.  Schemes like `-unittest=mod1,mod2,...` have been proposed before, but AFAIK never implemented.
>
>
> T

Then perhaps have some exlude-unittest argument that accepts packages instead?
That would allow to exclude unittest from entire set of modules.

Also why do we even make an executable when we compile with -unittest flag?
Can't we just make a shared lib, and then have a unit test runner loading this lib and run the tests in it?
Imho this would be a lot more flexible. Dmd could come with an default runner, and if someone doesn't like it, he could just replace runner with his own implementation, that would do all the prettyfying he wants.

The only thing needed here is well built unit test api exposed to the runner and reflection api at runtime, to allow that said runner inspect underlying tests for additional information, such as annotations, to do more advanced logic than default runner would do.

Regards,
Alexandru.
August 31, 2021

On Tuesday, 31 August 2021 at 16:52:59 UTC, Alexandru Ermicioi wrote:

>

Imho this would be a lot more flexible. Dmd could come with an default runner, and if someone doesn't like it, he could just replace runner with his own implementation, that would do all the prettyfying he wants.

This is already the case. Replacing the default runner is what silly's doing here: https://gitlab.com/AntonMeep/silly/-/blob/master/silly.d#L29

>

The only thing needed here is well built unit test api exposed to the runner and reflection api at runtime, to allow that said runner inspect underlying tests for additional information, such as annotations, to do more advanced logic than default runner would do.

"at runtime" aside, this is already possible. I give an example of a test runner that runs tests differently depending on its annotations in https://forum.dlang.org/post/bukhjtbxouadyunqwdih@forum.dlang.org