/** A D 2.0 unit test framework inspired by Ruby's Unit::Test // Written in the D programming language 1.0 || 2.0 Authors: Wei Li (oldrev@gmail.com) License: BSD Copyright: Copyright (C) 2007 by Wei Li. */ import std.stdio; version(Windows) private invariant char PATH_SEP = '\\'; else private invariant char PATH_SEP = '/'; //////////////////////////////////////////////////////////////////////////////// struct Failure { string location; string message; string testName; } //////////////////////////////////////////////////////////////////////////////// struct Error { Exception exception; string testName; string location; } //////////////////////////////////////////////////////////////////////////////// class TestResult { private Error[] m_errors; private Failure[] m_fails; private int m_runCount; private int m_assertionCount; private int m_testCount; const(Error)[] errors() { return m_errors; } const(Failure)[] failures() { return m_fails; } void addFailure(const string loc, const string msg, const string name) { Failure f; with(f) { location = loc; message = msg; testName = name; } m_fails ~= f; } void addError(Exception ex, const string name, const string loc) { Error e; with(e) { exception = ex; testName = name; location = loc; } m_errors ~= e; } void addAssertion() { m_assertionCount++; } void addTest() { m_testCount++; } void addRun() { m_runCount++; } bool hasPassed() { return m_errors.length == 0 && m_fails.length == 0; } int errorCount() { return cast(int)m_errors.length; } int failureCount() { return cast(int)m_fails.length; } int runCount() { return m_runCount; } int testCount() { return m_testCount; } int assertionCount() { return m_assertionCount; } } //////////////////////////////////////////////////////////////////////////////// abstract class TestBase { protected this() { } abstract void run(TestResult result); abstract const bool isRunning(); abstract const string className(); const string fileName() { string ret = className.idup; foreach(inout char c; ret) if(c == '.') c = PATH_SEP; ret ~= ".d"; return ret; } static string removeModuleName(const string name) { size_t i = 0; foreach_reverse(char c; name) { if(c == '.') break; i++; } return name[$ - i .. $].idup; } const string name() { return removeModuleName(className); } } //////////////////////////////////////////////////////////////////////////////// abstract class TestCase(Subclass) : TestBase { version(D_Version2) { private static const(string) ctfMakeString(T)() { string ret; foreach(str; __traits(derivedMembers, T)) { if(str[0..4] == "test") ret ~= `registerTest("` ~ str ~ `", &sc.` ~ str ~ `); ` ~ "\n"; } return ret; } } alias typeof(this) SelfType; struct Test { string name; void delegate() method; } public string m_className = Subclass.classinfo.name.idup; private Test[] m_methods; private TestResult m_result; private size_t m_currentMethod; private bool m_isFailed; private bool m_running = false; this() { } private void initial(const Subclass sc) { version(D_Version2) { mixin(ctfMakeString!(Subclass)()); } } const const(Test)[] tests() { return m_methods; } override const string className() { return m_className; } private void registerTest(Test t) { m_methods ~= t; } private void registerTest(const string name, void delegate() method) { registerTest(Test(name, method)); } static Subclass createChild() { auto o = new Subclass; o.initial(o); return o; } void setup() { } void teardown() { } override const bool isRunning() { return m_running; } override void run(TestResult result) { m_result = result; m_result.addRun(); foreach(size_t i, Test t; m_methods) { m_isFailed = false; m_currentMethod = i; m_result.addTest(); setup(); m_running = true; try { t.method(); } catch(Exception ex) { m_result.addError(ex, currentMethodName, fileName()); } finally { m_running = false; } teardown(); } } const string currentMethodName() { return name() ~ "." ~ m_methods[m_currentMethod].name; } private void addFailure(const string message = null) { if(!m_isFailed) { m_isFailed = true; m_result.addFailure(fileName(), message, currentMethodName); } } //////////////////////////// Assertion Functions /////////////////////////// void assertTrue(bool x, const string message = null) { m_result.addAssertion(); if(!x) { addFailure(message); } } void assertNull(T)(const T value, const string message = null) { m_result.addAssertion(); if(value !is null) { addFailure(message); } } void assertNotNull(T)(const T value, const string message = null) { m_result.addAssertion(); if(value is null) { addFailure(message); } } void assertEqual(T)(const T expected, const T actual, const string message = null) { m_result.addAssertion(); if(expected != actual) { addFailure(message); } } void assertNotEqual(T)(const T expected, const T actual, const T delta, const string message = null) { m_result.addAssertion(); if(expected == actual) { addFailure(message); } } void flunk(const string message = "Flunked") { m_result.addAssertion(); addFailure(message); } } //////////////////////////////////////////////////////////////////////////////// class TestSuit(Subclass, Tests...) : TestBase { alias typeof(this) SelfType; public const string m_className = Subclass.classinfo.name; private TestBase[] m_tests; private bool m_running = false; this() { m_running = false; foreach(T; Tests) { T test = T.createChild(); addTest(test); } } override const string className() { return m_className; } static Subclass createChild() { return new Subclass; } const(TestBase)[] tests() { return m_tests; } void addTest(TestBase tb) in { assert(tb !is null); } body { m_tests ~= tb; } const bool empty() { return Tests.length == 0; } override const bool isRunning() { return m_running; } override void run(TestResult result) { m_running = true; foreach(test; m_tests) { test.run(result); } m_running = false; } } static class ConsoleRunner { static void showFailures(TestResult tr) { foreach(fail; tr.failures) { writefln("Failure: %s", fail.message); writefln("%s [%s]", fail.testName, fail.location); writefln(); } } static void showErrors(TestResult tr) { foreach(err; tr.errors) { writefln("Error: %s", err.exception.msg); writefln("%s [%s]", err.testName, err.location); writefln(); } } static TestResult run(TestBase tb) { auto result = new TestResult; writefln("Started..."); tb.run(result); writefln("Finished\n"); showErrors(result); showFailures(result); writefln(); writefln("%d tests, %d assertions, %d failures, %d errors", result.testCount, result.assertionCount, result.failureCount, result.errorCount); if(result.hasPassed) writefln("All passed."); return result; } } ////////////////////////////////// SAMPLE ////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// class MyTestCase : TestCase!(MyTestCase) { void testOne() { assertTrue(false, "A stupid assertion"); assertTrue(true); assertTrue(true); throw new Exception("Exception raised"); } void testTwo() { assertTrue(true); } void testThree() { assertTrue(true); } } class MyTestCase2 : TestCase!(MyTestCase2) { override void setup() { } override void teardown() { } void testOne() { assertTrue(true); } void testTwo() { assertTrue(true); } void testThree() { assertTrue(false, "Yet another stupid assertion"); } } class MyTestCase3 : TestCase!(MyTestCase3) { void testMethod() { assertTrue(true); } } class MyTestSuit1: TestSuit!(MyTestSuit1, MyTestCase) { } class MyTestSuit2: TestSuit!(MyTestSuit2, MyTestCase2) { } class MyTestSuit3: TestSuit!(MyTestSuit3, MyTestSuit1, MyTestSuit2, MyTestCase3) { } void main() { auto ts = new MyTestSuit3; ConsoleRunner.run(ts); }