Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is unit testing not working in this D program?

Why does unit testing work for program 1, but not for program 2 below?

Program 1

import std.stdio;

unittest
{
    assert(false);
}

void main()
{
    writeln("Hello D-World!");
}

Program 2

module winmain;

import core.sys.windows.windows;

unittest {
    assert(false);
}

extern (Windows)
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow)
{
    return 0;
}

Both programs were compiled with the -unittest option (by running dmd -unittest <program.d>). When run, program 1 displays the unit test failure, but program 2 does not. What am I missing?

Update: reformulated question & added working example.

Update 2: also compiled with dmd -debug -unittest <program.d>, with similar results.

like image 289
Dan Nestor Avatar asked Dec 20 '14 12:12

Dan Nestor


People also ask

How do I enable unit testing?

Turn live unit testing from the Test menu by choosing Test > Live Unit Testing > Start.

What are the errors in unit testing?

Unit testing considerations What errors are commonly found during Unit Testing? (1) Misunderstood or incorrect arithmetic precedence, (2) Mixed mode operations, (3) Incorrect initialization, (4) Precision inaccuracy, (5) Incorrect symbolic representation of an expression.

Why unit testing is not recommended in agile?

Summary: Agile projects assume that test planning, test creation, and test execution take place throughout a project's lifecycle. So the need for unit testing (and especially automated unit testing) can't be ignored and should be considered as a key responsibility of the entire team—not just the software developers.

What is not included in unit testing?

Unit Testing will not cover all the errors in the module because there is a chance of having errors in the modules while doing integration testing. Unit Testing is not efficient for checking the errors in the UI(User Interface) part of the module.


1 Answers

The answer is kinda simple: in program one, the unittests are actually run, by the runtime in program two, they aren't because the unit test function is never called, since declaring your own WinMain (or even extern(C) main) bypasses the runtime initialization and setup that is usually done automatically before it calls your D main - code done in a C main.

Open up your dmd zip and get to the file dmd2/src/druntime/src/rt/dmain2.d. Find the function _d_run_main().

When a D program with a regular D main is started up, the compiler inserts a C main which calls _d_run_main(). This function, as you can see looking through the source, does a bunch of things:

  • it initializes the floating point hardware to the mode D expects
  • it formats the command line arguments into D strings
  • it initializes the runtime
  • it runs the unit tests <<--- super important to you!
  • it runs the D main, wrapped in a try/catch block for default exception handling
  • it terminates the runtime
  • it flushes output and returns

Yes, on line 399 (of the version I have, might be a bit different in your version of druntime's source), you'll see these lines:

    if (rt_init() && runModuleUnitTests()) 
        tryExec({ result = mainFunc(args); });

Yup, unit tests are run separately from rt_init (also known as Runtime.initialize). The way the compiler -unittest switch works is it just doesn't compile the unittest functions, so runModuleUnitTests sees a bunch of null tests, which it skips. Thus you can call the function in your custom main without worrying about the compiler switch.

Since you have a custom main and didn't call runModuleUnitTests (defined in core.runtime btw), the unit tests never happen. They are called before D main, but still inside c main or Winmain.

My recommendation is to avoid the use of WinMain in D, instead preferring to write regular D mains. You can get the arguments passed to WinMain with API functions like GetCommandLineW, and GetModuleHandle. (nCmdShow is rarely used anyway and I think hPrevInstance is legacy leftover from the 16 bit days, so I doubt you'd care about them anyway!)

The presence of WinMain also signals the linker that you're writing a GUI program and thus should use the Windows subsystem - you don't get a console. You can do this explicitly too by passing -L/SUBSYSTEM:WINDOWS:5.0 to dmd when compiling on Windows 32 bit. (The /SUBSYSTEM argument is one of optlink's switches.) On Windows 64, I'm not sure, but it is probably similar if not identical - check the Microsoft linker's docs for choosing subsystem, I'm sure it is there.

Between that linker switch and the two API calls to fetch the arguments, you don't need WinMain anymore so it saves you the trouble of reimplementing what the runtime's _d_run_main function does yourself.

If you do want to use it anyway, there's two options: just call _d_run_main - look at the source code for the signature it expects. It takes a pointer to the main function so you could reuse all that. Or, you can import core.runtime; and call the Runtime.initialize(); runModuleUnitTests(); your main here... Runtime.terminate(); yourself. Don't forget to check return values and handle exceptions too! You need to do it in the right order and handle errors correctly or you'll see crashes.

All this same applies if you are writing your own extern(C) main as well as your own WinMain.

Again though, you're probably better off avoiding it and just writing a regular D main function, with the linker switch to turn off the console on your gui app.

like image 182
Adam D. Ruppe Avatar answered Nov 11 '22 20:11

Adam D. Ruppe