On Sunday, 21 August 2016 at 09:02:09 UTC, Solomon E wrote:
On Saturday, 20 August 2016 at 20:39:13 UTC, Engine Machine
wrote:
We have a unittest, what about an examples?
....
It seems like there could be a library function that
compile-time-reflects to make a collection of all the functions
in a module that have names starting with "maintest" and calls
each of them in a try block, with a catch block that just
prints the error messages to stderr and incorrect return codes
and counts the total fails and finally counts the number of
tests run to return the success ratio.
[replying to my own post]
So here's what I wrote on the idea in D, as it cooled down this
evening. It was an interesting exercise in D style. I kept
getting blocked by suggested features not being available (static
foreach, enum string[], etc.) So I decided to cut it down to
something a little simpler than I was going for, no imports or
string mixins or traits or underscores, just the core D language.
I think it shows there are enough features in D to get something
done like building a test framework that allows multiplying the
number of tests that can be run, at a small overhead in verbosity
(i.e. where I had to repeat the function refs and function names.)
module patterntester;
struct PatternTestResults(returnT)
{
ResultsArray!(returnT)[string] funCases;
int failures;
int successes;
int exceptions;
int tries;
int funs;
ulong cases;
}
struct ResultsArray(returnT)
{
PatternTestResult!returnT[int] res;
}
enum SingleTestStatus { UNTESTED = 0, SUCCESS, FAILURE, EXCEPTION
};
struct PatternTestResult(returnT)
{
returnT returned;
Exception exception;
SingleTestStatus status;
}
struct TestIO(argsT, returnT)
{
argsT arguments;
returnT expect;
this(argsT args, returnT ret)
{
arguments = args;
expect = ret;
}
}
immutable(Match!(argsT, returnT))[] FunctionGetter(string
pattern, argsT,
returnT)()
{
Match!(argsT, returnT)[] matches;
foreach(num, fun; maintests.contents)
{
string sym = maintests.names[num];
if (pattern == sym[0 .. pattern.length])
{
matches ~= Match!(argsT, returnT)(sym, fun);
}
}
return matches.idup;
}
struct Match(argsT, returnT)
{
string funName;
returnT function(argsT) funRef;
this(string funStr, returnT function(argsT) funRefer)
{
funName = funStr;
funRef = funRefer;
}
}
PatternTestResults!returnT PatternTest(string pattern, argsT,
returnT)
(TestIO!(argsT, returnT)[int] tests)
{
enum Match!(argsT, returnT)[] matches
= FunctionGetter!(pattern, argsT, returnT)();
PatternTestResults!returnT results;
alias STS = SingleTestStatus;
foreach(match; matches)
{
string funStr = match.funName;
returnT function(argsT) funref = match.funRef;
results.funCases[funStr] = ResultsArray!returnT();
foreach(tnum, testPair; tests)
{
argsT args = testPair.arguments;
returnT expect = testPair.expect;
auto singleResult = PatternTestResult!returnT();
try
{
auto exitcode = funref(args);
if (exitcode != expect)
{
++results.failures;
singleResult.status = STS.FAILURE;
}
else
{
++results.successes;
singleResult.status = STS.SUCCESS;
}
singleResult.returned = exitcode;
}
catch (Exception ex)
{
++results.failures;
++results.exceptions;
singleResult.exception = ex;
singleResult.status = STS.EXCEPTION;
}
finally
{
++results.tries;
}
results.funCases[funStr].res[tnum] = singleResult;
}
++results.funs;
}
results.cases = tests.length;
assert(results.cases * results.funs == results.tries);
return results;
}
struct Flist(argsT, returnT)
{
alias funt = returnT function(argsT);
funt[] contents;
string[] names;
}
enum maintests = Flist!(string[], int)([&maintestA, &maintestB,
&maintestC,
&maintestD],
["maintestA", "maintestB",
"maintestC",
"maintestD"]);
int maintestA(string[] args)
{
return 0;
}
int maintestB(string[] args)
{
return 1;
}
int maintestC(string[] args)
{
throw new Exception("meant throw");
return 0;
}
int maintestD(string[] args)
{
return 3 * (args[$ - 1] == "-A");
}
unittest
{
string df = "./a.out";
alias fntype = TestIO!(string[], int);
// first test set: one success
auto aresult = PatternTest!("maintestA", string[], int)
([1: fntype([df,"a","b"], 0)]);
assert(aresult.failures == 0);
assert(aresult.tries == 1);
assert(aresult.exceptions == 0);
assert(aresult.successes == 1);
assert(aresult.funCases["maintestA"].res[1].returned == 0);
assert(aresult.funCases["maintestA"].res[1].exception is
null);
alias STS = SingleTestStatus;
assert(aresult.funCases["maintestA"].res[1].status ==
STS.SUCCESS);
// second test set: one failure
auto bresult = PatternTest!("maintestB", string[], int)
([1: fntype([df,"a","b"], 0)]);
assert(bresult.failures == 1);
assert(bresult.tries == 1);
assert(bresult.exceptions == 0);
assert(bresult.successes == 0);
assert(bresult.funCases["maintestB"].res[1].returned == 1);
assert(bresult.funCases["maintestB"].res[1].exception is
null);
// third test set: one exception
auto cresult = PatternTest!("maintestC", string[], int)
([1: fntype([df,"a","b"], 0)]);
assert(cresult.failures == 1);
assert(cresult.tries == 1);
assert(cresult.exceptions == 1);
assert(cresult.successes == 0);
assert(cresult.funCases["maintestC"].res[1].returned ==
int.init);
assert(cresult.funCases["maintestC"].res[1].exception !is
null);
assert(cresult.funCases["maintestC"].res[1].exception.msg ==
"meant throw");
// fourth test set: multiplied tests
auto dresult = PatternTest!("maintest", string[], int)
([1: fntype([df,"a","b"], 0),
2: fntype([df,"c","d"], 0),
3: fntype([df,"-x","-A"], 3)]);
assert(dresult.failures == 7);
assert(dresult.tries == 12);
assert(dresult.exceptions == 3);
assert(dresult.successes == 5);
}
void main(string[] args)
{
assert(args[0] == "./d.out");
}