tests: add a simple unit testing framework and a couple tests
authorBartosz Golaszewski <bartekgola@gmail.com>
Thu, 9 Feb 2017 10:02:13 +0000 (11:02 +0100)
committerBartosz Golaszewski <bartekgola@gmail.com>
Fri, 17 Feb 2017 13:40:43 +0000 (14:40 +0100)
Implement a simple unit testing framework working together with the
gpio-mockup module present in the mainline linux kernel.

Signed-off-by: Bartosz Golaszewski <bartekgola@gmail.com>
.gitignore
Makefile.am
configure.ac
tests/Makefile.am [new file with mode: 0644]
tests/unit/Makefile.am [new file with mode: 0644]
tests/unit/gpiod-unit.c [new file with mode: 0644]
tests/unit/gpiod-unit.h [new file with mode: 0644]
tests/unit/tests-chip.c [new file with mode: 0644]

index 67e5cadf20eaf1864294c7fde53dec30b85cfdaa..043fe9bf5536faac68bb28c1334c8bae90b38c7e 100644 (file)
@@ -27,3 +27,6 @@ configure
 libtool
 m4/
 stamp-h1
+
+# unit tests
+gpiod-unit
index 56c8b2b17ad170b9a2d51c9ca3a6cc219d8dcd87..159eb3cb6e0dd6c3e2fd57b58fea109ccf19253c 100644 (file)
@@ -9,6 +9,12 @@
 AUTOMAKE_OPTIONS = foreign
 SUBDIRS = include src
 
+if WITH_TESTS
+
+SUBDIRS += tests
+
+endif
+
 if HAS_DOXYGEN
 
 doc:
index 2226513a7dae0c4d0a7eda5d9ce88cbe86667118..4572987fdc39f018a467ed737ed0fb40ed62e40f 100644 (file)
@@ -82,6 +82,34 @@ then
        AC_CHECK_HEADERS([sys/signalfd.h], [], [HEADER_NOT_FOUND_TOOLS([sys/signalfd.h])])
 fi
 
+AC_ARG_ENABLE([tests],
+       [AC_HELP_STRING([--enable-tests],
+               [enable libgpiod tests [default=no]])],
+       [
+               if test "x$enableval" = xyes
+               then
+                       with_tests=true
+               else
+                       with_tests=false
+               fi
+       ],
+       [with_tests=false])
+AM_CONDITIONAL([WITH_TESTS], [test "x$with_tests" = xtrue])
+
+AC_DEFUN([HEADER_NOT_FOUND_TESTS],
+       [ERR_NOT_FOUND([$1 header], [tests])])
+
+AC_DEFUN([FUNC_NOT_FOUND_TESTS],
+       [ERR_NOT_FOUND([$1()], [tests])])
+
+if test "x$with_tools" = xtrue
+then
+       AC_CHECK_LIB([kmod], [kmod_module_probe_insert_module], [],
+               [AC_MSG_ERROR([libkmod not found (needed to build tests])])
+       AC_CHECK_HEADERS([libkmod.h], [], [HEADER_NOT_FOUND_TESTS([libkmod.h])])
+       AC_CHECK_FUNC([qsort], [], [FUNC_NOT_FOUND_TESTS([qsort])])
+fi
+
 AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false])
 AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue])
 if test "x$has_doxygen" = xfalse
