diff mbox series

[libgpiod] tests: rework error handling and stop overusing g_error()

Message ID 20230201162247.218716-1-brgl@bgdev.pl
State New
Headers show
Series [libgpiod] tests: rework error handling and stop overusing g_error() | expand

Commit Message

Bartosz Golaszewski Feb. 1, 2023, 4:22 p.m. UTC
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>

GLib recommends using g_error() only for programming errors and
unrecoverable situations. It calls abort() and triggers a core dump. This
means that if we encounter an error when running a test case, we'll
exit immediately and leave a "leaked" GPIO simulator in configfs.

Rework the error handling: use g_error() only when a programming bug is
encountered (e.g. invalid enum value) while everywhere else use the
regular GError-based error handling. This way, in case of an error in
libgpiosim, we'll simply fail the current test case and release all
resources as usual.

In order not to pollute the test cases with error handling, let's hide the
actual error checking behind macros for g_gpiosim_chip_new() and
g_gpiosim_chip_get_value(). For g_gpiosim_chip_set_pull() let's just emit
a log and do nothing as the test case in question will inevitably fail if
the expected value is not correctly read back.

In order for the chip to get a failing constructor, we need to link the
test-suite against the entire libgio, not only libgobject (for the
GInitable interface).

Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
 configure.ac           |   2 +-
 tests/Makefile.am      |   4 +-
 tests/gpiod-test-sim.c | 329 +++++++++++++++++++++++++++++------------
 tests/gpiod-test-sim.h |  42 +++++-
 tests/gpiod-test.c     |  33 +++--
 5 files changed, 298 insertions(+), 112 deletions(-)
diff mbox series

Patch

diff --git a/configure.ac b/configure.ac
index ab1a9fd..f16f29a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -153,7 +153,7 @@  then
 
 	# For core library tests
 	PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.50])
-	PKG_CHECK_MODULES([GOBJECT], [gobject-2.0 >= 2.50])
+	PKG_CHECK_MODULES([GIO], [gio-2.0 >= 2.50])
 
 	AC_CHECK_FUNC([asprintf], [], [FUNC_NOT_FOUND_LIB([asprintf])])
 	AC_CHECK_FUNC([prctl], [], [FUNC_NOT_FOUND_LIB([prctl])])
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0fdbe5b..9171a58 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -5,12 +5,12 @@  SUBDIRS = gpiosim
 
 AM_CFLAGS = -I$(top_srcdir)/include/ -I$(top_srcdir)/tests/gpiosim/
 AM_CFLAGS += -include $(top_builddir)/config.h
-AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS) $(GOBJECT_CFLAGS)
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS) $(GIO_CFLAGS)
 AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiod-test\"
 AM_CFLAGS += $(PROFILING_CFLAGS)
 LDADD = $(top_builddir)/lib/libgpiod.la
 LDADD += $(top_builddir)/tests/gpiosim/libgpiosim.la
-LDADD += $(GLIB_LIBS) $(GOBJECT_LIBS)
+LDADD += $(GLIB_LIBS) $(GIO_LIBS)
 
 bin_PROGRAMS = gpiod-test
 
diff --git a/tests/gpiod-test-sim.c b/tests/gpiod-test-sim.c
index b934a16..3939825 100644
--- a/tests/gpiod-test-sim.c
+++ b/tests/gpiod-test-sim.c
@@ -8,9 +8,16 @@ 
 
 #include "gpiod-test-sim.h"
 
