Mercurial > projects > orange
diff 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 diff
--- a/orange/test/UnitTester.d Tue Oct 19 10:22:10 2010 +0200 +++ b/orange/test/UnitTester.d Fri Nov 19 11:14:55 2010 +0100 @@ -6,56 +6,215 @@ */ 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 + private: + + struct DescriptionManager { - struct Test + Description[] descriptions; + size_t lastIndex = size_t.max; + + void opCatAssign (Description description) + { + descriptions ~= description; + lastIndex++; + } + + void opCatAssign (Test test) { - void delegate () test; - string message; - AssertException exception; + 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; - bool failed () + foreach (desc ; descriptions) { - return !succeeded; - } - - bool succeeded () - { - return !exception; + result = dg(desc); + + if (result) + return result; } - void run () - { - if (!isPending) - test(); - } - - bool isPending () - { - return test is null; - } + 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; } - Test[] tests; - AssertException[] exceptions; - void delegate () pre_; - void delegate () post_; - size_t failures; - size_t pending; - size_t lastIndex = size_t.max; - } + 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) { - tests ~= Test(null, message); - lastIndex++; - + addTest(message); Use!(void delegate (), string) use; use.args[0] = &internalTest; @@ -65,101 +224,298 @@ } void run () - { - foreach (test ; tests) - { - if (test.isPending) - pending++; - - try - { - execute in { - test.run(); - }; - } - - catch (AssertException e) - { - exceptions ~= e; - failures++; - } - } - + { + foreach (description ; descriptions) + runDescription(description); + printResult; } - void delegate () pre () + void runDescription (Description description) { - return pre_; + 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 () pre (void delegate () pre) + void delegate () before () { - return pre_ = pre; + return before_; + } + + void delegate () before (void delegate () before) + { + return before_ = before; } - void delegate () post () + void delegate () after () { - return post_; + return after_; } - void delegate () post (void delegate () post) + void delegate () after (void delegate () after) + { + return after_ = after; + } + + void addTest (string message) { - return post_ = post; + numberOfTests++; + currentDescription.tests ~= Test(null, message); + } + + void addDescription (string message) + { + if (currentDescription) + currentDescription.descriptions ~= new Description(message); + + else + descriptions ~= new Description(message); } - private void internalTest (void delegate () dg, string message) + void addPendingTest (Description description, ref Test test) { - tests[lastIndex] = Test(dg, message); + numberOfPending++; + description.pending ~= test; + } + + void handleFailure (Description description, ref Test test, AssertException exception) + { + numberOfFailures++; + test.exception = exception; + description.failures ~= test; } - private void printResult () + 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 (test ; tests) + foreach (description ; descriptions) { - print("- ", test.message); - - if (test.isPending) - print(" ", "(PENDING: Not Yet Implemented)"); - - println(); + printDescription(description); + printResultImpl(description.descriptions); } - print("\n", tests.length, " test, ", failures, " failures"); - printPending(); + failureId = 0; + + printPending; + printFailures; + + print("\n", numberOfTests, " ", "test".pluralize(numberOfTests),", ", numberOfFailures, " ", "failure".pluralize(numberOfFailures)); + printNumberOfPending; println(); } - private void printPending () + void printResultImpl (DescriptionManager descriptions) + { + restore(indentation) in { + indentation ~= defaultIndentation; + + foreach (description ; descriptions) + { + printDescription(description); + printResultImpl(description.descriptions); + } + }; + } + + void printDescription (Description description) { - if (hasPending) - print(", ", pending, " pending"); + 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"); } - private void printSuccess () + void printFailures () + { + if (!hasFailures) + return; + + println("\nFailures:"); + + restore(indentation) in { + indentation ~= defaultIndentation; + + foreach (description ; descriptions) + { + printFailuresDescription(description); + printFailuresImpl(description.descriptions); + } + }; + } + + void printFailuresImpl (DescriptionManager descriptions) { - println("All ", tests.length, " tests passed successfully."); + 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)); + } } - private bool isAllTestsSuccessful () + 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; } - private bool hasPending () + bool hasPending () { - return pending > 0; + return numberOfPending > 0; } - private bool hasFailures () + bool hasFailures () { - return failures > 0; + return numberOfFailures > 0; } - private Use!(void delegate ()) execute () + Use!(void delegate ()) execute () { Use!(void delegate ()) use; @@ -168,10 +524,31 @@ return use; } - private void executeImpl (void delegate () dg) + 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) { - if (pre) pre(); - if (dg) dg(); - if (post) post(); + 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"; } } \ No newline at end of file