diff --git a/testing.pl b/testing.pl index 77e6233..380ea05 100644 --- a/testing.pl +++ b/testing.pl @@ -75,6 +75,39 @@ After running, `run_tests/1` exits with a status code of 0 if all tests that were run succeeded and 1 otherwise. This makes it possible to check if tests pass inside scripts. + +# Setup and Teardown + +The testing framework supports setup and teardown hooks that run before and after each test: + +- `test_setup/0`: Runs before each test. Use this to initialize test fixtures or state. +- `test_teardown/0`: Runs after each test, regardless of whether the test succeeds or fails. + +These predicates are optional. If they are not defined, the tests run normally without setup/teardown. + +Example: + +```prolog +:- use_module(testing). +:- use_module(my_module). + +:- dynamic(test_state/1). + +test_setup :- + retractall(test_state(_)), + assert(test_state(initialized)). + +test_teardown :- + retractall(test_state(_)). + +test("test with setup", ( + test_state(initialized), + example(1) +)). +``` + +The teardown hook is guaranteed to run even if the test fails or throws an exception, ensuring +proper cleanup of resources. */ % SPDX-License-Identifier: Unlicense @@ -171,7 +204,12 @@ run_tests_([], _, Success, Success). run_tests_([test(Name, Goal)|Tests], Color, Success0, Success) :- portray(format_(" test \"~s\" ... ", [Name])), - ( catch(call(Goal), Exception, true) -> + run_single_test_(Goal, Color, Success0, Success1), + portray("\n"), + run_tests_(Tests, Color, Success1, Success). + +run_single_test_(Goal, Color, Success0, Success1) :- + ( catch(call_with_setup_teardown_(Goal), Exception, true) -> ( nonvar(Exception) -> portray(ansi(Color, red)), ( Exception = error(_,_) -> @@ -185,9 +223,33 @@ ) ; portray((ansi(Color, red), "failed", ansi(Color, reset))), Success1 = false + ). + +call_with_setup_teardown_(Goal) :- + ( Goal = Module:_ -> + SetupGoal = Module:test_setup, + TeardownGoal = Module:test_teardown + ; SetupGoal = test_setup, + TeardownGoal = test_teardown ), - portray("\n"), - run_tests_(Tests, Color, Success1, Success). + catch( + ( + call_setup_(SetupGoal), + catch( + (call(Goal), call_teardown_(TeardownGoal)), + TestException, + (call_teardown_(TeardownGoal), throw(TestException)) + ) + ), + SetupException, + throw(SetupException) + ). + +call_setup_(SetupGoal) :- + catch(call(SetupGoal), error(existence_error(procedure, _), _), true). + +call_teardown_(TeardownGoal) :- + catch(call(TeardownGoal), error(existence_error(procedure, _), _), true). ansi(true, reset) --> "\x1b\[0m". ansi(true, red) --> "\x1b\[31;1m".