+G_DEFINE_QUARK(g-gpiosim-error, g_gpiosim_error);
+
 struct _GPIOSimChip {
 	GObject parent_instance;
 	struct gpiosim_bank *bank;
+	GError *construct_err;
+	guint num_lines;
+	gchar *label;
+	GVariant *line_names;
+	GVariant *hogs;
 };
 
 enum {
@@ -24,35 +31,167 @@  enum {
 
 static struct gpiosim_ctx *sim_ctx;
 
-G_DEFINE_TYPE(GPIOSimChip, g_gpiosim_chip, G_TYPE_OBJECT);
+static gboolean
+g_gpiosim_chip_initable_init(GInitable *initable,
+			     GCancellable *cancellable G_GNUC_UNUSED,
+			     GError **err)
+{
+	GPIOSimChip *self = G_GPIOSIM_CHIP_OBJ(initable);
+
+	if (self->construct_err) {
+		g_propagate_error(err, self->construct_err);
+		self->construct_err = NULL;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void g_gpiosim_chip_initable_iface_init(GInitableIface *iface)
+{
+	iface->init = g_gpiosim_chip_initable_init;
+}
+
+G_DEFINE_TYPE_WITH_CODE(GPIOSimChip, g_gpiosim_chip, G_TYPE_OBJECT,
+			G_IMPLEMENT_INTERFACE(
+				G_TYPE_INITABLE,
+				g_gpiosim_chip_initable_iface_init));
 
 static void g_gpiosim_ctx_unref(void)
 {
 	gpiosim_ctx_unref(sim_ctx);
 }
 
-static void g_gpiosim_ctx_init(void)
+static gboolean g_gpiosim_chip_apply_line_names(GPIOSimChip *self)
 {
-	sim_ctx = gpiosim_ctx_new();
-	if (!sim_ctx)
-		g_error("Unable to initialize libgpiosim: %s",
-			g_strerror(errno));
+	g_autoptr(GVariantIter) iter = NULL;
+	guint offset;
+	gchar *name;
+	int ret;
 
-	atexit(g_gpiosim_ctx_unref);
+	if (!self->line_names)
+		return TRUE;
+
+	iter = g_variant_iter_new(self->line_names);
+
+	while (g_variant_iter_loop(iter, "(us)", &offset, &name)) {
+		ret = gpiosim_bank_set_line_name(self->bank, offset, name);
+		if (ret) {
+			g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+				    G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+				    "Unable to set the name of the simulated GPIO line: %s",
+				    g_strerror(errno));
+			return FALSE;
+		}
+	}
+
+	return TRUE;
 }
 
-static void g_gpiosim_chip_constructed(GObject *obj)
+static gboolean g_gpiosim_chip_apply_hogs(GPIOSimChip *self)
+{
+	g_autoptr(GVariantIter) iter = NULL;
+	enum gpiosim_direction dir;
+	guint offset;
+	gchar *name;
+	gint vdir;
+	int ret;
+
+	if (!self->hogs)
+		return TRUE;
+
+	iter = g_variant_iter_new(self->hogs);
+
+	while (g_variant_iter_loop(iter, "(usi)", &offset, &name, &vdir)) {
+		switch (vdir) {
+		case G_GPIOSIM_DIRECTION_INPUT:
+			dir = GPIOSIM_DIRECTION_INPUT;
+			break;
+		case G_GPIOSIM_DIRECTION_OUTPUT_HIGH:
+			dir = GPIOSIM_DIRECTION_OUTPUT_HIGH;
+			break;
+		case G_GPIOSIM_DIRECTION_OUTPUT_LOW:
+			dir = GPIOSIM_DIRECTION_OUTPUT_LOW;
+			break;
+		default:
+			g_error("Invalid hog direction value: %d", vdir);
+		}
+
+		ret = gpiosim_bank_hog_line(self->bank, offset, name, dir);
+		if (ret) {
+			g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+				    G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+				    "Unable to hog the simulated GPIO line: %s",
+				    g_strerror(errno));
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean g_gpiosim_chip_apply_properties(GPIOSimChip *self)
+{
+	int ret;
+
+	ret = gpiosim_bank_set_num_lines(self->bank, self->num_lines);
+	if (ret) {
+		g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+			    G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+			    "Unable to set the number of lines exposed by the simulated chip: %s",
+			    g_strerror(errno));
+		return FALSE;
+	}
+
+	if (self->label) {
+		ret = gpiosim_bank_set_label(self->bank, self->label);
+		if (ret) {
+			g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+				    G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+				    "Unable to set the label of the simulated chip: %s",
+				    g_strerror(errno));
+			return FALSE;
+		}
+	}
+
+	ret = g_gpiosim_chip_apply_line_names(self);
+	if (!ret)
+		return FALSE;
+
+	return g_gpiosim_chip_apply_hogs(self);
+}
+
+static gboolean g_gpiosim_chip_enable(GPIOSimChip *self)
 {
-	GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
 	struct gpiosim_dev *dev;
-	gint ret;
+	int ret;
 
 	dev = gpiosim_bank_get_dev(self->bank);
 	ret = gpiosim_dev_enable(dev);
 	gpiosim_dev_unref(dev);
-	if (ret)
-		g_error("Error while trying to enable the simulated GPIO device: %s",
-			g_strerror(errno));
+	if (ret) {
+		g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+			    G_GPIOSIM_ERR_CHIP_ENABLE_FAILED,
+			    "Error while trying to enable the simulated GPIO device: %s",
+			    g_strerror(errno));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void g_gpiosim_chip_constructed(GObject *obj)
+{
+	GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+	gboolean ret;
+
+	ret = g_gpiosim_chip_apply_properties(self);
+	if (!ret)
+		return;
+
+	ret = g_gpiosim_chip_enable(self);
+	if (!ret)
+		return;
 
 	G_OBJECT_CLASS(g_gpiosim_chip_parent_class)->constructed(obj);
 }
@@ -77,80 +216,36 @@  static void g_gpiosim_chip_get_property(GObject *obj, guint prop_id,
 	}
 }
 
+static void set_variant_prop(GVariant **prop, const GValue *val)
+{
+	GVariant *variant = g_value_get_variant(val);
+
+	g_clear_pointer(prop, g_variant_unref);
+	if (variant)
+		*prop = g_variant_ref(variant);
+}
+
 static void g_gpiosim_chip_set_property(GObject *obj, guint prop_id,
 					const GValue *val, GParamSpec *pspec)
 {
 	GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
-	gint ret, vdir, dir;
-	GVariantIter *iter;
-	GVariant *variant;
-	guint offset;
-	gchar *name;
+	const gchar *label;
 
 	switch (prop_id) {
 	case G_GPIOSIM_CHIP_PROP_NUM_LINES:
-		ret = gpiosim_bank_set_num_lines(self->bank,
-						 g_value_get_uint(val));
-		if (ret)
-			g_error("Unable to set the number of lines exposed by the simulated chip: %s",
-				g_strerror(errno));
+		self->num_lines = g_value_get_uint(val);
 		break;
 	case G_GPIOSIM_CHIP_PROP_LABEL:
-		ret = gpiosim_bank_set_label(self->bank,
-					     g_value_get_string(val));
-		if (ret)
-			g_error("Unable to set the label of the simulated chip: %s",
-				g_strerror(errno));
+		g_clear_pointer(&self->label, g_free);
+		label = g_value_get_string(val);
+		if (label)
+			self->label = g_strdup(label);
 		break;
 	case G_GPIOSIM_CHIP_PROP_LINE_NAMES:
-		variant = g_value_get_variant(val);
-		if (!variant)
-			break;
-
-		iter = g_variant_iter_new(variant);
-
-		while (g_variant_iter_loop(iter, "(us)", &offset, &name)) {
-			ret = gpiosim_bank_set_line_name(self->bank,
-							 offset, name);
-			if (ret)
-				g_error("Unable to set the name of the simulated GPIO line: %s",
-					g_strerror(errno));
-		}
-
-		g_variant_iter_free(iter);
+		set_variant_prop(&self->line_names, val);
 		break;
 	case G_GPIOSIM_CHIP_PROP_HOGS:
-		variant = g_value_get_variant(val);
-		if (!variant)
-			break;
-
-		iter = g_variant_iter_new(variant);
-
-		while (g_variant_iter_loop(iter, "(usi)",
-					   &offset, &name, &vdir)) {
-			switch (vdir) {
-			case G_GPIOSIM_DIRECTION_INPUT:
-				dir = GPIOSIM_DIRECTION_INPUT;
-				break;
-			case G_GPIOSIM_DIRECTION_OUTPUT_HIGH:
-				dir = GPIOSIM_DIRECTION_OUTPUT_HIGH;
-				break;
-			case G_GPIOSIM_DIRECTION_OUTPUT_LOW:
-				dir = GPIOSIM_DIRECTION_OUTPUT_LOW;
-				break;
-			default:
-				g_error("Invalid hog direction value: %d",
-					vdir);
-			}
-
-			ret = gpiosim_bank_hog_line(self->bank,
-						    offset, name, dir);
-			if (ret)
-				g_error("Unable to hog the simulated GPIO line: %s",
-					g_strerror(errno));
-		}
-
-		g_variant_iter_free(iter);
+		set_variant_prop(&self->hogs, val);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
@@ -158,19 +253,32 @@  static void g_gpiosim_chip_set_property(GObject *obj, guint prop_id,
 	}
 }
 
+static void g_gpiosim_chip_dispose(GObject *obj)
+{
+	GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+
+	g_clear_pointer(&self->line_names, g_variant_unref);
+	g_clear_pointer(&self->hogs, g_variant_unref);
+
+	G_OBJECT_CLASS(g_gpiosim_chip_parent_class)->dispose(obj);
+}
+
 static void g_gpiosim_chip_finalize(GObject *obj)
 {
 	GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
 	struct gpiosim_dev *dev;
 	gint ret;
 
+	g_clear_error(&self->construct_err);
+	g_clear_pointer(&self->label, g_free);
+
 	dev = gpiosim_bank_get_dev(self->bank);
 
 	if (gpiosim_dev_is_live(dev)) {
 		ret = gpiosim_dev_disable(dev);
 		if (ret)
-			g_error("Error while trying to disable the simulated GPIO device: %s",
-				g_strerror(errno));
+			g_warning("Error while trying to disable the simulated GPIO device: %s",
+				  g_strerror(errno));
 	}
 
 	gpiosim_dev_unref(dev);
@@ -186,6 +294,7 @@  static void g_gpiosim_chip_class_init(GPIOSimChipClass *chip_class)
 	class->constructed = g_gpiosim_chip_constructed;
 	class->get_property = g_gpiosim_chip_get_property;
 	class->set_property = g_gpiosim_chip_set_property;
+	class->dispose = g_gpiosim_chip_dispose;
 	class->finalize = g_gpiosim_chip_finalize;
 
 	g_object_class_install_property(
@@ -231,24 +340,57 @@  static void g_gpiosim_chip_class_init(GPIOSimChipClass *chip_class)
 			G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 }
 
+static gboolean g_gpiosim_ctx_init(GError **err)
+{
+	sim_ctx = gpiosim_ctx_new();
+	if (!sim_ctx) {
+		g_set_error(err, G_GPIOSIM_ERROR,
+			    G_GPIOSIM_ERR_CTX_INIT_FAILED,
+			    "Unable to initialize libgpiosim: %s",
+			    g_strerror(errno));
+		return FALSE;
+	}
+
+	atexit(g_gpiosim_ctx_unref);
+
+	return TRUE;
+}
+
 static void g_gpiosim_chip_init(GPIOSimChip *self)
 {
-	g_autofree gchar *dev_name = NULL;
 	struct gpiosim_dev *dev;
-
-	if (!sim_ctx)
-		g_gpiosim_ctx_init();
+	int ret;
+
+	self->construct_err = NULL;
+	self->num_lines = 1;
+	self->label = NULL;
+	self->line_names = NULL;
+	self->hogs = NULL;
+
+	if (!sim_ctx) {
+		ret = g_gpiosim_ctx_init(&self->construct_err);
+		if (!ret)
+			return;
+	}
 
 	dev = gpiosim_dev_new(sim_ctx);
-	if (!dev)
-		g_error("Unable to instantiate new GPIO device: %s",
-			g_strerror(errno));
+	if (!dev) {
+		g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+			    G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+			    "Unable to instantiate new GPIO device: %s",
+			    g_strerror(errno));
+		return;
+	}
 
 	self->bank = gpiosim_bank_new(dev);
 	gpiosim_dev_unref(dev);
-	if (!self->bank)
-		g_error("Unable to instantiate new GPIO bank: %s",
-			g_strerror(errno));
+	if (!self->bank) {
+		g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+			    G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+			    "Unable to instantiate new GPIO bank: %s",
+			    g_strerror(errno));
+		return;
+	}
 }
 
 static const gchar *
@@ -274,13 +416,18 @@  const gchar *g_gpiosim_chip_get_name(GPIOSimChip *self)
 	return g_gpiosim_chip_get_string_prop(self, "name");
 }
 
-gint g_gpiosim_chip_get_value(GPIOSimChip *chip, guint offset)
+gint _g_gpiosim_chip_get_value(GPIOSimChip *chip, guint offset, GError **err)
 {
 	gint val;
 
 	val = gpiosim_bank_get_value(chip->bank, offset);
-	if (val < 0)
-		g_error("Unable to read the line value: %s", g_strerror(errno));
+	if (val < 0) {
+		g_set_error(err, G_GPIOSIM_ERROR,
+			    G_GPIOSIM_ERR_GET_VALUE_FAILED,
+			    "Unable to read the line value: %s",
+			    g_strerror(errno));
+		return -1;
+	}
 
 	return val;
 }
@@ -302,6 +449,6 @@  void g_gpiosim_chip_set_pull(GPIOSimChip *chip, guint offset, GPIOSimPull pull)
 
 	ret = gpiosim_bank_set_pull(chip->bank, offset, sim_pull);
 	if (ret)
-		g_error("Unable to set the pull setting for simulated line: %s",
-			g_strerror(errno));
+		g_critical("Unable to set the pull setting for simulated line: %s",
+			    g_strerror(errno));
 }
diff --git a/tests/gpiod-test-sim.h b/tests/gpiod-test-sim.h
index db864e6..1293ea7 100644
--- a/tests/gpiod-test-sim.h
+++ b/tests/gpiod-test-sim.h
@@ -4,6 +4,7 @@ 
 #ifndef __GPIOD_TEST_SIM_H__
 #define __GPIOD_TEST_SIM_H__
 
+#include <gio/gio.h>
 #include <glib.h>
 #include <glib-object.h>
 
@@ -20,21 +21,52 @@  typedef enum {
 	G_GPIOSIM_DIRECTION_OUTPUT_LOW,
 } GPIOSimDirection;
 
+#define G_GPIOSIM_ERROR g_gpiosim_error_quark()
+
+typedef enum {
+	G_GPIOSIM_ERR_CTX_INIT_FAILED = 1,
+	G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+	G_GPIOSIM_ERR_CHIP_ENABLE_FAILED,
+	G_GPIOSIM_ERR_GET_VALUE_FAILED,
+} GPIOSimError;
+
+GQuark g_gpiosim_error_quark(void);
+
 G_DECLARE_FINAL_TYPE(GPIOSimChip, g_gpiosim_chip, G_GPIOSIM, CHIP, GObject);
 
-#define G_GPIOSIM_TYPE_CHIP (g_gpiosim_chip_get_type())
-#define G_GPIOSIM_CHIP(obj) \
-	(G_TYPE_CHECK_INSTANCE_CAST((obj), G_GPIOSIM_TYPE_CHIP, GPIOSimChip))
+#define G_GPIOSIM_CHIP_TYPE (g_gpiosim_chip_get_type())
+#define G_GPIOSIM_CHIP_OBJ(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj), G_GPIOSIM_CHIP_TYPE, GPIOSimChip))
 
 #define g_gpiosim_chip_new(...) \
-	G_GPIOSIM_CHIP(g_object_new(G_GPIOSIM_TYPE_CHIP, __VA_ARGS__))
+	({ \
+		g_autoptr(GError) _err = NULL; \
+		GPIOSimChip *_chip = G_GPIOSIM_CHIP_OBJ( \
+					g_initable_new(G_GPIOSIM_CHIP_TYPE, \
+						       NULL, &_err, \
+						       __VA_ARGS__)); \
+		g_assert_no_error(_err); \
+		if (g_test_failed()) \
+			return; \
+		_chip; \
+	})
 
 const gchar *g_gpiosim_chip_get_dev_path(GPIOSimChip *self);
 const gchar *g_gpiosim_chip_get_name(GPIOSimChip *self);
 
-gint g_gpiosim_chip_get_value(GPIOSimChip *self, guint offset);
+gint _g_gpiosim_chip_get_value(GPIOSimChip *self, guint offset, GError **err);
 void g_gpiosim_chip_set_pull(GPIOSimChip *self, guint offset, GPIOSimPull pull);
 
+#define g_gpiosim_chip_get_value(self, offset) \
+	({ \
+		g_autoptr(GError) _err = NULL; \
+		gint _val = _g_gpiosim_chip_get_value(self, offset, &_err); \
+		g_assert_no_error(_err); \
+		if (g_test_failed()) \
+			return; \
+		_val; \
+	})
+
 G_END_DECLS
 
 #endif /* __GPIOD_TEST_SIM_H__ */
diff --git a/tests/gpiod-test.c b/tests/gpiod-test.c
index 39a1f40..05ec24a 100644
--- a/tests/gpiod-test.c
+++ b/tests/gpiod-test.c
@@ -18,7 +18,7 @@ 
 
 static GList *tests;
 
-static void check_kernel(void)
+static gboolean check_kernel(void)
 {
 	guint major, minor, release;
 	struct utsname un;
@@ -27,23 +27,29 @@  static void check_kernel(void)
 	g_debug("checking linux kernel version");
 
 	ret = uname(&un);
-	if (ret)
-		g_error("unable to read the kernel release version: %s",
-			g_strerror(errno));
+	if (ret) {
+		g_critical("unable to read the kernel release version: %s",
+			   g_strerror(errno));
+		return FALSE;
+	}
 
 	ret = sscanf(un.release, "%u.%u.%u", &major, &minor, &release);
-	if (ret != 3)
-		g_error("error reading kernel release version");
-
-	if (KERNEL_VERSION(major, minor, release) < MIN_KERNEL_VERSION)
-		g_error("linux kernel version must be at least v%u.%u.%u - got v%u.%u.%u",
-			MIN_KERNEL_MAJOR, MIN_KERNEL_MINOR, MIN_KERNEL_RELEASE,
-			major, minor, release);
+	if (ret != 3) {
+		g_critical("error reading kernel release version");
+		return FALSE;
+	}
+
+	if (KERNEL_VERSION(major, minor, release) < MIN_KERNEL_VERSION) {
+		g_critical("linux kernel version must be at least v%u.%u.%u - got v%u.%u.%u",
+			   MIN_KERNEL_MAJOR, MIN_KERNEL_MINOR, MIN_KERNEL_RELEASE,
+			   major, minor, release);
+		return FALSE;
+	}
 
 	g_debug("kernel release is v%u.%u.%u - ok to run tests",
 		major, minor, release);
 
-	return;
+	return TRUE;
 }
 
 static void test_func_wrapper(gconstpointer data)
@@ -68,7 +74,8 @@  int main(gint argc, gchar **argv)
 	g_debug("running libgpiod test suite");
 	g_debug("%u tests registered", g_list_length(tests));
 
-	check_kernel();
+	if (!check_kernel())
+		return 1;
 
 	g_list_foreach(tests, add_test_from_list, NULL);
 	g_list_free(tests);