Module: DFMC-Testing Author: Jonathan Bachrach and Steve Rowley Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND define variable *tests* = make(); // Test repository define variable *test-successes* = make(); // Result repository define variable *test-failures* = make(); // Result repository define variable *test-flamers* = make(); // Result repository define function clear-tests() => () // Clear away old test results, if any. size(*test-successes*) := 0; size(*test-failures*) := 0; size(*test-flamers*) := 0 end; // Function for use in tests... define function compile-library-until-optimized (lib) compile-library-from-definitions(lib, force?: #t, skip-link?: #t); end function; define function print-test-report (#key stream = *standard-output*, title = "DFMC Test Suite Report:", test-successes = *test-successes*, test-failures = *test-failures*, test-flamers = *test-flamers*, lift-rocks? = #f, cursor-x-max = 80, cursor-x-min = size("SUCCEEDING: ")) => () // Print test report in reasonably readable format. let cursor-x = 0; let indent-string = make(, size: cursor-x-min, fill: ' '); local method key< (k1 :: , k2 :: ) => (lt :: ) // Sort for keywords as(, k1) < as(, k2) end, method print-callout(stream, title) // Print the bars above & below the title. newline(stream); for (x in title) write-element(stream, '=') end end, method newline (stream, #key indent?) // Newline & maybe indent, updating x cursorpos. write-element(stream, '\n'); if (indent?) write(stream, indent-string); cursor-x := cursor-x-min else cursor-x := 0 end end, method print-item(item, stream) // Printer who breaks lines & indents when necessary. let item-string = as-lowercase(as(, item)); let item-length = size(item-string); let new-cursor-x = cursor-x + item-length + 2; // Include comma & space if (new-cursor-x <= cursor-x-max) cursor-x := new-cursor-x else newline(stream, indent?: #t); cursor-x := cursor-x + item-length + 2 // off by one on last item end; write(stream, item-string) end, method print-test-list (tests) // Print a list of comma-separated test names or "(None)" cursor-x := cursor-x-min; if (empty?(tests)) write(stream, "(None)") else print-separated-collection( sort(tests, test: key<), stream: stream, conjunction: "&", printer: print-item) end end; let n-successes = size(test-successes); let n-failures = size(test-failures); let n-flames = size(test-flamers); // Title & summary print-callout(stream, title); format(stream, "\n%s %d successes + %d failures + %d flames = %d total.", title, n-successes, n-failures, n-flames, n-successes + n-failures + n-flames); print-callout(stream, title); // Details format(stream, "\n\nSUCCEEDING: "); print-test-list(test-successes); format(stream, "\n\nFAILING: "); print-test-list(test-failures); format(stream, "\n\nFLAMING: "); print-test-list(test-flamers); format(stream, "\n\n"); when (lift-rocks? & size(*test-flamers*) ~= 0) for (rock in sort!(copy-sequence(*test-flamers*), test: key<)) block () // Lift suspicious rocks & see what run-tests(tests: list(rock), // kinds of slimy bugs crawl out safely?: #f, // So errors not caught higher up progress?: #f, // Gag progress report?: #f, // Gag reporting lift-rocks?: #f) // Don't recurse exception (e :: ) // Talk about the error format(stream, "Error in test %=: %s\n\n", rock, e) end end end; values() end; define function do-with-testing-context (code :: , library-or-false) with-library-context (library-or-false | dylan-library-compilation-context()) without-dependency-tracking code() end; end; end function; define macro with-testing-context { with-testing-context (?:expression) ?:body end } => { do-with-testing-context(method () ?body end, ?expression) } end macro; define function compiler-test-internal (name, test) => (result :: ) // Compile the template let lib-desc = dynamic-bind (*progress-stream* = #f, // with-compiler-muzzled *trace-compilation-passes* = #f) compile-template(test, compiler: compile-library-until-optimized) end; // Run the initializations; the last one is the test value. let init-method-value = #f; with-testing-context (lib-desc) for (cr in library-description-compilation-records(lib-desc)) for (form in compilation-record-top-level-forms(cr)) let init-method = form-init-method(form); when (init-method) init-method-value := eval(init-method); end end end end; init-method-value end; define macro compiler-test-definer // Compile forms, run initializations, last init method must return #t. { define compiler-test ?test-name:name = ?template:* } => { *tests*[?#"test-name"] := method () compiler-test-internal (?#"test-name", method () ?template end) end } end; define macro manual-compiler-test-definer // A pure code test: run something, get a boolean back. { define manual-compiler-test ?test-name:name = ?code:* } => { *tests*[?#"test-name"] := method () ?code end } end; // *** Will also need a native-compiler-test-definer. define function run-test (name, #key safely? = #t, progress? = *standard-output*) => (result) // If safely?: is given, then catch all errors & mark that test as a flamer. // Otherwise, let the signal happen so you can figure out why it's flaming. when (progress?) // Progress note, for ssslooowww tests format(progress?, "// Testing %s\n", as-lowercase(as(, name))) end; let test = *tests*[name]; // Get the test out of test registry let result = if (~instance?(test, )) // No such test, warn if permitted to talk. when (progress?) format(progress?, "Ignoring test %= = %=, because it's not a .", name, test) end; #"ignored test" elseif (safely?) // Don kludge-proof goggles & rubber gloves block () test() ~== #f exception (e :: ) e end // Accept the risk of signals for benefit of debugging information in them. else test() ~== #f end; select (result by instance?) singleton(#t) => add!(*test-successes*, name); // Test succeeds #"succeeds"; singleton(#f) => add!(*test-failures*, name); // Test fails #"fails"; => add!(*test-flamers*, name); // Test flames result; otherwise => result; end; end; define function run-tests (#key tests = key-sequence(*tests*), safely? = #t, progress? = *standard-output*, report? = *standard-output*, lift-rocks? = #f) => () // Run some tests, maybe printing a report. clear-tests(); // Throw out old results. for (test in tests) // Get new results. run-test(test, safely?: safely?, progress?: progress?) end; when (report?) // Report if requested to given stream. print-test-report(stream: report?, lift-rocks?: lift-rocks?) end; values() end; // eof