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