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");
}

Reply via email to