about summary refs log tree commit diff
path: root/src/c-list
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2024-05-05 00:07:30 +0200
committerMichael Biebl <biebl@debian.org>2024-05-05 00:07:30 +0200
commit34bb501be08aa2b313d88e67d6e0a7e0a3f9cfa6 (patch)
tree4e6220877828be4c6f261de09ec0cb2d80e32389 /src/c-list
parentbba2e4b4de668db525cbfdfc35292e5a0b51671a (diff)
New upstream version 1.47.90 upstream/1.47.90
Diffstat (limited to 'src/c-list')
-rw-r--r--src/c-list/.editorconfig11
-rw-r--r--src/c-list/.github/workflows/ci.yml32
-rw-r--r--src/c-list/AUTHORS41
-rw-r--r--src/c-list/NEWS.md77
-rw-r--r--src/c-list/README.md52
-rw-r--r--src/c-list/meson.build46
-rw-r--r--src/c-list/src/meson.build34
-rw-r--r--src/c-list/src/test-api.c144
-rw-r--r--src/c-list/src/test-basic.c319
-rw-r--r--src/c-list/src/test-embed.c173
10 files changed, 929 insertions, 0 deletions
diff --git a/src/c-list/.editorconfig b/src/c-list/.editorconfig
new file mode 100644
index 00000000..b10bb4f3
--- /dev/null
+++ b/src/c-list/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+
+[*.{c,h}]
+indent_style = space
+indent_size = 8
diff --git a/src/c-list/.github/workflows/ci.yml b/src/c-list/.github/workflows/ci.yml
new file mode 100644
index 00000000..00bca960
--- /dev/null
+++ b/src/c-list/.github/workflows/ci.yml
@@ -0,0 +1,32 @@
+name: Continuous Integration
+
+on:
+  push:
+  pull_request:
+  schedule:
+  - cron:  '0 0 * * *'
+
+jobs:
+  ci-linux:
+    name: Linux CI
+    uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1
+    with:
+      cabuild_ref: "v1"
+      linux: true
+      m32: true
+      matrixmode: true
+      valgrind: true
+  ci-macos:
+    name: MacOS CI
+    uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1
+    with:
+      cabuild_ref: "v1"
+      linux: false
+      macos: true
+  ci-windows:
+    name: Windows CI
+    uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1
+    with:
+      cabuild_ref: "v1"
+      linux: false
+      windows: true
diff --git a/src/c-list/AUTHORS b/src/c-list/AUTHORS
new file mode 100644
index 00000000..76dea872
--- /dev/null
+++ b/src/c-list/AUTHORS
@@ -0,0 +1,41 @@
+LICENSE:
+        This project is dual-licensed under both the Apache License, Version
+        2.0, and the GNU Lesser General Public License, Version 2.1+.
+
+AUTHORS-ASL:
+        Licensed under the Apache License, Version 2.0 (the "License");
+        you may not use this file except in compliance with the License.
+        You may obtain a copy of the License at
+
+                http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+
+AUTHORS-LGPL:
+        This program is free software; you can redistribute it and/or modify it
+        under the terms of the GNU Lesser General Public License as published
+        by the Free Software Foundation; either version 2.1 of the License, or
+        (at your option) any later version.
+
+        This program is distributed in the hope that it will be useful, but
+        WITHOUT ANY WARRANTY; without even the implied warranty of
+        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+        Lesser General Public License for more details.
+
+        You should have received a copy of the GNU Lesser General Public License
+        along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+COPYRIGHT: (ordered alphabetically)
+        Copyright (C) 2015-2022 Red Hat, Inc.
+
+AUTHORS: (ordered alphabetically)
+        Danilo Horta <danilo.horta@pm.me>
+        David Rheinsberg <david.rheinsberg@gmail.com>
+        Lucas De Marchi <lucas.de.marchi@gmail.com>
+        Michele Dionisio
+        Thomas Haller <thaller@redhat.com>
+        Tom Gundersen <teg@jklm.no>
diff --git a/src/c-list/NEWS.md b/src/c-list/NEWS.md
new file mode 100644
index 00000000..095f1ea7
--- /dev/null
+++ b/src/c-list/NEWS.md
@@ -0,0 +1,77 @@
+# c-list - Circular Intrusive Double Linked List Collection
+
+## CHANGES WITH 3.1.0:
+
+        * The minimum required meson version is now 0.60.0.
+
+        * New function c_list_split() is added. It reverses c_list_splice()
+          and thus allows to split a list in half.
+
+        Contributions from: David Rheinsberg, Michele Dionisio
+
+        - Brno, 2022-06-22
+
+## CHANGES WITH 3:
+
+        * API break: The c_list_loop_*() symbols were removed, since we saw
+                     little use for them. No user was known at the time, so
+                     all projects should build with the new API version
+                     unchanged.
+                     Since c-list does not distribute any compiled code, there
+                     is no ABI issue with this change.
+
+        * Two new symbols c_list_length() and c_list_contains(). They are meant
+          for debugging purposes, to easily verify list integrity. Since they
+          run in O(n) time, they are not recommended for any other use than
+          debugging.
+
+        * New symbol c_list_init() is provided as alternative to the verbose
+          C_LIST_INIT assignment.
+
+        * The c-list API is extended to work well with `const CList` objects.
+          That is, any read-only accessor function allows constant objects as
+          input now.
+          Note that this does not propagate into other members linked in the
+          list. Using `const` for CList members is of little practical use.
+          However, it might be of use for its embedding objects, so we now
+          allow it in the CList API as well.
+
+        * The c_list_splice() call now clears the source list, rather than
+          returning with stale pointers. Technically, this is also an API
+          break, but unlikely to affect any existing code.
+
+        Contributions from: David Herrmann, Thomas Haller
+
+        - Berlin, 2017-08-13
+
+## CHANGES WITH 2:
+
+        * Adjust project-name in build-system to reflect the actual project. The
+          previous releases incorrectly claimed to be c-rbtree in the build
+          system.
+
+        * Add c_list_swap() that swaps two lists given their head pointers.
+
+        * Add c_list_splice() that moves a list.
+
+        * Add LGPL2.1+ as license so c-list can be imported into GPL2 projects.
+          It is now officially dual-licensed.
+
+        * As usual a bunch of fixes, additional tests, and documentation
+          updates.
+
+        Contributions from: David Herrmann, Tom Gundersen
+
+        - Lund, 2017-05-03
+
+## CHANGES WITH 1:
+
+        * Initial release of c-list.
+
+        * This project provides an implementation of a circular double linked
+          list in standard ISO-C11. License is ASL-2.0 and the build system
+          used is `Meson'.
+
+        Contributions from: David Herrmann, Tom Gundersen
+
+        - Berlin, 2017-03-03
diff --git a/src/c-list/README.md b/src/c-list/README.md
new file mode 100644
index 00000000..a2e47528
--- /dev/null
+++ b/src/c-list/README.md
@@ -0,0 +1,52 @@
+c-list
+======
+
+Circular Intrusive Double Linked List Collection
+
+The c-list project implements an intrusive collection based on circular double
+linked lists in ISO-C11. It aims for minimal API constraints, leaving maximum
+control over the data-structures to the API consumer.
+
+### Project
+
+ * **Website**: <https://c-util.github.io/c-list>
+ * **Bug Tracker**: <https://github.com/c-util/c-list/issues>
+
+### Requirements
+
+The requirements for this project are:
+
+ * `libc` (e.g., `glibc >= 2.16`)
+
+At build-time, the following software is required:
+
+ * `meson >= 0.60`
+ * `pkg-config >= 0.29`
+
+### Build
+
+The meson build-system is used for this project. Contact upstream
+documentation for detailed help. In most situations the following
+commands are sufficient to build and install from source:
+
+```sh
+mkdir build
+cd build
+meson setup ..
+ninja
+meson test
+ninja install
+```
+
+No custom configuration options are available.
+
+### Repository:
+
+ - **web**:   <https://github.com/c-util/c-list>
+ - **https**: `https://github.com/c-util/c-list.git`
+ - **ssh**:   `git@github.com:c-util/c-list.git`
+
+### License:
+
+ - **Apache-2.0** OR **LGPL-2.1-or-later**
+ - See AUTHORS file for details.
diff --git a/src/c-list/meson.build b/src/c-list/meson.build
new file mode 100644
index 00000000..b5ef78e6
--- /dev/null
+++ b/src/c-list/meson.build
@@ -0,0 +1,46 @@
+project(
+        'c-list',
+        'c',
+        default_options: [
+                'c_std=c99',
+        ],
+        license: 'Apache',
+        meson_version: '>=0.60.0',
+        version: '3.1.0',
+)
+major = meson.project_version().split('.')[0]
+project_description = 'Circular Intrusive Double Linked List Collection'
+
+mod_pkgconfig = import('pkgconfig')
+
+# See c-stdaux for details on these. We do not have c-stdaux as dependency, so
+# we keep a duplicated set here, reduced to the minimum.
+cflags = meson.get_compiler('c').get_supported_arguments(
+        '-D_GNU_SOURCE',
+
+        '-Wno-gnu-alignof-expression',
+        '-Wno-maybe-uninitialized',
+        '-Wno-unknown-warning-option',
+        '-Wno-unused-parameter',
+
+        '-Wno-error=type-limits',
+        '-Wno-error=missing-field-initializers',
+
+        '-Wdate-time',
+        '-Wdeclaration-after-statement',
+        '-Wlogical-op',
+        '-Wmissing-include-dirs',
+        '-Wmissing-noreturn',
+        '-Wnested-externs',
+        '-Wredundant-decls',
+        '-Wshadow',
+        '-Wstrict-aliasing=3',
+        '-Wsuggest-attribute=noreturn',
+        '-Wundef',
+        '-Wwrite-strings',
+)
+add_project_arguments(cflags, language: 'c')
+
+subdir('src')
+
+meson.override_dependency('libclist-'+major, libclist_dep, static: true)
diff --git a/src/c-list/src/meson.build b/src/c-list/src/meson.build
new file mode 100644
index 00000000..ec7f29d5
--- /dev/null
+++ b/src/c-list/src/meson.build
@@ -0,0 +1,34 @@
+#
+# target: libclist.so
+# (No .so is built so far, since we are header-only. This might change in the
+#  future, if we add more complex list helpers.)
+#
+
+libclist_dep = declare_dependency(
+        include_directories: include_directories('.'),
+        version: meson.project_version(),
+)
+
+if not meson.is_subproject()
+        install_headers('c-list.h')
+
+        mod_pkgconfig.generate(
+                description: project_description,
+                filebase: 'libclist-'+major,
+                name: 'libclist',
+                version: meson.project_version(),
+        )
+endif
+
+#
+# target: test-*
+#
+
+test_api = executable('test-api', ['test-api.c'], dependencies: libclist_dep)
+test('API Symbol Visibility', test_api)
+
+test_basic = executable('test-basic', ['test-basic.c'], dependencies: libclist_dep)
+test('Basic API Behavior', test_basic)
+
+test_embed = executable('test-embed', ['test-embed.c'], dependencies: libclist_dep)
+test('Embedded List Nodes', test_embed)
diff --git a/src/c-list/src/test-api.c b/src/c-list/src/test-api.c
new file mode 100644
index 00000000..864d198a
--- /dev/null
+++ b/src/c-list/src/test-api.c
@@ -0,0 +1,144 @@
+/*
+ * Tests for Public API
+ * This test, unlikely the others, is linked against the real, distributed,
+ * shared library. Its sole purpose is to test for symbol availability.
+ */
+
+#undef NDEBUG
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "c-list.h"
+
+typedef struct {
+        int id;
+        CList link;
+} Node;
+
+static void test_api(void) {
+        CList *list_iter, *list_safe;
+        CList list = C_LIST_INIT(list), list2 = C_LIST_INIT(list2);
+        Node node = { .id = 0, .link = C_LIST_INIT(node.link) };
+
+        assert(c_list_init(&list) == &list);
+        assert(!c_list_entry_offset(NULL, 0));
+        assert(!c_list_entry_offset(NULL, offsetof(Node, link)));
+        assert(!c_list_entry(NULL, Node, link));
+        assert(c_list_entry(&node.link, Node, link) == &node);
+        assert(!c_list_is_linked(&node.link));
+        assert(c_list_is_empty(&list));
+        assert(c_list_length(&list) == 0);
+        assert(c_list_contains(&list, &list));
+        assert(!c_list_contains(&list, &node.link));
+        c_list_flush(&list);
+
+        /* basic link / unlink calls */
+
+        c_list_link_before(&list, &node.link);
+        assert(c_list_is_linked(&node.link));
+        assert(!c_list_is_empty(&list));
+        assert(c_list_length(&list) == 1);
+        assert(c_list_contains(&list, &list));
+        assert(c_list_contains(&list, &node.link));
+
+        c_list_unlink_stale(&node.link);
+        assert(c_list_is_linked(&node.link));
+        assert(c_list_is_empty(&list));
+        assert(c_list_length(&list) == 0);
+
+        c_list_link_after(&list, &node.link);
+        assert(c_list_is_linked(&node.link));
+        assert(!c_list_is_empty(&list));
+
+        c_list_unlink(&node.link);
+        assert(!c_list_is_linked(&node.link));
+        assert(c_list_is_empty(&list));
+
+        /* link / unlink aliases */
+
+        c_list_link_front(&list, &node.link);
+        assert(c_list_is_linked(&node.link));
+
+        c_list_unlink(&node.link);
+        assert(!c_list_is_linked(&node.link));
+
+        c_list_link_tail(&list, &node.link);
+        assert(c_list_is_linked(&node.link));
+
+        c_list_unlink(&node.link);
+        assert(!c_list_is_linked(&node.link));
+
+        /* swap / splice / split list operators */
+
+        c_list_swap(&list, &list);
+        assert(c_list_is_empty(&list));
+
+        c_list_splice(&list, &list);
+        assert(c_list_is_empty(&list));
+
+        c_list_split(&list, &list, &list2);
+        assert(c_list_is_empty(&list));
+        assert(c_list_is_empty(&list2));
+
+        /* direct/raw iterators */
+
+        c_list_for_each(list_iter, &list)
+                assert(list_iter != &list);
+
+        c_list_for_each_safe(list_iter, list_safe, &list)
+                assert(list_iter != &list);
+
+        list_iter = NULL;
+        c_list_for_each_continue(list_iter, &list)
+                assert(list_iter != &list);
+
+        list_iter = NULL;
+        c_list_for_each_safe_continue(list_iter, list_safe, &list)
+                assert(list_iter != &list);
+
+        c_list_for_each_safe_unlink(list_iter, list_safe, &list)
+                assert(list_iter != &list);
+
+        /* list accessors */
+
+        assert(!c_list_first(&list));
+        assert(!c_list_last(&list));
+        assert(!c_list_first_entry(&list, Node, link));
+        assert(!c_list_last_entry(&list, Node, link));
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+static void test_api_gnu(void) {
+        CList list = C_LIST_INIT(list);
+        Node *node_iter, *node_safe;
+
+        /* c_list_entry() based iterators */
+
+        c_list_for_each_entry(node_iter, &list, link)
+                assert(&node_iter->link != &list);
+
+        c_list_for_each_entry_safe(node_iter, node_safe, &list, link)
+                assert(&node_iter->link != &list);
+
+        node_iter = NULL;
+        c_list_for_each_entry_continue(node_iter, &list, link)
+                assert(&node_iter->link != &list);
+
+        node_iter = NULL;
+        c_list_for_each_entry_safe_continue(node_iter, node_safe, &list, link)
+                assert(&node_iter->link != &list);
+
+        c_list_for_each_entry_safe_unlink(node_iter, node_safe, &list, link)
+                assert(&node_iter->link != &list);
+}
+#else
+static void test_api_gnu(void) {
+}
+#endif
+
+int main(void) {
+        test_api();
+        test_api_gnu();
+        return 0;
+}
diff --git a/src/c-list/src/test-basic.c b/src/c-list/src/test-basic.c
new file mode 100644
index 00000000..58ed8636
--- /dev/null
+++ b/src/c-list/src/test-basic.c
@@ -0,0 +1,319 @@
+/*
+ * Tests for basic functionality
+ * This contains basic, deterministic tests for list behavior, API
+ * functionality, and usage.
+ */
+
+#undef NDEBUG
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "c-list.h"
+
+static void assert_list_integrity(CList *list) {
+        CList *iter;
+
+        iter = list;
+        do {
+                assert(iter->next->prev == iter);
+                assert(iter->prev->next == iter);
+
+                iter = iter->next;
+        } while (iter != list);
+}
+
+static void test_iterators(void) {
+        CList *iter, *safe, a, b, list = C_LIST_INIT(list);
+        unsigned int i;
+
+        assert(!c_list_first(&list));
+        assert(!c_list_last(&list));
+
+        /* link @a and verify iterators see just it */
+
+        c_list_link_tail(&list, &a);
+        assert(c_list_is_linked(&a));
+        assert(c_list_first(&list) == &a);
+        assert(c_list_last(&list) == &a);
+
+        i = 0;
+        c_list_for_each(iter, &list) {
+                assert(iter == &a);
+                ++i;
+        }
+        assert(i == 1);
+
+        i = 0;
+        iter = NULL;
+        c_list_for_each_continue(iter, &list) {
+                assert(iter == &a);
+                ++i;
+        }
+        assert(i == 1);
+
+        i = 0;
+        iter = &a;
+        c_list_for_each_continue(iter, &list)
+                ++i;
+        assert(i == 0);
+
+        /* link @b as well and verify iterators again */
+
+        c_list_link_tail(&list, &b);
+        assert(c_list_is_linked(&a));
+        assert(c_list_is_linked(&b));
+
+        i = 0;
+        c_list_for_each(iter, &list) {
+                assert((i == 0 && iter == &a) ||
+                       (i == 1 && iter == &b));
+                ++i;
+        }
+        assert(i == 2);
+
+        i = 0;
+        iter = NULL;
+        c_list_for_each_continue(iter, &list) {
+                assert((i == 0 && iter == &a) ||
+                       (i == 1 && iter == &b));
+                ++i;
+        }
+        assert(i == 2);
+
+        i = 0;
+        iter = &a;
+        c_list_for_each_continue(iter, &list) {
+                assert(iter == &b);
+                ++i;
+        }
+        assert(i == 1);
+
+        i = 0;
+        iter = &b;
+        c_list_for_each_continue(iter, &list)
+                ++i;
+        assert(i == 0);
+
+        /* verify safe-iterator while removing elements */
+
+        i = 0;
+        c_list_for_each_safe(iter, safe, &list) {
+                assert(iter == &a || iter == &b);
+                c_list_unlink_stale(iter);
+                ++i;
+        }
+        assert(i == 2);
+
+        assert(c_list_is_empty(&list));
+
+        /* link both and verify *_unlink() iterators */
+
+        c_list_link_tail(&list, &a);
+        c_list_link_tail(&list, &b);
+
+        i = 0;
+        c_list_for_each_safe_unlink(iter, safe, &list) {
+                assert(iter == &a || iter == &b);
+                assert(!c_list_is_linked(iter));
+                ++i;
+        }
+        assert(i == 2);
+
+        assert(c_list_is_empty(&list));
+}
+
+static void test_swap(void) {
+        CList list1 = (CList)C_LIST_INIT(list1);
+        CList list2 = (CList)C_LIST_INIT(list2);
+        CList list;
+
+        c_list_swap(&list1, &list2);
+
+        assert(list1.prev == list1.next && list1.prev == &list1);
+        assert(list2.prev == list2.next && list2.prev == &list2);
+
+        c_list_link_tail(&list1, &list);
+
+        assert(c_list_first(&list1) == &list);
+        assert(c_list_last(&list1) == &list);
+        assert(list.next == &list1);
+        assert(list.prev == &list1);
+
+        c_list_swap(&list1, &list2);
+
+        assert(c_list_first(&list2) == &list);
+        assert(c_list_last(&list2) == &list);
+        assert(list.next == &list2);
+        assert(list.prev == &list2);
+
+        assert(list1.prev == list1.next && list1.prev == &list1);
+}
+
+static void test_splice(void) {
+        CList target = (CList)C_LIST_INIT(target);
+        CList source = (CList)C_LIST_INIT(source);
+        CList e1, e2;
+
+        c_list_link_tail(&source, &e1);
+
+        c_list_splice(&target, &source);
+        assert(c_list_first(&target) == &e1);
+        assert(c_list_last(&target) == &e1);
+
+        source = (CList)C_LIST_INIT(source);
+
+        c_list_link_tail(&source, &e2);
+
+        c_list_splice(&target, &source);
+        assert(c_list_first(&target) == &e1);
+        assert(c_list_last(&target) == &e2);
+}
+
+static void test_split(void) {
+        CList e1, e2;
+
+        /* split empty list */
+        {
+                CList source = C_LIST_INIT(source), target;
+
+                c_list_split(&source, &source, &target);
+                assert(c_list_is_empty(&source));
+                assert(c_list_is_empty(&target));
+                assert_list_integrity(&source);
+                assert_list_integrity(&target);
+        }
+
+        /* split 1-element list excluding the element */
+        {
+                CList source = C_LIST_INIT(source), target;
+
+                c_list_link_tail(&source, &e1);
+                c_list_split(&source, &source, &target);
+                assert(!c_list_is_empty(&source));
+                assert(c_list_is_empty(&target));
+                assert_list_integrity(&source);
+                assert_list_integrity(&target);
+        }
+
+        /* split 1-element list including the element */
+        {
+                CList source = C_LIST_INIT(source), target;
+
+                c_list_link_tail(&source, &e1);
+                c_list_split(&source, &e1, &target);
+                assert(c_list_is_empty(&source));
+                assert(!c_list_is_empty(&target));
+                assert_list_integrity(&source);
+                assert_list_integrity(&target);
+        }
+
+        /* split 2-element list excluding the elements */
+        {
+                CList source = C_LIST_INIT(source), target;
+
+                c_list_link_tail(&source, &e1);
+                c_list_link_tail(&source, &e2);
+                c_list_split(&source, &source, &target);
+                assert(!c_list_is_empty(&source));
+                assert(c_list_is_empty(&target));
+                assert_list_integrity(&source);
+                assert_list_integrity(&target);
+        }
+
+        /* split 2-element list including one element */
+        {
+                CList source = C_LIST_INIT(source), target;
+
+                c_list_link_tail(&source, &e1);
+                c_list_link_tail(&source, &e2);
+                c_list_split(&source, &e2, &target);
+                assert(!c_list_is_empty(&source));
+                assert(!c_list_is_empty(&target));
+                assert_list_integrity(&source);
+                assert_list_integrity(&target);
+        }
+
+        /* split 2-element list including both elements */
+        {
+                CList source = C_LIST_INIT(source), target;
+
+                c_list_link_tail(&source, &e1);
+                c_list_link_tail(&source, &e2);
+                c_list_split(&source, &e1, &target);
+                assert(c_list_is_empty(&source));
+                assert(!c_list_is_empty(&target));
+                assert_list_integrity(&source);
+                assert_list_integrity(&target);
+        }
+}
+
+
+static void test_flush(void) {
+        CList e1 = C_LIST_INIT(e1), e2 = C_LIST_INIT(e2);
+        CList list1 = C_LIST_INIT(list1), list2 = C_LIST_INIT(list2);
+
+        c_list_link_tail(&list2, &e1);
+        c_list_link_tail(&list2, &e2);
+
+        assert(c_list_is_linked(&e1));
+        assert(c_list_is_linked(&e2));
+
+        c_list_flush(&list1);
+        c_list_flush(&list2);
+
+        assert(!c_list_is_linked(&e1));
+        assert(!c_list_is_linked(&e2));
+}
+
+static void test_macros(void) {
+        /* Verify `c_list_entry()` evaluates arguments only once. */
+        {
+                struct TestList {
+                        int a;
+                        CList link;
+                        int b;
+                } list = { .link = C_LIST_INIT(list.link) };
+                CList *p[2] = { &list.link, NULL };
+                unsigned int i = 0;
+
+                assert(i == 0);
+                assert(c_list_entry(p[i++], struct TestList, link) == &list);
+                assert(i == 1);
+        }
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+static void test_gnu(void) {
+        CList e1 = C_LIST_INIT(e1), e2 = C_LIST_INIT(e2);
+
+        /* Test `c_list_flush()` in combination with cleanup attributes. */
+        {
+                __attribute((cleanup(c_list_flush))) CList list1 = C_LIST_INIT(list1);
+                __attribute((cleanup(c_list_flush))) CList list2 = C_LIST_INIT(list2);
+
+                c_list_link_tail(&list2, &e1);
+                c_list_link_tail(&list2, &e2);
+
+                assert(c_list_is_linked(&e1));
+                assert(c_list_is_linked(&e2));
+        }
+
+        assert(!c_list_is_linked(&e1));
+        assert(!c_list_is_linked(&e2));
+}
+#else
+static void test_gnu(void) {
+}
+#endif
+
+int main(void) {
+        test_iterators();
+        test_swap();
+        test_splice();
+        test_split();
+        test_flush();
+        test_macros();
+        test_gnu();
+        return 0;
+}
diff --git a/src/c-list/src/test-embed.c b/src/c-list/src/test-embed.c
new file mode 100644
index 00000000..7ee6ff01
--- /dev/null
+++ b/src/c-list/src/test-embed.c
@@ -0,0 +1,173 @@
+/*
+ * Tests for embedded CList members
+ */
+
+#undef NDEBUG
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "c-list.h"
+
+typedef struct Entry Entry;
+
+struct Entry {
+        short foo;
+        CList link;
+        short bar;
+};
+
+static void test_entry(void) {
+        CList list = C_LIST_INIT(list);
+        Entry e1 = { .foo = 1 * 7, .bar = 1 * 11 };
+        Entry e2 = { .foo = 2 * 7, .bar = 2 * 11 };
+        Entry e3 = { .foo = 3 * 7, .bar = 3 * 11 };
+        Entry e4 = { .foo = 4 * 7, .bar = 4 * 11 };
+        Entry *e;
+        CList *iter, *safe;
+        size_t i;
+
+        /* verify c_list_entry() works as expected (even with NULL) */
+
+        assert(!c_list_entry(NULL, Entry, link));
+        assert(&e1 == c_list_entry(&e1.link, Entry, link));
+
+        /* verify @list is empty */
+
+        assert(!c_list_first_entry(&list, Entry, link));
+        assert(!c_list_last_entry(&list, Entry, link));
+
+        /* link 2 entries and verify list state */
+
+        c_list_link_tail(&list, &e1.link);
+        c_list_link_tail(&list, &e2.link);
+
+        assert(c_list_first_entry(&list, Entry, link)->foo == 1 * 7);
+        assert(c_list_first_entry(&list, Entry, link)->bar == 1 * 11);
+        assert(c_list_last_entry(&list, Entry, link)->foo == 2 * 7);
+        assert(c_list_last_entry(&list, Entry, link)->bar == 2 * 11);
+
+        i = 0;
+        c_list_for_each(iter, &list) {
+                e = c_list_entry(iter, Entry, link);
+                assert(i != 0 || e == &e1);
+                assert(i != 1 || e == &e2);
+                assert(i < 2);
+                ++i;
+        }
+        assert(i == 2);
+
+        /* link 2 more entries */
+
+        c_list_link_tail(&list, &e3.link);
+        c_list_link_tail(&list, &e4.link);
+
+        assert(c_list_first_entry(&list, Entry, link)->foo == 1 * 7);
+        assert(c_list_first_entry(&list, Entry, link)->bar == 1 * 11);
+        assert(c_list_last_entry(&list, Entry, link)->foo == 4 * 7);
+        assert(c_list_last_entry(&list, Entry, link)->bar == 4 * 11);
+
+        i = 0;
+        c_list_for_each(iter, &list) {
+                e = c_list_entry(iter, Entry, link);
+                assert(i != 0 || e == &e1);
+                assert(i != 1 || e == &e2);
+                assert(i != 2 || e == &e3);
+                assert(i != 3 || e == &e4);
+                assert(i < 4);
+                ++i;
+        }
+        assert(i == 4);
+
+        assert(!c_list_is_empty(&list));
+        assert(c_list_is_linked(&e1.link));
+        assert(c_list_is_linked(&e2.link));
+        assert(c_list_is_linked(&e3.link));
+        assert(c_list_is_linked(&e4.link));
+
+        /* remove via safe iterator */
+
+        i = 0;
+        c_list_for_each_safe(iter, safe, &list) {
+                e = c_list_entry(iter, Entry, link);
+                assert(i != 0 || e == &e1);
+                assert(i != 1 || e == &e2);
+                assert(i != 2 || e == &e3);
+                assert(i != 3 || e == &e4);
+                assert(i < 4);
+                ++i;
+                c_list_unlink(&e->link);
+        }
+        assert(i == 4);
+
+        assert(c_list_is_empty(&list));
+        assert(!c_list_is_linked(&e1.link));
+        assert(!c_list_is_linked(&e2.link));
+        assert(!c_list_is_linked(&e3.link));
+        assert(!c_list_is_linked(&e4.link));
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+static void test_entry_gnu(void) {
+        CList list = C_LIST_INIT(list);
+        Entry e1 = { .foo = 1 * 7, .bar = 1 * 11 };
+        Entry e2 = { .foo = 2 * 7, .bar = 2 * 11 };
+        Entry e3 = { .foo = 3 * 7, .bar = 3 * 11 };
+        Entry e4 = { .foo = 4 * 7, .bar = 4 * 11 };
+        Entry *e, *safe;
+        size_t i;
+
+        /* link entries and verify list state */
+
+        c_list_link_tail(&list, &e1.link);
+        c_list_link_tail(&list, &e2.link);
+        c_list_link_tail(&list, &e3.link);
+        c_list_link_tail(&list, &e4.link);
+
+        i = 0;
+        c_list_for_each_entry(e, &list, link) {
+                assert(i != 0 || e == &e1);
+                assert(i != 1 || e == &e2);
+                assert(i != 2 || e == &e3);
+                assert(i != 3 || e == &e4);
+                assert(i < 4);
+                ++i;
+        }
+        assert(i == 4);
+
+        assert(!c_list_is_empty(&list));
+        assert(c_list_is_linked(&e1.link));
+        assert(c_list_is_linked(&e2.link));
+        assert(c_list_is_linked(&e3.link));
+        assert(c_list_is_linked(&e4.link));
+
+        /* remove via safe iterator */
+
+        i = 0;
+        c_list_for_each_entry_safe(e, safe, &list, link) {
+                assert(i != 0 || e == &e1);
+                assert(i != 1 || e == &e2);
+                assert(i != 2 || e == &e3);
+                assert(i != 3 || e == &e4);
+                assert(i < 4);
+                ++i;
+                c_list_unlink(&e->link);
+        }
+        assert(i == 4);
+
+        assert(c_list_is_empty(&list));
+        assert(!c_list_is_linked(&e1.link));
+        assert(!c_list_is_linked(&e2.link));
+        assert(!c_list_is_linked(&e3.link));
+        assert(!c_list_is_linked(&e4.link));
+}
+#else
+static void test_entry_gnu(void) {
+}
+#endif
+
+int main(void) {
+        test_entry();
+        test_entry_gnu();
+        return 0;
+}