@@ -93,6 +121,8 @@ AC_CONFIG_FILES([Makefile
                 include/Makefile
                 src/Makefile
                 src/lib/Makefile
-                src/tools/Makefile])
+                src/tools/Makefile
+                tests/Makefile
+                tests/unit/Makefile])
 
 AC_OUTPUT
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644 (file)
index 0000000..fe02e4a
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# Copyright (C) 2017 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2.1 of the GNU Lesser General Public License
+# as published by the Free Software Foundation.
+#
+
+SUBDIRS = . unit
diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am
new file mode 100644 (file)
index 0000000..b92e101
--- /dev/null
@@ -0,0 +1,16 @@
+#
+# Copyright (C) 2017 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2.1 of the GNU Lesser General Public License
+# as published by the Free Software Foundation.
+#
+
+AM_CFLAGS = -I$(top_srcdir)/include/ -include $(top_srcdir)/config.h
+AM_CFLAGS += -Wall -Wextra -g
+LDADD = -lgpiod -L$(top_srcdir)/src/lib -lkmod
+DEPENDENCIES = libgpiod.la
+
+check_PROGRAMS = gpiod-unit
+
+gpiod_unit_SOURCES = gpiod-unit.c tests-chip.c
diff --git a/tests/unit/gpiod-unit.c b/tests/unit/gpiod-unit.c
new file mode 100644 (file)
index 0000000..a5b77bb
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+ * Unit testing framework for libgpiod.
+ *
+ * Copyright (C) 2017 Bartosz Golaszewski <bartekgola@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include "gpiod-unit.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <time.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <libkmod.h>
+
+#define NORETURN __attribute__((noreturn))
+
+struct mockup_chip {
+       char *path;
+       char *name;
+       unsigned int number;
+};
+
+struct test_context {
+       struct mockup_chip *chips;
+       size_t num_chips;
+       bool test_failed;
+       struct timeval mod_loaded_ts;
+};
+
+static struct {
+       struct gu_test *test_list_head;
+       struct gu_test *test_list_tail;
+       unsigned int num_tests;
+       unsigned int tests_failed;
+       struct kmod_ctx *module_ctx;
+       struct kmod_module *module;
+       struct test_context test_ctx;
+} globals;
+
+static void vmsg(FILE *stream, const char *hdr, const char *fmt, va_list va)
+{
+       fprintf(stream, "[%.5s] ", hdr);
+       vfprintf(stream, fmt, va);
+       fputc('\n', stream);
+}
+
+void gu_msg(const char *fmt, ...)
+{
+       va_list va;
+
+       va_start(va, fmt);
+       vmsg(stderr, " INFO", fmt, va);
+       va_end(va);
+}
+
+void gu_err(const char *fmt, ...)
+{
+       va_list va;
+
+       va_start(va, fmt);
+       vmsg(stderr, "ERROR", fmt, va);
+       va_end(va);
+}
+
+static void GU_PRINTF(1, 2) NORETURN die(const char *fmt, ...)
+{
+       va_list va;
+
+       va_start(va, fmt);
+       vmsg(stderr, "FATAL", fmt, va);
+       va_end(va);
+
+       exit(EXIT_FAILURE);
+}
+
+static void GU_PRINTF(1, 2) NORETURN die_perr(const char *fmt, ...)
+{
+       va_list va;
+
+       va_start(va, fmt);
+       fprintf(stderr, "[FATAL] ");
+       vfprintf(stderr, fmt, va);
+       fprintf(stderr, ": %s\n", strerror(errno));
+       va_end(va);
+
+       exit(EXIT_FAILURE);
+}
+
+static void  check_chip_index(unsigned int index)
+{
+       if (index >= globals.test_ctx.num_chips)
+               die("invalid chip number requested from test code");
+}
+
+const char * gu_chip_path(unsigned int index)
+{
+       check_chip_index(index);
+
+       return globals.test_ctx.chips[index].path;
+}
+
+const char * gu_chip_name(unsigned int index)
+{
+       check_chip_index(index);
+
+       return globals.test_ctx.chips[index].name;
+}
+
+unsigned int gu_chip_num(unsigned int index)
+{
+       check_chip_index(index);
+
+       return globals.test_ctx.chips[index].number;
+}
+
+void _gu_register_test(struct gu_test *test)
+{
+       struct gu_test *tmp;
+
+       if (!globals.test_list_head) {
+               globals.test_list_head = globals.test_list_tail = test;
+               test->_next = NULL;
+       } else {
+               tmp = globals.test_list_tail;
+               globals.test_list_tail = test;
+               test->_next = NULL;
+               tmp->_next = test;
+       }
+
+       globals.num_tests++;
+}
+
+void _gu_set_test_failed(void)
+{
+       globals.test_ctx.test_failed = true;
+}
+
+static bool mockup_loaded(void)
+{
+       int state;
+
+       if (!globals.module_ctx || !globals.module)
+               return false;
+
+       state = kmod_module_get_initstate(globals.module);
+
+       return state == KMOD_MODULE_LIVE;
+}
+
+static void module_cleanup(void)
+{
+       gu_msg("cleaning up");
+
+       if (mockup_loaded())
+               kmod_module_remove_module(globals.module, 0);
+
+       if (globals.module)
+               kmod_module_unref(globals.module);
+
+       if (globals.module_ctx)
+               kmod_unref(globals.module_ctx);
+}
+
+static void check_gpio_mockup(void)
+{
+       const char *modpath;
+       int status;
+
+       gu_msg("checking gpio-mockup availability");
+
+       globals.module_ctx = kmod_new(NULL, NULL);
+       if (!globals.module_ctx)
+               die_perr("error creating kernel module context");
+
+       status = kmod_module_new_from_name(globals.module_ctx,
+                                          "gpio-mockup", &globals.module);
+       if (status)
+               die_perr("error allocating module info");
+
+       /* First see if we can find the module. */
+       modpath = kmod_module_get_path(globals.module);
+       if (!modpath)
+               die("the gpio-mockup module does not exist in the system or is built into the kernel");
+
+       /* Then see if we can freely load and unload it. */
+       status = kmod_module_probe_insert_module(globals.module, 0,
+                                                NULL, NULL, NULL, NULL);
+       if (status)
+               die_perr("unable to load gpio-mockup");
+
+       status = kmod_module_remove_module(globals.module, 0);
+       if (status)
+               die_perr("unable to remove gpio-mockup");
+
+       gu_msg("gpio-mockup ok");
+}
+
+static void test_load_module(struct gu_chip_descr *descr)
+{
+       char *modarg, *tmp_modarg;
+       char **line_sizes;
+       size_t modarg_len;
+       unsigned int i;
+       int status;
+
+       line_sizes = malloc(sizeof(char *) * descr->num_chips);
+       if (!line_sizes)
+               die("out of memory");
+
+       modarg_len = strlen("gpio_mockup_ranges=");
+       for (i = 0; i < descr->num_chips; i++) {
+               status = asprintf(&line_sizes[i],
+                                 "-1,%u,", descr->num_lines[i]);
+               if (status < 0)
+                       die_perr("asprintf");
+
+               modarg_len += status;
+       }
+
+       modarg = malloc(modarg_len + 1);
+       if (!modarg)
+               die("out of memory");
+       tmp_modarg = modarg;
+
+       status = sprintf(tmp_modarg, "gpio_mockup_ranges=");
+       tmp_modarg += status;
+
+       for (i = 0; i < descr->num_chips; i++) {
+               status = sprintf(tmp_modarg, "%s", line_sizes[i]);
+               tmp_modarg += status;
+       }
+
+       /*
+        * TODO Once the support for named lines in the gpio-mockup module
+        * is merged upstream, implement checking the named_lines field of
+        * the test description and setting the corresponding module param.
+        */
+
+       modarg[modarg_len - 1] = '\0'; /* Remove the last comma. */
+
+       gettimeofday(&globals.test_ctx.mod_loaded_ts, NULL);
+       status = kmod_module_probe_insert_module(globals.module, 0,
+                                                modarg, NULL, NULL, NULL);
+       if (status)
+               die_perr("unable to load gpio-mockup");
+
+       free(modarg);
+       for (i = 0; i < descr->num_chips; i++)
+               free(line_sizes[i]);
+       free(line_sizes);
+}
+
+/*
+ * To see if given chip is a mockup chip, check if it was created after
+ * inserting the gpio-mockup module. It's not too clever, but works well
+ * enough...
+ */
+static bool is_mockup_chip(const char *name)
+{
+       struct timeval gdev_created_ts;
+       struct stat gdev_stat;
+       char *path;
+       int status;
+
+       status = asprintf(&path, "/dev/%s", name);
+       if (status < 0)
+               die("asprintf");
+
+       status = stat(path, &gdev_stat);
+       free(path);
+       if (status < 0)
+               die_perr("stat");
+
+       gdev_created_ts.tv_sec = gdev_stat.st_ctim.tv_sec;
+       gdev_created_ts.tv_usec = gdev_stat.st_ctim.tv_nsec / 1000;
+
+       return timercmp(&globals.test_ctx.mod_loaded_ts, &gdev_created_ts, >=);
+}
+
+static int chipcmp(const void *c1, const void *c2)
+{
+       const struct mockup_chip *chip1 = c1;
+       const struct mockup_chip *chip2 = c2;
+
+       return strcmp(chip1->name, chip2->name);
+}
+
+static void test_prepare(struct gu_chip_descr *descr)
+{
+       struct test_context *ctx;
+       struct mockup_chip *chip;
+       unsigned int current = 0;
+       struct dirent *dentry;
+       int status;
+       DIR *dir;
+
+       ctx = &globals.test_ctx;
+       memset(ctx, 0, sizeof(*ctx));
+
+       test_load_module(descr);
+
+       ctx->num_chips = descr->num_chips;
+       ctx->chips = malloc(sizeof(*ctx->chips) * ctx->num_chips);
+       if (!ctx->chips)
+               die("out of memory");
+
+       dir = opendir("/dev");
+       if (!dir)
+               die_perr("error opening /dev");
+
+       for (dentry = readdir(dir); dentry; dentry = readdir(dir)) {
+               if (strncmp(dentry->d_name, "gpiochip", 8) == 0) {
+                       if (!is_mockup_chip(dentry->d_name))
+                               continue;
+
+                       chip = &ctx->chips[current++];
+
+                       chip->name = strdup(dentry->d_name);
+                       if (!chip->name)
+                               die("out of memory");
+
+                       status = asprintf(&chip->path,
+                                         "/dev/%s", dentry->d_name);
+                       if (status < 0)
+                               die_perr("asprintf");
+
+                       status = sscanf(dentry->d_name,
+                                       "gpiochip%u", &chip->number);
+                       if (status != 1)
+                               die("unable to determine the chip number");
+               }
+       }
+
+       qsort(ctx->chips, ctx->num_chips, sizeof(*ctx->chips), chipcmp);
+
+       if (descr->num_chips != current)
+               die("number of requested and detected mockup gpiochips is not the same");
+
+       closedir(dir);
+}
+
+static void test_teardown(void)
+{
+       struct mockup_chip *chip;
+       unsigned int i;
+       int status;
+
+       for (i = 0; i < globals.test_ctx.num_chips; i++) {
+               chip = &globals.test_ctx.chips[i];
+
+               free(chip->path);
+               free(chip->name);
+       }
+
+       free(globals.test_ctx.chips);
+
+       status = kmod_module_remove_module(globals.module, 0);
+       if (status)
+               die_perr("unable to remove gpio-mockup");
+}
+
+int main(int argc GU_UNUSED, char **argv GU_UNUSED)
+{
+       struct gu_test *test;
+
+       atexit(module_cleanup);
+
+       gu_msg("libgpiod unit-test suite");
+       gu_msg("%u tests registered", globals.num_tests);
+
+       check_gpio_mockup();
+
+       gu_msg("running tests");
+
+       for (test = globals.test_list_head; test; test = test->_next) {
+               test_prepare(&test->chip_descr);
+
+               test->func();
+               gu_msg("test '%s': %s", test->name,
+                      globals.test_ctx.test_failed ? "FAILED" : "OK");
+               if (globals.test_ctx.test_failed)
+                       globals.tests_failed++;
+
+               test_teardown();
+       }
+
+       if (!globals.tests_failed)
+               gu_msg("all tests passed");
+       else
+               gu_err("%u out of %u tests failed",
+                      globals.tests_failed, globals.num_tests);
+
+       return globals.tests_failed ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/tests/unit/gpiod-unit.h b/tests/unit/gpiod-unit.h
new file mode 100644 (file)
index 0000000..ffefabe
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Unit testing framework for libgpiod.
+ *
+ * Copyright (C) 2017 Bartosz Golaszewski <bartekgola@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#ifndef __GPIOD_UNIT_H__
+#define __GPIOD_UNIT_H__
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#define GU_INIT                        __attribute__((constructor))
+#define GU_UNUSED              __attribute__((unused))
+#define GU_PRINTF(fmt, arg)    __attribute__((format(printf, fmt, arg)))
+#define GU_CLEANUP(func)       __attribute__((cleanup(func)))
+
+#define GU_ARRAY_SIZE(x)       (sizeof(x) / sizeof(*(x)))
+
+typedef void (*gu_test_func)(void);
+
+struct gu_chip_descr {
+       unsigned int num_chips;
+       unsigned int *num_lines;
+       bool named_lines;
+};
+
+struct gu_test {
+       struct gu_test *_next;
+
+       const char *name;
+       gu_test_func func;
+
+       struct gu_chip_descr chip_descr;
+};
+
+void _gu_register_test(struct gu_test *test);
+void _gu_set_test_failed(void);
+
+#define GU_REGISTER_TEST(test)                                         \
+       static GU_INIT void _gu_register_##test(void)                   \
+       {                                                               \
+               _gu_register_test(&test);                               \
+       }                                                               \
+       static int _gu_##test##_sentinel GU_UNUSED
+
+#define GU_DEFINE_TEST(_a_func, _a_name, _a_named_lines, ...)          \
+       static unsigned int _##_a_func##_lines[] = __VA_ARGS__;         \
+       static struct gu_test _##_a_func##_descr = {                    \
+               .name = _a_name,                                        \
+               .func = _a_func,                                        \
+               .chip_descr = {                                         \
+                       .num_chips = GU_ARRAY_SIZE(_##_a_func##_lines), \
+                       .num_lines = _##_a_func##_lines,                \
+                       .named_lines = _a_named_lines,                  \
+               },                                                      \
+       };                                                              \
+       GU_REGISTER_TEST(_##_a_func##_descr)
+
+enum {
+       GU_LINES_UNNAMED = false,
+       GU_LINES_NAMED = true,
+};
+
+void GU_PRINTF(1, 2) gu_msg(const char *fmt, ...);
+void GU_PRINTF(1, 2) gu_err(const char *fmt, ...);
+
+const char * gu_chip_path(unsigned int index);
+const char * gu_chip_name(unsigned int index);
+unsigned int gu_chip_num(unsigned int index);
+
+#define GU_ASSERT(statement)                                           \
+       do {                                                            \
+               if (!(statement)) {                                     \
+                       gu_err("assertion failed (%s:%d): '%s'",        \
+                              __FILE__, __LINE__, #statement);         \
+                       _gu_set_test_failed();                          \
+                       return;                                         \
+               }                                                       \
+       } while (0)
+
+#define GU_ASSERT_NOT_NULL(ptr)                GU_ASSERT(ptr != NULL)
+#define GU_ASSERT_NULL(ptr)            GU_ASSERT(ptr == NULL)
+#define GU_ASSERT_EQ(a1, a2)           GU_ASSERT(a1 == a2)
+#define GU_ASSERT_STR_EQ(s1, s2)       GU_ASSERT(strcmp(s1, s2) == 0)
+
+#endif /* __GPIOD_UNIT_H__ */
diff --git a/tests/unit/tests-chip.c b/tests/unit/tests-chip.c
new file mode 100644 (file)
index 0000000..514da80
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * GPIO chip test cases for libgpiod.
+ *
+ * Copyright (C) 2017 Bartosz Golaszewski <bartekgola@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include "gpiod-unit.h"
+#include <gpiod.h>
+
+#include <stdio.h>
+#include <errno.h>
+
+static void close_chip(struct gpiod_chip **chip)
+{
+       if (*chip)
+               gpiod_chip_close(*chip);
+}
+
+static void free_str(char **str)
+{
+       if (*str)
+               free(*str);
+}
+
+static void chip_open_good(void)
+{
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip = NULL;
+
+       chip = gpiod_chip_open(gu_chip_path(0));
+       GU_ASSERT_NOT_NULL(chip);
+}
+GU_DEFINE_TEST(chip_open_good,
+              "gpiod_chip_open() good",
+              GU_LINES_UNNAMED, { 8 });
+
+static void chip_open_nonexistent(void)
+{
+       struct gpiod_chip *chip;
+
+       chip = gpiod_chip_open("/dev/nonexistent_gpiochip");
+       GU_ASSERT_NULL(chip);
+       GU_ASSERT_EQ(gpiod_errno(), ENOENT);
+}
+GU_DEFINE_TEST(chip_open_nonexistent,
+              "gpiod_chip_open() nonexistent",
+              GU_LINES_UNNAMED, { 8 });
+
+static void chip_open_notty(void)
+{
+       struct gpiod_chip *chip;
+
+       chip = gpiod_chip_open("/dev/null");
+       GU_ASSERT_NULL(chip);
+       GU_ASSERT_EQ(gpiod_errno(), ENOTTY);
+}
+GU_DEFINE_TEST(chip_open_notty,
+              "gpiod_chip_open() notty",
+              GU_LINES_UNNAMED, { 8 });
+
+static void chip_open_by_number_good(void)
+{
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip = NULL;
+
+       chip = gpiod_chip_open_by_number(gu_chip_num(0));
+       GU_ASSERT_NOT_NULL(chip);
+}
+GU_DEFINE_TEST(chip_open_by_number_good,
+              "gpiod_chip_open_by_number() good",
+              GU_LINES_UNNAMED, { 8 });
+
+static void chip_open_lookup(void)
+{
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip_by_name = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip_by_path = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip_by_num = NULL;
+       GU_CLEANUP(free_str) char *chip_num;
+
+       GU_ASSERT(asprintf(&chip_num, "%u", gu_chip_num(0)) > 0);
+
+       chip_by_name = gpiod_chip_open_lookup(gu_chip_name(0));
+       chip_by_path = gpiod_chip_open_lookup(gu_chip_path(0));
+       chip_by_num = gpiod_chip_open_lookup(chip_num);
+
+       GU_ASSERT_NOT_NULL(chip_by_name);
+       GU_ASSERT_NOT_NULL(chip_by_path);
+       GU_ASSERT_NOT_NULL(chip_by_num);
+}
+GU_DEFINE_TEST(chip_open_lookup,
+              "gpiod_chip_open_lookup()",
+              GU_LINES_UNNAMED, { 8 });
+
+static void chip_name(void)
+{
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip0 = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip1 = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip2 = NULL;
+
+       chip0 = gpiod_chip_open(gu_chip_path(0));
+       chip1 = gpiod_chip_open(gu_chip_path(1));
+       chip2 = gpiod_chip_open(gu_chip_path(2));
+       GU_ASSERT_NOT_NULL(chip0);
+       GU_ASSERT_NOT_NULL(chip1);
+       GU_ASSERT_NOT_NULL(chip2);
+
+       GU_ASSERT_STR_EQ(gpiod_chip_name(chip0), gu_chip_name(0));
+       GU_ASSERT_STR_EQ(gpiod_chip_name(chip1), gu_chip_name(1));
+       GU_ASSERT_STR_EQ(gpiod_chip_name(chip2), gu_chip_name(2));
+}
+GU_DEFINE_TEST(chip_name, "gpiod_chip_name()", GU_LINES_UNNAMED, { 8, 8, 8 });
+
+static void chip_label(void)
+{
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip0 = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip1 = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip2 = NULL;
+
+       chip0 = gpiod_chip_open(gu_chip_path(0));
+       chip1 = gpiod_chip_open(gu_chip_path(1));
+       chip2 = gpiod_chip_open(gu_chip_path(2));
+       GU_ASSERT_NOT_NULL(chip0);
+       GU_ASSERT_NOT_NULL(chip1);
+       GU_ASSERT_NOT_NULL(chip2);
+
+       GU_ASSERT_STR_EQ(gpiod_chip_label(chip0), "gpio-mockup-A");
+       GU_ASSERT_STR_EQ(gpiod_chip_label(chip1), "gpio-mockup-B");
+       GU_ASSERT_STR_EQ(gpiod_chip_label(chip2), "gpio-mockup-C");
+}
+GU_DEFINE_TEST(chip_label, "gpiod_chip_label()",
+              GU_LINES_UNNAMED, { 8, 8, 8 });
+
+static void chip_num_lines(void)
+{
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip0 = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip1 = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip2 = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip3 = NULL;
+       GU_CLEANUP(close_chip) struct gpiod_chip *chip4 = NULL;
+
+       chip0 = gpiod_chip_open(gu_chip_path(0));
+       chip1 = gpiod_chip_open(gu_chip_path(1));
+       chip2 = gpiod_chip_open(gu_chip_path(2));
+       chip3 = gpiod_chip_open(gu_chip_path(3));
+       chip4 = gpiod_chip_open(gu_chip_path(4));
+       GU_ASSERT_NOT_NULL(chip0);
+       GU_ASSERT_NOT_NULL(chip1);
+       GU_ASSERT_NOT_NULL(chip2);
+       GU_ASSERT_NOT_NULL(chip3);
+       GU_ASSERT_NOT_NULL(chip4);
+
+       GU_ASSERT_EQ(gpiod_chip_num_lines(chip0), 1);
+       GU_ASSERT_EQ(gpiod_chip_num_lines(chip1), 4);
+       GU_ASSERT_EQ(gpiod_chip_num_lines(chip2), 8);
+       GU_ASSERT_EQ(gpiod_chip_num_lines(chip3), 16);
+       GU_ASSERT_EQ(gpiod_chip_num_lines(chip4), 32);
+}
+GU_DEFINE_TEST(chip_num_lines, "chip_num_lines()",
+              GU_LINES_UNNAMED, { 1, 4, 8, 16, 32 });