# Plunit and individual test setup/cleanup

**URL:** <https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853>\
**Category:** Tools\
**Tags:** how-to\
**Created:** [January 7, 2022, 11:38pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853 "2022-01-07T23:38:29Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [January 7, 2022, 11:38pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/1 "2022-01-07T23:38:29Z")

</div>

Is there any way to tell plunit to run a goal before every test or after every test? This is useful for tests that implicitly affect global state, for example when testing predicates that cache results (library(pcre) is one example, where some predicates cache intermediate results using assertz/1).

There’s a `setup(Goal)` option in begin\_tests/2, but it runs before any of the tests. (Similarly, begin\_tests/2 option `cleanup(Goal)` runs after all the tests.)  
I want a way of specifying a goal to be run for each test, and not have to specify `setup(Goal)` for each test. (Some other language’s unit test frameworks have both global and per-test setup/cleanuyp; SWI-Prolog’s plunit only has global setup/cleanup.)

Do the tests run sequentially or can they run in parallel? I presume that they run sequentially, but this isn’t documented AFAICT. If I want to test that things work properly in a multi-threaded environment (e.g., that caching is done on a per-thread basis or globally), is there any test infrastructure to help this kind of test? I noticed that the code for library(plunit) has `set_test_options([concurrent(true])` but this doesn’t appear in the documentation for set\_test\_options/1; also, it’s not clear how to specify these options – do I simply add `:- set_test_options([concurrent(true]).` to the file?

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [January 8, 2022, 1:33pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/2 "2022-01-08T13:33:36Z")

</div>

Individual tests have the same setup and cleanup options (which may share  
variables with the test). For repetitive similar tests this may get a bit of verbose. Next to the forall construct in the test head you can term expand tests. Something like this:

```prolog
term_expansion(re(...), (test(...) :- Body)) :-
    ...

re(...).

```

That makes your tests concise and creates nice independent tests that correctly report line numbers if one fails.

> [@peter.ludemann](#):
>
> Do the tests run sequentially or can they run in parallel?

Tests are run sequentially. The overall SWI-Prolog test suite defines them in over 70 bundles that are executed in concurrent Prolog instances if you run `ctest -j tasks`. You can tweak a bit with the numbers to get the shortest total time depending on your hardware. On my desktop I use `ctest -j 16` run the whole thing in 4.2 seconds.

The concurrent option was experimental. I don’t really recall the status. Not all system tests are designed to run concurrent, for example because they test whether all threads have died or whether atom garbage collection has been executed. Another problem is that output needs to be considered carefully not to make a mess.

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [January 8, 2022, 3:48pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/3 "2022-01-08T15:48:06Z")

</div>

I suppose I could do something like this (untested):

```prolog
term_expansion((re_test(Name) :- Body),
                  (test(Name, []) :- Body)).
term_expansion((re_test(Name, Options) :- Body), 
                  (test(Name, Options2) :- Body)) :-
    expand_options(Options, Options2).
expand_options([], Options2) =>
    Options2 = [setup(my_setup)].
expand_options([O1|O2], Options2) =>
    Options2 = [setup(my_setup),O1|O2].
expand_options(Option, Options2) => % e.g.: re_test(t123, X==[1,2,3]) :- pred(123, X).
    Options2 = [setup(my_setup),Option].

```

Have I forgotten a pattern? TBH, it’s easier to just add the `my_setup` goal when I write each test, or add them retroactively using a simple Emacs keyboard macro.

But it would be nicer if this were done for me as part of `plunit:expand_test/4`, by just writing something like `begin_tests(my_tests, test_setup(my_setup)])` .

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [January 8, 2022, 4:07pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/4 "2022-01-08T16:07:23Z")

</div>

Term expansion is not applied recursively, so the first clause will not work.

> [@peter.ludemann](#):
>
> `begin_tests(my_tests, test_setup(my_setup)])` .

You can do that, but it means something different. It runs the setup and cleanup once for the entire block of tests, not for each individual test.

Just term expanding to get a setup handler is probably not worthwhile. The term expansion trick is mostly useful if you have a lot of tests that are closely related such that you can write them in some dedicated concise syntax.

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [January 8, 2022, 4:25pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/5 "2022-01-08T16:25:45Z")

</div>

> [@jan](#):
>
> > [@peter.ludemann](#):
> >
> > `begin_tests(my_tests, test_setup(my_setup)])` .
> 
> You can do that, but it means something different. It runs the setup and cleanup once for the entire block of tests, not for each individual test.

`begin_tests(my_tests,setup(my_setup)])` behaves the way you describe. I was suggesting new option `test_setup` to begin\_tests/2.

FWIW, other test frameworks I’ve used (in other languages) allow specifying both global setup/cleanup (done once for a set of tests) and every-test setup/cleanup – which is why I’m suggesting adding this to plunit.

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [January 8, 2022, 4:32pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/6 "2022-01-08T16:32:44Z")

</div>

BTW, the term\_expansion I gave isn’t quite right – it needs to look for any `setup` option and combine with its goal. (It appears that specifying `setup` multiple times results in only the first one being used and the others are ignored.)

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [January 8, 2022, 4:52pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/7 "2022-01-08T16:52:28Z")

</div>

> [@jan](#):
>
> Term expansion is not applied recursively, so the first clause will not work.

Thanks for pointing that out – which shows how tricky it can be to get even simple term expansion right. (I got confused with goal\_expansion, which does apply recursively to a fix-point)

---

<div class="post-metadata">

**Author:** ![EricGT](https://avatars.discourse-cdn.com/v4/letter/e/f1d935/32.png) [@EricGT](https://swi-prolog.discourse.group/u/EricGT)\
**Post date:** [January 8, 2022, 5:14pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/8 "2022-01-08T17:14:27Z")

</div>

> [@peter.ludemann](#):
>
> FWIW, other test frameworks I’ve used (in other languages) allow specifying both global setup/cleanup (done once for a set of tests) and every-test setup/cleanup – which is why I’m suggesting adding this to plunit.

Have you tried using a [wrapper](https://www.swi-prolog.org/pldoc/doc/_SWI_/library/prolog_wrap.pl)?

I remember originally [noting](https://swi-prolog.discourse.group/t/wrapping-predicates/668/21) this with wrappers but IIRC a Prolog wrapper can not pass stateful information from the `before` predicate to the `after` predicate. Never checked if that was allowed or not.

* * *

I also have to agree that this is often the case

> other test frameworks allow specifying both global setup/cleanup and every-test setup/cleanup

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [January 8, 2022, 5:47pm UTC](https://swi-prolog.discourse.group/t/plunit-and-individual-test-setup-cleanup/4853/9 "2022-01-08T17:47:48Z")

</div>

> [@EricGT](#):
>
> Have you tried using a [wrapper](https://www.swi-prolog.org/pldoc/doc/_SWI_/library/prolog_wrap.pl)?

Using a wrapper would work but requires knowing the details of how `test/2` is expanded and could break if details of plunit change in the future.

As it happens, plunit term expansion produces two clauses: `'unit test'/4` and `‘unit body’/2, which are “interpreted” by the plunit framework.  
E.g.:

```prolog
test(match1, [Sub == re_match{0:"aap"}]) :-
    re_compile("a+p", Re, []),
    re_matchsub(Re, "aapenootjes", Sub, []).

```

expands to this (inside `:-begin_tests(pcre)`):

```prolog
plunit_pcre:'unit test'(match1,
                        76,
                        [true(A==re_match{0:"aap"})],
                        plunit_pcre:'unit body'('match1@line 76', vars(A))).

plunit_pcre:'unit body'('match1@line 76', vars(A)) :-
    !,
    re_compile("a+p", B, []),
    re_matchsub(B, "aapenootjes", A, []).

```
