Mercurial > projects > orange
view orange/test/UnitTester.d @ 27:fc315d786f24 experimental
Added unit testing.
author | Jacob Carlborg <doob@me.com> |
---|---|
date | Fri, 19 Nov 2010 11:14:55 +0100 |
parents | 78e5fef4bbf2 |
children | 4fea56a5849f |
line wrap: on
line source
/** * Copyright: Copyright (c) 2010 Jacob Carlborg. All rights reserved. * Authors: Jacob Carlborg * Version: Initial created: Oct 17, 2010 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) */ module orange.test.UnitTester; version (Tango) { import tango.io.device.File; import tango.io.stream.Lines; import tango.util.Convert; import tango.sys.Environment; } else import std.conv; import tango.core.Exception; import tango.io.FilePath; import orange.core._; import orange.util._; Use!(void delegate (), string) describe (string message) { return UnitTester.instance.describe(message); } Use!(void delegate (), string) it (string message) { return UnitTester.instance.test(message); } void delegate () before () { return UnitTester.instance.before; } void delegate () before (void delegate () before) { return UnitTester.instance.before = before; } void delegate () after () { return UnitTester.instance.after; } void delegate () after (void delegate () after) { return UnitTester.instance.after = after; } void run () { UnitTester.instance.run; } private: class UnitTester { private: struct DescriptionManager { Description[] descriptions; size_t lastIndex = size_t.max; void opCatAssign (Description description) { descriptions ~= description; lastIndex++; } void opCatAssign (Test test) { last.tests ~= test; } Description opIndex (size_t i) { return descriptions[i]; } Description last () { return descriptions[$ - 1]; } Description first () { return descriptions[0]; } int opApply (int delegate(ref Description) dg) { int result = 0; foreach (desc ; descriptions) { result = dg(desc); if (result) return result; } return result; } size_t length () { return descriptions.length; } } class Description { private { DescriptionManager descriptions; Test[] tests; Test[] failures; Test[] pending; size_t lastIndex = size_t.max; string message; void delegate () description; } this (string message) { this.message = message; } void run () { if (shouldRun) description(); } bool shouldRun () { return description !is null; } } struct Test { void delegate () test; string message; AssertException exception; bool failed () { return !succeeded; } bool succeeded () { if (exception is null) return true; return false; } void run () { if (!isPending) test(); } bool isPending () { return test is null; } } static UnitTester instance_; DescriptionManager descriptions; Description currentDescription; void delegate () before_; void delegate () after_; size_t numberOfFailures; size_t numberOfPending; size_t numberOfTests; size_t failureId; string defaultIndentation = " "; string indentation; static UnitTester instance () { if (instance_) return instance_; return instance_ = new UnitTester; } Use!(void delegate (), string) describe (string message) { addDescription(message); Use!(void delegate (), string) use; use.args[0] = &internalDescribe; use.args[1] = message; return use; } Use!(void delegate (), string) test (string message) { addTest(message); Use!(void delegate (), string) use; use.args[0] = &internalTest; use.args[1] = message; return use; } void run () { foreach (description ; descriptions) runDescription(description); printResult; } void runDescription (Description description) { restore(currentDescription) in { currentDescription = description; description.run; foreach (desc ; description.descriptions) runDescription(desc); foreach (ref test ; description.tests) { if (test.isPending) addPendingTest(description, test); try { execute in { test.run(); }; } catch (AssertException e) handleFailure(description, test, e); } }; } void delegate () before () { return before_; } void delegate () before (void delegate () before) { return before_ = before; } void delegate () after () { return after_; } void delegate () after (void delegate () after) { return after_ = after; } void addTest (string message) { numberOfTests++; currentDescription.tests ~= Test(null, message); } void addDescription (string message) { if (currentDescription) currentDescription.descriptions ~= new Description(message); else descriptions ~= new Description(message); } void addPendingTest (Description description, ref Test test) { numberOfPending++; description.pending ~= test; } void handleFailure (Description description, ref Test test, AssertException exception) { numberOfFailures++; test.exception = exception; description.failures ~= test; } void internalDescribe (void delegate () dg, string message) { if (currentDescription) currentDescription.descriptions.last.description = dg; else descriptions.last.description = dg; } void internalTest (void delegate () dg, string message) { currentDescription.tests[$ - 1] = Test(dg, message); } void printResult () { if (isAllTestsSuccessful) return printSuccess(); foreach (description ; descriptions) { printDescription(description); printResultImpl(description.descriptions); } failureId = 0; printPending; printFailures; print("\n", numberOfTests, " ", "test".pluralize(numberOfTests),", ", numberOfFailures, " ", "failure".pluralize(numberOfFailures)); printNumberOfPending; println(); } void printResultImpl (DescriptionManager descriptions) { restore(indentation) in { indentation ~= defaultIndentation; foreach (description ; descriptions) { printDescription(description); printResultImpl(description.descriptions); } }; } void printDescription (Description description) { println(indentation, description.message); restore(indentation) in { indentation ~= defaultIndentation; foreach (i, ref test ; description.tests) { print(indentation, "- ", test.message); if (test.isPending) print(" (PENDING: Not Yet Implemented)"); if (test.failed) print(" (FAILED - ", ++failureId, ')'); println(); } }; } void printPending () { if (!hasPending) return; println("\nPending:"); restore(indentation) in { indentation ~= defaultIndentation; foreach (description ; descriptions) { printPendingDescription(description); printPendingImpl(description.descriptions); } }; } void printPendingImpl (DescriptionManager descriptions) { foreach (description ; descriptions) { printPendingDescription(description); printPendingImpl(description.descriptions); } } void printPendingDescription (Description description) { foreach (test ; description.pending) println(indentation, description.message, " ", test.message, "\n", indentation, indentation, "# Not Yet Implemented"); } void printFailures () { if (!hasFailures) return; println("\nFailures:"); restore(indentation) in { indentation ~= defaultIndentation; foreach (description ; descriptions) { printFailuresDescription(description); printFailuresImpl(description.descriptions); } }; } void printFailuresImpl (DescriptionManager descriptions) { foreach (description ; descriptions) { printFailuresDescription(description); printFailuresImpl(description.descriptions); } } void printFailuresDescription (Description description) { foreach (test ; description.failures) { auto str = indentation ~ to!(string)(++failureId) ~ ") "; auto whitespace = toWhitespace(str.length); println(str, description.message, " ", test.message); println(whitespace, "# ", test.exception.file, ".d:", test.exception.line); println(whitespace, "Stack trace:"); print(whitespace); test.exception.writeOut(&printStackTrace); println(); //println(readFailedTest(test, 0)); } } void printStackTrace (string str) { return print(str); /*if (str.find("start") < size_t.max || str.find("main") < size_t.max || str.find("rt.compiler.") < size_t.max || str.find("orange.") || str.find(":0") || str.find("_d_assert") || str.find("onAssertError") || str.find("tango.core.Exception.AssertException._ctor ") || str.find("object.") || str.find("tango.core.tools.")) return;*/ } string readFailedTest (ref Test test, int numberOfSurroundingLines = 3) { auto filename = test.exception.file.dup.replace('.', '/'); filename ~= ".d"; filename = Environment.toAbsolute(filename); auto lineNumber = test.exception.line; string str; auto file = new File(filename); foreach (i, line ; new Lines!(char)(file)) if (i >= (lineNumber - 1) - numberOfSurroundingLines && i <= (lineNumber - 1) + numberOfSurroundingLines) str ~= line ~ '\n'; file.close; return str; } void printNumberOfPending () { if (hasPending) print(", ", numberOfPending, " pending"); } void printSuccess () { println("All ", numberOfTests, " test".pluralize(numberOfTests), " passed successfully."); } bool isAllTestsSuccessful () { return !hasPending && !hasFailures; } bool hasPending () { return numberOfPending > 0; } bool hasFailures () { return numberOfFailures > 0; } Use!(void delegate ()) execute () { Use!(void delegate ()) use; use.args[0] = &executeImpl; return use; } void executeImpl (void delegate () dg) { auto before = this.before; auto after = this.after; if (before) before(); if (dg) dg(); if (after) after(); } string toWhitespace (size_t value) { string str; for (size_t i = 0; i < value; i++) str ~= ' '; return str; } string pluralize (string str, int value) { if (value == 1) return str; return str ~ "s"; } }