Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 65 additions & 3 deletions testing.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(_,_) ->
Expand All @@ -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)
).
Comment on lines +235 to +246
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you use setup_call_cleanup/3 instead?


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".
Expand Down