Unit test runner for 0m/03 and for 1a/01 and later

I dropped some earlier versions of this in other threads, but here’s an up to date version of my unit.dg (formerly testrunner.dg) extension for anyone who wants to write unit tests for their Dialog projects. You can get the up-to-date version out of the Dialog repo on Github, and it will be included in 1a/02 when that comes out, but here’s a version that will continue to work with 0m/03 if you’re still on that:

%% unit.dg
(extension version)	Unit test runner 1a/01 by Susan Davis (line)

%% See example(s) in test/unit in Dialog source distribution.
%% Licensed under the same license as the rest of Dialog.

%% (test #foo) expr will pass if expr is true and fail if expr is false.
%% Include #foo in the global variable (list of tests [test1 test2 ...])

(interface (test $<Testname))

(interface (set up $<Testname)) %% special setup for the test

(interface (clean up $<Testname)) %% restore to original state afterward

(interface (assert $<Var = $<Val))

(keep running on failure) %% negate to stop at first failed test

%% We can't just (exhaust *(test $)), because that actually runs each test
%% without setup, cleanup, or failure checks, so we work around that with
%% a global variable, populated with all the tests. You can quarantine tests
%% by commenting out their entry in the global variable declaration.

(global variable (list of tests $))

%%----------------------------- IMPLEMENTATION -----------------------------

%% We almost don't need this, since that final line already does what we
%% want, but adding binding checks can be useful.

(assert $Thing1 = $Thing2) %% true if both are bound and ($Thing1 = $Thing2)
	(fully bound $Thing1)
	(bound $Thing2)
	($Thing1 = $Thing2)

(all tests pass)

(program entry point)
	(list of tests $Tests)
	(length of $Tests into $Number)
	(bold) Attempting $Number tests (roman) (line)
	(stoppable) (exhaust) {
		*($Test is one of $Tests)
		Testing $Test:
		(run-test $Test)
	}
	(if) (all tests pass) (then)
		(bold) $Number tests passed successfully. (roman) (par)
		(quit 0)
	(else)
		(bold) FAILED TEST. (roman) (par)
		(quit 1)
	(endif)

(error $Code entry point)
	(bold) FAILED TEST -- FATAL ERROR $Code. (roman) (par)
	(now) ~(all tests pass)
	(quit minus 1)

(run-test $Test)
	(if)
		(stoppable) (set up $Test)   %% don't fail if absent
		(test $Test)
		(stoppable) (clean up $Test) %% don't fail if absent
	(then)
		Passed! (line)
	(else)
		(bold) Failed. (roman) (space) :-\( (line)
		(now) ~(all tests pass)
		(if) ~(keep running on failure) (then) (stop) (endif)
	(endif)

%% patch for ($ contains sublist $) issues; delete when fixed
($List contains sublist [$Head | $Tail])
	*(split $List by [$Head] into $ and $Rest)
	(append $Tail $ $Rest)

%% Delete these two interfaces and predicates after adding them to
%% dialogc and dgdebug.
(interface (quit $<ExitStatus)) %% specify exit status
(quit $)   (quit)

(interface (quit minus $<ExitStatus)) %% specify negative exit status
(quit minus $)   (quit)

1 Like

At the moment, dgdebug doesn’t let you specify the exit status, so make can’t fail the build from a failing test. The workaround for now is to pipe the output to grep "tests passed successfully", which will fail if you’ve had a test faiure.

(ETA: Hmm, I should probably add that to the code as a comment.)

Ah right, I was going to add that capability! I completely forgot. Let me add it to the issue tracker; it shouldn’t be hard to implement.

That was on my personal “to do” list for after my next task (outputting in green or red depending on whether a test has failed), but if someone else wants to get to it, go for it.

(The code above with (quit $) and (quit minus $) was what I had in mind, with dialogc silently ignoring the arguments and using them as synonyms for (quit), and dgdebug exiting with that status, or negative that status for (quit minus $), respectively.)

1 Like