Paste: clay test framework

Author: j
Mode: text
Date: Mon, 2 Aug 2010 21:31:16
Plain Text |
record TestStatus {
    passed: UInt;
    failed: UInt;
}

overload TestStatus() = TestStatus(UInt(0), UInt(0));

record TestCase[F] {
    name: StringConstant;
    case: F;
    pending?: Bool;
}

[F] overload TestCase(name, fn:F) = TestCase[F](name, fn, false);

[F] PendingTestCase(name, fn:F) = TestCase[F](name, fn, true);

record TestSuite[TestCases] {
    name: StringConstant;
    testCases: TestCases;
}

alias TEST_CASE_INDENT = "  ";
alias TEST_FAILURE_INDENT = TEST_CASE_INDENT + TEST_CASE_INDENT;

accumulateTestStatus(test: TestStatus, newTest: TestStatus) {
    test.passed += newTest.passed;
    test.failed += newTest.failed;
}

reportTest(test: TestStatus, ...prefix) {
    println(...prefix, test.passed, " expectations passed; ", test.failed, " failed");
}

[TestCases] runTestSuite(suite: TestSuite[TestCases]) TestStatus {
    var test = TestStatus();
    println("Running test suite ", suite.name, ":");
    eachValue(
        lambda(case) { accumulateTestStatus(test, runTestCase(case)); },
        ...unpack(suite.testCases)
    );
    reportTest(test, TEST_CASE_INDENT, "-- ");
    return test;
}

pendingMessage(pending?) {
    if (pending?) {
        return " (pending)";
    } else {
        return "";
    }
}

printFailure(...xs) { println(TEST_FAILURE_INDENT, ...xs); }

[F] runTestCase(case: TestCase[F]) TestStatus {
    print(TEST_CASE_INDENT, case.name, pendingMessage(case.pending?), ":");
    flush(stdout);
    
    var test = TestStatus();
    case.case(test);

    if (test.failed > 0) {
        if (case.pending?) {
            printFailure("-- pending");
            test.failed = 0;
        } else {
            printFailure("-- FAILED");
        }
    } else {
        if (case.pending?) {
            println();
            printFailure("-- FINISHED (clear pending flag)");
            test.failed = 1;
        } else {
            println("passed");
        }
    }

    return test;
}

passed(test) { test.passed += 1; }
failed(test) { test.failed += 1; }

// primitive assertion function
expect(test: TestStatus, fn, failReportFn) {
    if (fn()) {
        passed(test);
    } else {
        failed(test);
        println();
        failReportFn();
    }
}

expectTrue(test: TestStatus, name, fn) {
    expect(test, fn, lambda() { printFailure(name, " didn't return true"); });
}

expectFalse(test: TestStatus, name, fn) {
    expect(
        test,
        lambda() { return not fn(); },
        lambda() { printFailure(name, " didn't return false"); }
    );
}

expectValues(test: TestStatus, name, fn, ...results) {
    expect(
        test,
        lambda() { return Tuple(...fn()) == Tuple(...results); },
        lambda() {
            printFailure(name, " didn't return the expected values:");
            printFailure(TEST_CASE_INDENT, ...weaveValues(", ", ...results));
        }
    );
}

expectCallDefined(test: TestStatus, Proc, ...Types) {
    expect(
        test,
        lambda() { return CallDefined?(Proc, ...Types); },
        lambda() {
            printFailure(Proc, " can't be called with the expected argument types:");
            printFailure(TEST_CASE_INDENT, ...weaveValues(", ", ...Types));
        }
    );
}

expectCallUndefined(test: TestStatus, Proc, ...Types) {
    expect(
        test,
        lambda() { return not CallDefined?(Proc, ...Types); },
        lambda() {
            printFailure(Proc, " shouldn't be callable with the expected argument types:");
            printFailure(TEST_CASE_INDENT, ...weaveValues(", ", ...Types));
        }
    );
}

testMain(...suites) {
    var test = TestStatus();
    eachValue(
        lambda(suite) { accumulateTestStatus(test, runTestSuite(suite)); },
        ...suites
    );

    reportTest(test, "Total ");

    if (test.failed > 0) {
        println("TESTS FAILED");
        return 1;
    } else {
        return 0;
    }
}

///////////////////// main.clay


import test.*;

main() = testMain(
    TestSuite(
        "test test suite", Tuple(
            TestCase("should pass", lambda(test) {
                expectTrue(test, "truth", lambda() { return true; });
                expectFalse(test, "falsehood", lambda() { return false; });
                expectValues(test, "unity", lambda() { return 1; }, 1);
                expectValues(test, "unity and trinity", lambda() { return 1, 3; }, 1, 3);
                expectCallDefined(test, add, Int, Int);
                expectCallUndefined(test, add, String, Int);
            }),
            TestCase("should fail", lambda(test) {
                expectTrue(test, "truth", lambda() { return false; });
                expectFalse(test, "falsehood", lambda() { return true; });
                expectValues(test, "unity", lambda() { return 3; }, 1);
                expectValues(test, "unity and trinity", lambda() { return 1, 2; }, 1, 3);
                expectCallDefined(test, add, Int, String);
                expectCallUndefined(test, add, String, String);
            }),
            PendingTestCase("pending should pass", lambda(test) {
                expectTrue(test, "truth", lambda() { return true; });
            }),
            PendingTestCase("pending should fail", lambda(test) {
                expectTrue(test, "truth", lambda() { return false; });
            })
        )
    )
);

New Annotation

Summary:
Author:
Mode:
Body: