Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for deferring cleanup operations #40

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ examples/test-root-user.exe

# Release archives
libzt_*.tar.gz
libzt_*.tar.gz.asc
libzt_*.zip

# The test program
Expand Down Expand Up @@ -53,9 +54,14 @@ libzt-test.exe
pvs-report/

# Configured in-tree build
configure
GNUmakefile.configure.mk
.version-from-git

# Build system snapshot
z.mk
zmk

# Manual pages
man/*.[123456789]

Expand Down
20 changes: 13 additions & 7 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,30 @@ $(eval $(call ZMK.Import,GitVersion))

# Manual pages
manpages = \
libzt-test.1 \
libzt.3 \
zt_check.3 \
zt_claim.3 \
ZT_CMP_BOOL.3 \
ZT_CMP_INT.3 \
ZT_CMP_PTR.3 \
ZT_CMP_RUNE.3 \
ZT_CMP_UINT.3 \
ZT_CURRENT_LOCATION.3 \
ZT_FALSE.3 \
ZT_NOT_NULL.3 \
ZT_NULL.3 \
ZT_TRUE.3 \
libzt-test.1 \
libzt.3 \
zt_check.3 \
zt_claim.3 \
zt_closure.3 \
zt_closure_func0.3 \
zt_closure_func1.3 \
zt_defer.3 \
zt_location.3 \
zt_location_at.3 \
zt_main.3 \
ZT_NOT_NULL.3 \
ZT_NULL.3 \
zt_pack_boolean.3 \
zt_pack_closure0.3 \
zt_pack_closure1.3 \
zt_pack_integer.3 \
zt_pack_nothing.3 \
zt_pack_pointer.3 \
Expand All @@ -60,7 +67,6 @@ manpages = \
zt_test.3 \
zt_test_case_func.3 \
zt_test_suite_func.3 \
ZT_TRUE.3 \
zt_value.3 \
zt_visit_test_case.3 \
zt_visitor.3
Expand Down
9 changes: 9 additions & 0 deletions HACKING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Tips

# Checking test coverage

On Linux, configure the build using clang, and use the `make coverage-todo` target to see missing test coverage.

```
./configure CC=clang CXX=clang++ CFLAGS=-Wall
```
17 changes: 17 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
Changes in 0.4 (unreleased):

* Test suites can now offer resources, through the zt_visit_resource()
function. Resources are remembered by the library and used in specific
circumstances. This mode decouples allocation of resources such as memory
or threads from the internals of the library and puts ultimate control in
the hand of the test author.

* Test functions can now defer cleanup operations through the zt_defer()
function and the family of supporting types, functions and macros.
Deferred functions execute when the test function returns, both
successfully and abnormally, through non-local exit.

To use deferred cleanup, the test suite that either directly or indirectly
contains a test function must offer the resource ZT_RESOURCE_DEFER_CLOSURES,
which represent closures that can be prepared and called at a later moment.

Changes in 0.3.1:

* The build system has been updated to ZMK 0.3.6. This should fix
Expand Down
2 changes: 2 additions & 0 deletions libzt.def
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ EXPORTS
zt_cmp_ptr
zt_cmp_rune
zt_cmp_uint
zt_defer
zt_false
zt_main
zt_not_null
zt_null
zt_pack_rune
zt_true
zt_visit_resource
zt_visit_test_case
zt_visit_test_suite
2 changes: 2 additions & 0 deletions libzt.export_list
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ _zt_cmp_int
_zt_cmp_ptr
_zt_cmp_rune
_zt_cmp_uint
_zt_defer
_zt_false
_zt_main
_zt_not_null
_zt_null
_zt_pack_rune
_zt_true
_zt_visit_resource
_zt_visit_test_case
_zt_visit_test_suite
2 changes: 2 additions & 0 deletions libzt.map
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ VERS_0_2 {
VERS_0_3 {
global:
zt_cmp_ptr;
zt_visit_resource;
zt_defer;
} VERS_0_2;
2 changes: 1 addition & 1 deletion man/ZT_TRUE.3.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#define ZT_TRUE(value) \\
zt_true( \\
ZT_CURRENT_LOCATION(), \\
zt_pack_boolean((value), #value)) \\
zt_pack_boolean((value), #value))
.Ed
.Ft zt_claim
.Fo zt_true
Expand Down
12 changes: 9 additions & 3 deletions man/zt_check.3.in
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,16 @@ test on failure.
Non-local exit from
.Fn zt_assert
is implemented using
.Fn siglongjump .
One should not depend on neither C++ destructors nor the GCC cleanup function extension for correct resource management.
.Fn siglongjump
or
.Fn longjump ,
depending on platform support. One should not depend on neither C++
destructors nor the GCC cleanup function extension for correct resource
management. Instead, please use
.Fn zt_defer
to arrange a function to be called after the test function returns.
.Sh RETURN VALUES
Neither function return anything. Failures are remembered inside the opaque
Neither function returns anything. Failures are remembered inside the opaque
.Nm zt_test
structure passed by pointer (aka
.Nm zt_t )
Expand Down
57 changes: 57 additions & 0 deletions man/zt_closure.3.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
.Dd January 14, 2020
.Os libzt @VERSION@
.Dt zt_closure 3 PRM
.Sh NAME
.Nm zt_closure
.Nd structure representing a function closure
.Sh SYNOPSIS
.In zt.h
.Bd -literal
typedef struct zt_closure {
union {
zt_closure_func0 args0;
zt_closure_func1 args1;
} func;
zt_location location;
int nargs;
zt_value args[1];
} zt_closure;
.Be
.Sh DESCRIPTION
The structure
.Nm
contains a function pointer and at most one argument represented as a
.Nm zt_value .
.Pp
Closures are used by
.Fn zt_defer
to facilitate reliable management of memory and other resources inside test
code. It can be also used to setup and restore mocking of application
behaviors. Function arguments are represented by
.Nm zt_value
for convenience, to avoid casting integers and pointers. Test code is
expected to provide wrapper functions that take a
.Nm zt_value
argument and pass the call to another function such as
.Fn free
or
.Fn close .
.Sh IMPLEMENTATION NOTES
The
.Nm location
field describes location in the source code where the closure was packed. It
is used in some diagnostic error messages.
.Sh SEE ALSO
.Xr zt_closure_func0 3 ,
.Xr zt_closure_func1 3 ,
.Xr zt_defer 3 ,
.Xr zt_location 3 ,
.Xr zt_pack_closure0 3 ,
.Xr zt_pack_closure1 3 ,
.Xr zt_value 3
.Sh HISTORY
The structure
.Nm
first appeared in libzt 0.4
.Sh AUTHORS
.An "Zygmunt Krynicki" Aq Mt me@zygoon.pl
24 changes: 24 additions & 0 deletions man/zt_closure_func0.3.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.Dd January 14, 2020
.Os libzt @VERSION@
.Dt zt_closure_func0 3 PRM
.Sh NAME
.Nm zt_closure_func0
.Nd function type for closures without arguments
.Sh SYNOPSIS
.In zt.h
.Ft typedef void
.Fo (*zt_closure_func0)
.Fa "void"
.Fc
.Sh DESCRIPTION
.Nm
is a function type that takes no arguments. It is used by
.Nm zt_closure .
.Sh SEE ALSO
.Xr zt_closure 3
.Sh HISTORY
The type
.Nm
first appeared in libzt 0.4
.Sh AUTHORS
.An "Zygmunt Krynicki" Aq Mt me@zygoon.pl
25 changes: 25 additions & 0 deletions man/zt_closure_func1.3.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.Dd January 14, 2020
.Os libzt @VERSION@
.Dt zt_closure_func1 3 PRM
.Sh NAME
.Nm zt_closure_func1
.Nd function type for closures with one argument
.Sh SYNOPSIS
.In zt.h
.Ft typedef void
.Fo (*zt_closure_func1)
.Fa "zt_value arg1"
.Fc
.Sh DESCRIPTION
.Nm
is a function type that takes one argument. It is used by
.Nm zt_closure .
.Sh SEE ALSO
.Xr zt_closure 3 ,
.Xr zt_value 3
.Sh HISTORY
The type
.Nm
first appeared in libzt 0.4
.Sh AUTHORS
.An "Zygmunt Krynicki" Aq Mt me@zygoon.pl
91 changes: 91 additions & 0 deletions man/zt_defer.3.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
.Dd January 14, 2020
.Os libzt @VERSION@
.Dt zt_defer 3 PRM
.Sh NAME
.Nm zt_defer,
.Nd call a function after the current test terminates
.Sh SYNOPSIS
.In zt.h
.Ft void
.Fo zt_defer
.Fa "zt_t t"
.Fa "zt_closure closure"
.Fc
.Sh DESCRIPTION
The function
.Fn
arranges for the function encapsulated by
.Em closure
to be called at after the current test function returns.
This mechanism works even in the case of non-local exit
used by
.Fn zt_assert .
It is designed to allow test authors to reclaim allocated memory, close
files, restore the state of mocked state or other tasks that are required
to ensure program consistency.
.Pp
In the spirit of robustness, the burden of providing memory for deferred
function calls is shifted to the test author. Test authors must provide a
.Em test resource
of the kind
.Em ZT_RESOURCE_DEFER_CLOSURES
with enough capacity to remember all of the deferred calls in all the tests.
This can be done by calling
.Fn zt_visit_resource
anywhere in a test suite function, before visiting the first test that relies
on defer.
.Pp
If insufficient amount of defer resources are available, the defer function
is called immediately, the current test is marked as failed and the test
terminates through a non-local exit. Note that succesfully deferred calls
are also executed regardless of the means of exiting from a test function.
.Sh IMPLEMENTATION NOTES
Defer is implemented using a stack of
.Nm zt_closure
structures. Before visiting a test function, the stack is emptied.
At the end of the test subsequent closures are popped and executed.
.Sh RETURN VALUES
.Nm
does not return any value.
.Sh EXAMPLES
The following example shows a minimal test program using resources and defer
to ensure that allocated memory is not leaked.
.Bd -literal -offset indent
#include <stdlib.h>
#include <zt.h>

static void free_value(zt_value val) {
free(val.as.pointer);
}

#define FREE(ptr) ZT_CLOSURE1(free_value, zt_pack_pointer((ptr), #ptr))

static void test_allocated_memory(zt_t t) {
void *m = malloc(100);
zt_assert(t, ZT_NOT_NULL(m));
zt_defer(t, FREE(m));
/* Do something with the allocated memory. */
}

static void test_suite(zt_visitor v) {
zt_closure closures[1];
zt_visit_resource(v, ZT_RESOURCE_DEFER_CLOSURES,
sizeof closures / sizeof *closures, closures);
ZT_VISIT_TEST_CASE(v, test_allocated_memory);
}

int main(int argc, char** argv, char** envp) {
return zt_main(argc, argv, envp, test_suite);
}
.Ed
.Sh SEE ALSO
.Xr ZT_CLOSURE0 3 ,
.Xr ZT_CLOSURE1 3 ,
.Xr zt_closure 3 ,
.Xr zt_visit_resource 3
.Sh HISTORY
The function
.Nm
first appeared in libzt 0.4
.Sh AUTHORS
.An "Zygmunt Krynicki" Aq Mt me@zygoon.pl
Loading