]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add unit testing cmocka based framework
authorJakub Zelenka <jakub.zelenka@openssl.foundation>
Sat, 20 Jun 2026 12:06:09 +0000 (14:06 +0200)
committerTomas Mraz <tomas@openssl.foundation>
Thu, 9 Jul 2026 17:38:28 +0000 (19:38 +0200)
This adds the unit testing framework that extends the build so it can
be enabled and tests are built. It is executed as part of the test
using a recipe which executes all unit tests.

The documentation is added with more info about writing the unit tests.

Assisted-by: Claude:claude-opus-4-8
Reviewed-by: Richard Levitte <levitte@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul  9 17:39:14 2026
(Merged from https://github.com/openssl/openssl/pull/30788)

.github/workflows/ci.yml
.github/workflows/coveralls.yml
CHANGES.md
Configurations/unix-Makefile.tmpl
Configure
INSTALL.md
test/build.info
test/recipes/02-test_unit.t [new file with mode: 0644]
test/unit/.gitignore [new file with mode: 0644]
test/unit/README.md [new file with mode: 0644]
test/unit/build.info [new file with mode: 0644]

index 2de5c58009c1ac8e82f0f4819bd3ee69c2f07c26..b10ab496c5001a171689fdd4d027d093b3655053 100644 (file)
@@ -88,12 +88,14 @@ jobs:
       run: git submodule update --init --depth 1 fuzz/corpora
     - name: localegen
       run: sudo locale-gen tr_TR.UTF-8
+    - name: cmocka
+      run: sudo apt-get -y install libcmocka-dev
     - name: fipsvendor
       # Make one fips build use a customized FIPS vendor
       run: echo "FIPS_VENDOR=CI" >> VERSION.dat
     - name: config
       # enable-quic is on by default, but we leave it here to check we're testing the explicit enable somewhere
-      run: CC=gcc ./config --strict-warnings --banner=Configured enable-demos enable-h3demo enable-ec_explicit_curves enable-sslkeylog enable-fips enable-quic enable-lms && perl configdata.pm --dump
+      run: CC=gcc ./config --strict-warnings --banner=Configured enable-demos enable-h3demo enable-ec_explicit_curves enable-sslkeylog enable-fips enable-quic enable-lms enable-unit-tests && perl configdata.pm --dump
     - name: make
       run: make -s -j4
     - name: get cpu info
index 2a70b2a1bdcb7b14bf3089cdaaf438baddf2ee46..34075a356608eb7bd734a453eb30b78fa42c5dc4 100644 (file)
@@ -47,7 +47,7 @@ jobs:
           MATRIX=$(cat << EOF
           [{
               "branch": "master",
-              "extra_config": "enable-fips enable-tfo enable-lms enable-crypto-mdebug"
+              "extra_config": "enable-fips enable-tfo enable-lms enable-crypto-mdebug enable-unit-tests"
             }, {
               "branch": "openssl-4.0",
               "extra_config": "enable-fips enable-tfo enable-lms enable-crypto-mdebug"
@@ -93,7 +93,7 @@ jobs:
       run: |
         sudo apt-get update
         sudo apt-get -yq install lcov
-        sudo apt-get -yq install bison gettext keyutils ldap-utils libldap2-dev libkeyutils-dev python3 python3-paste python3-pyrad slapd tcsh python3-virtualenv virtualenv python3-kdcproxy
+        sudo apt-get -yq install bison gettext keyutils ldap-utils libcmocka-dev libldap2-dev libkeyutils-dev python3 python3-paste python3-pyrad slapd tcsh python3-virtualenv virtualenv python3-kdcproxy
     - name: install Test2::V0 for gost_engine testing
       uses: perl-actions/install-with-cpanm@10d60f00b4073f484fc29d45bfbe2f776397ab3d #v1.7
       with:
index 275dc6c7c8fc68d728583fe4523b24cdc63c22f4..0c9366b01efcee9ca1288ba49dd92446f20c59b5 100644 (file)
@@ -31,6 +31,11 @@ OpenSSL Releases
 
 ### Changes between 4.0 and 4.1 [xx XXX xxxx]
 
+ * Added unit tests setup activated via `enable-unit-tests` option. This works
+   only on platforms with ld `--wrap` support (Linux, BSD).
+
+   *Jakub Zelenka*
+
  * Deprecated the `enable-unit-test` configure option and the
    `SSL_test_functions()` function. Both will be removed in OpenSSL 5.0.
 
index 19455e2b112a2ffa88071c9552b720ac61a7e03d..f8e44e73f7d35d3f262e5d0312a906f90ea28d27 100644 (file)
@@ -504,6 +504,8 @@ BIN_LDFLAGS={- join(' ', $target{bin_lflags} || (),
                          '$(CNF_LDFLAGS)', '$(LDFLAGS)') -}
 BIN_EX_LIBS=$(CNF_EX_LIBS) $(EX_LIBS)
 
+CMOCKA_LIBS={- $config{cmocka_libs} // '' -}
+
 # CPPFLAGS_Q is used for one thing only: to build up buildinf.h
 CPPFLAGS_Q={- $cppflags1 =~ s|([\\"])|\\$1|g;
               $cppflags2 =~ s|([\\"])|\\$1|g;
@@ -2022,11 +2024,13 @@ EOF
           }
       }
       my $wrapflags = '';
+      my $cmocka = '';
       if (defined $unified_info{wraps}->{$args{bin}}) {
           $wrapflags = join(' ',
                             map { "-Wl,--wrap=$_" }
                             @{$unified_info{wraps}->{$args{bin}}});
           $wrapflags = ' ' . $wrapflags;
+          $cmocka = ' $(CMOCKA_LIBS)';
       }
       my $linkflags = join("", map { $_." " } @linkdirs);
       my $linklibs = join("", map { $_." " } @linklibs);
@@ -2048,7 +2052,7 @@ $bin: $deps
        \$\${LDCMD:-$cmd} $cmdflags $linkflags\$(BIN_LDFLAGS)$wrapflags \\
                -o $bin \\
                $objs \\
-               $linklibs\$(BIN_EX_LIBS)
+               $linklibs\$(BIN_EX_LIBS)$cmocka
 EOF
   }
   sub in2script {
index d3e6563c072e5f5a14f56f36ea2aa77e7d2914ee..10a218954eaa1455a1e7d45a6f1027d1a1e31fbc 100755 (executable)
--- a/Configure
+++ b/Configure
@@ -577,6 +577,7 @@ my @disablables_features = (
     "ubsan",
     "ui-console",
     "unit-test",
+    "unit-tests",
     "uplink",
     "weak-ssl-ciphers",
     "zlib-dynamic",
@@ -654,6 +655,7 @@ our %disabled = ( # "what"         => "comment"
                   "trace"               => "default",
                   "ubsan"               => "default",
                   "unit-test"           => "default",
+                  "unit-tests"          => "default",
                   "weak-ssl-ciphers"    => "default",
                   "zlib"                => "default",
                   "zlib-dynamic"        => "default",
@@ -723,7 +725,7 @@ my @disable_cascades = (
 
     "stdio"             => [ "apps", "egd" ],
     "apps"              => [ "tests" ],
-    "tests"             => [ "external-tests" ],
+    "tests"             => [ "external-tests", "unit-tests" ],
     "comp"              => [ "zlib", "brotli", "zstd" ],
     "sm3"               => [ "sm2" ],
     sub { !$disabled{"unit-test"} } => [ "heartbeats" ],
@@ -1115,6 +1117,14 @@ while (@argvcopy)
                         {
                         $withargs{fuzzer_include}=$1;
                         }
+                elsif (/^--with-cmocka-lib=(.*)$/)
+                        {
+                        $withargs{cmocka_lib}=$1;
+                        }
+                elsif (/^--with-cmocka-include=(.*)$/)
+                        {
+                        $withargs{cmocka_include}=$1;
+                        }
                 elsif (/^--with-rand-seed=(.*)$/)
                         {
                         foreach my $x (split(m|,|, $1))
@@ -1917,6 +1927,18 @@ unless ($disabled{winstore}) {
 
 push @{$config{openssl_other_defines}}, "OPENSSL_NO_KTLS" if ($disabled{ktls});
 
+unless ($disabled{"unit-tests"}) {
+    if ($target =~ /^linux/ || $target =~ /^BSD/) {
+        $config{cmocka_includes} =
+            $withargs{cmocka_include} ? [$withargs{cmocka_include}] : [];
+        $config{cmocka_libs} = $withargs{cmocka_lib}
+            ? "-L$withargs{cmocka_lib} -lcmocka"
+            : "-lcmocka";
+    } else {
+        disable('no-wrap-support', 'unit-tests');
+    }
+}
+
 # Get the extra flags used when building shared libraries and modules.  We
 # do this late because some of them depend on %disabled.
 
@@ -3027,6 +3049,14 @@ EOF
     }
 }
 
+# Add cmocka include path to all targets that use WRAP
+if (!$disabled{"unit-tests"} && @{$config{cmocka_includes} // []}) {
+    foreach my $dest (keys %{$unified_info{wraps} // {}}) {
+        push @{$unified_info{includes}->{$dest}},
+             @{$config{cmocka_includes}};
+    }
+}
+
 # For the schemes that need it, we provide the old *_obj configs
 # from the *_asm_obj ones
 foreach (grep /_(asm|aux)_src$/, keys %target) {
index f907a89556aa189328a2f4950b10933add7210f2..20535f2c27cfa7c52f392207081b6f778174af8b 100644 (file)
@@ -427,6 +427,22 @@ The names of the libraries are:
 * brotlidec.lib
 * brotlienc.lib
 
+### with-cmocka-include
+
+    --with-cmocka-include=DIR
+
+The directory for the location of the cmocka include file. This option is only
+necessary if [enable-unit-tests](#enable-unit-tests) is used and the include
+file is not already on the system include path.
+
+### with-cmocka-lib
+
+    --with-cmocka-lib=DIR
+
+The directory containing the cmocka library. This option is only necessary if
+[enable-unit-tests](#enable-unit-tests) is used and the library is not already
+on the system library path.
+
 ### with-zlib-include
 
     --with-zlib-include=DIR
@@ -817,6 +833,12 @@ external test suites are currently supported:
 See the file [test/README-external.md](test/README-external.md)
 for further details.
 
+### enable-unit-tests
+
+Enable building and running unit tests.
+
+This works only on platforms supporting ld `--wrap` option like Linux and BSD.
+
 ### no-filenames
 
 Don't compile in filename and line number information (e.g.  for errors and
@@ -1094,7 +1116,7 @@ The User Interface console method enables text based console prompts.
 
 ### enable-unit-test
 
-Enable additional unit test APIs.
+Enable exposing SSL_test_functions for overwriting ssl_init_wbio_buffer.
 
 This should not typically be used in production deployments.
 
index c40c14e8f14815df0915e016673f8fa9fcbe66a3..86174be7c54a69fdfc4373a598d8c2a49dd44d0a 100644 (file)
@@ -9,6 +9,10 @@ IF[{- !$disabled{hqinterop} -}]
   SUBDIRS=quic-openssl-docker
 ENDIF
 
+IF[{- !$disabled{"unit-tests"} -}]
+  SUBDIRS=unit
+ENDIF
+
 # Auxiliary program source (copied from ../apps/build.info)
 IF[{- $config{target} =~ /^(?:VC-|mingw|BC-)/ -}]
   # It's called 'init', but doesn't have much 'init' in it...
diff --git a/test/recipes/02-test_unit.t b/test/recipes/02-test_unit.t
new file mode 100644 (file)
index 0000000..0c43c28
--- /dev/null
@@ -0,0 +1,45 @@
+#! /usr/bin/env perl
+# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use strict;
+use warnings;
+
+use File::Find;
+use File::Spec::Functions qw(abs2rel);
+
+use OpenSSL::Test qw(:DEFAULT bldtop_dir);
+use OpenSSL::Test::Utils;
+
+setup("test_unit");
+
+my $unit_dir = bldtop_dir('test', 'unit');
+
+my @tests = ();
+if (-d $unit_dir) {
+    find({
+        wanted => sub {
+            return unless -f $_ && -x $_;
+            return unless $_ =~ m|/test_[^/]*$|;
+            return if $_ =~ m|\.\w+$|;
+            push @tests, $_;
+        },
+        no_chdir => 1,
+    }, $unit_dir);
+}
+
+@tests = sort @tests;
+
+plan skip_all => "No unit tests built (enable-unit-tests not set?)"
+    unless @tests;
+
+plan tests => scalar @tests;
+
+foreach my $test_bin (@tests) {
+    my $name = abs2rel($test_bin, $unit_dir);
+    ok(run(cmd([$test_bin])), "unit: $name");
+}
diff --git a/test/unit/.gitignore b/test/unit/.gitignore
new file mode 100644 (file)
index 0000000..2576bf0
--- /dev/null
@@ -0,0 +1,13 @@
+*
+!*/
+!*.c
+!*.h
+!*.inc
+!*.pl
+!*.t
+!*.txt
+!*.cnf
+!*.pem
+!build.info
+!.gitignore
+!README*
diff --git a/test/unit/README.md b/test/unit/README.md
new file mode 100644 (file)
index 0000000..42fd187
--- /dev/null
@@ -0,0 +1,558 @@
+OpenSSL Unit Tests
+==================
+
+This directory holds the OpenSSL *unit* tests. Their purpose is to test a
+single function, or a small group of related functions, in isolation by
+replacing the other functions it calls with mocks. This makes it possible to
+test logic that is otherwise hard to reach: error and failure paths of
+dependencies, branches that depend on the exact arguments passed to a
+collaborator, or code whose real dependencies would require network access,
+specific hardware, or elaborate setup. Each test can then drive the function
+under test through a precise, fully controlled sequence of calls and return
+values, and assert on exactly how it interacts with its surroundings.
+
+Unit tests are not meant to replace the broader, integration-style testing that
+makes up most of `test/`, and they are not the right tool for everything. They
+are used for selected, self-contained pieces of the library where the mocking
+boundary is clean and the payoff is high, the BIO layer being the main example.
+Most code continues to be tested through the ordinary recipes (typically built
+on `testutil`, and generally without mocking).
+
+Requirements
+------------
+
+The tests are built on [cmocka], a lightweight C unit-testing framework, and
+rely on the GNU/BSD linker's `--wrap` option to intercept calls into the
+function under test's dependencies. Because `--wrap` is required, unit tests
+are only built on platforms that support it (Linux and BSD only at present),
+and only when the build is configured with `enable-unit-tests`.
+
+[cmocka]: https://cmocka.org/
+
+Tests must build and run against cmocka 1.1.5, as that is still the version
+shipped by some currently supported enterprise and LTS distributions. Do not
+rely on APIs introduced in cmocka 2.x, since a test that needs them cannot be
+built on those systems; when in doubt, check against the 1.1.5 headers rather
+than the latest online documentation.
+
+Building and Running
+--------------------
+
+Unit tests are disabled by default. To build them, configure with
+`enable-unit-tests` and make sure the cmocka development files are installed
+on the system:
+
+```console
+$ sudo apt-get install libcmocka-dev          # Debian/Ubuntu
+$ ./config enable-unit-tests
+$ make
+```
+
+If cmocka is installed in a non-standard location, point the build at it with:
+
+```console
+$ ./config enable-unit-tests \
+      --with-cmocka-include=/path/to/include \
+      --with-cmocka-lib=/path/to/lib
+```
+
+On a platform without `--wrap` support, `enable-unit-tests` is silently turned
+off during configuration; the rest of the build proceeds normally.
+
+The whole unit-test suite runs as part of the normal test target:
+
+```console
+$ make test
+```
+
+It is gathered under a single recipe, so it can also be run on its own:
+
+```console
+$ make test TESTS=test_unit
+```
+
+The recipe discovers every executable named `test_*` under the build's
+`test/unit` tree and runs each one, so a newly added test binary is picked up
+automatically once it builds. Each binary reports its results in TAP, which the
+harness consumes directly.
+
+Because each test is an ordinary standalone executable, it can also be run
+directly, which is convenient when debugging a single failure under a debugger:
+
+```console
+$ gdb test/unit/crypto/foo/test_bar
+```
+
+Running the binary on its own prints its TAP output to the terminal and makes
+it straightforward to set breakpoints in a specific test, mock, or in the
+function under test.
+
+For debugging it is worth configuring the build with `--debug` as well, e.g.
+`./config enable-unit-tests --debug`. A normal build is optimized, which makes
+stepping through code and inspecting variables awkward; `--debug` lowers the
+optimization level and adds debug information, giving a much more predictable
+experience under gdb.
+
+Anatomy of a Unit Test
+----------------------
+
+A unit test is a single C source file laid out under `test/unit/` in a path
+that mirrors the location of the code it exercises. For example, code that
+lives in `crypto/foo/bar.c` is tested by `test/unit/crypto/foo/test_bar.c`. The
+file is self-contained: it provides its own `main()`, registers a list of test
+cases, and runs them as a cmocka group.
+
+The body of a test file is organised into a few clearly separated sections,
+conventionally introduced by short comments, in this order:
+
+  * the `__wrap_*` mock implementations (`/* wraps */`),
+  * thin `expect_*` helpers that program each mock (`/* expectations */`),
+  * any shared helpers (fake methods, accessors, reset routines),
+  * the `setup`/`teardown` fixtures,
+  * the test functions themselves, and
+  * `main()`, which builds the `CMUnitTest` array and runs it.
+
+Keeping these sections in this order and clearly labelled makes a test file
+predictable to read and easy to extend.
+
+### main() and the test list
+
+`main()` declares a `struct CMUnitTest` array, selects TAP output, and runs the
+group:
+
+```c
+int main(void)
+{
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test(test_something_simple),
+        cmocka_unit_test_setup_teardown(test_with_fixture, setup, teardown),
+    };
+
+    cmocka_set_message_output(CM_OUTPUT_TAP);
+
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}
+```
+
+Always select `CM_OUTPUT_TAP` so the harness can parse the results. Use
+`cmocka_unit_test()` for tests that need no per-test fixture, and
+`cmocka_unit_test_setup_teardown()` when a test needs a fresh object built
+before it and cleaned up after. The last two arguments to
+`cmocka_run_group_tests()` are an optional group-level setup and teardown, run
+once before and after the whole group; pass `NULL` when they are not needed.
+
+It is common to wrap the registration macros in a short local macro when most
+tests share the same fixture, e.g.
+
+```c
+#define MY_TEST(name) \
+    cmocka_unit_test_setup_teardown(name, setup, teardown)
+```
+
+### A test function
+
+Each test is a function with the signature `void (void **state)`. The `state`
+argument carries whatever a `setup` fixture stored there; tests that do not use
+it should cast it to `void` to silence warnings:
+
+```c
+static void test_addr_family(void **state)
+{
+    BIO_ADDR ap;
+
+    (void)state;
+    memset(&ap, 0, sizeof(ap));
+    ap.sa.sa_family = AF_INET;
+    assert_int_equal(BIO_ADDR_family(&ap), AF_INET);
+}
+```
+
+Assertions come from cmocka: `assert_int_equal`, `assert_ptr_equal`,
+`assert_true`, `assert_false`, `assert_null`, `assert_non_null`,
+`assert_string_equal`, `assert_memory_equal`, and friends. A failing assertion
+aborts the current test and marks it failed without disturbing the others.
+
+How the Expectation Mechanism Works
+-----------------------------------
+
+Before writing mocks it helps to understand what cmocka is actually doing,
+because the model is simple once stated plainly and it makes the rest of the
+API obvious.
+
+For each `(function, parameter)` pair cmocka keeps an internal queue. The
+`expect_*()` macros, called from the test, push values onto these queues. The
+`check_expected()` macro, called from inside the mock, pops the next value and
+compares it against the argument the mock actually received; a mismatch fails
+the test. Return values work the same way: `will_return()` pushes a value from
+the test, and `mock_type()`/`mock_ptr_type()` pops it inside the mock to use as
+the return value. Call accounting is analogous: `expect_function_call()` pushes
+an expected call and `function_called()` consumes one.
+
+So a test programs, in order, the calls it expects the function under test to
+make, and the mocks consume those programmed entries as the calls actually
+happen. Entries are consumed in the order they were queued, which is why the
+`expect_*`/`will_return` calls in a test must be written in the same order the
+function under test will call its dependencies. At the end of the test cmocka
+fails if any queued entry was never consumed, or if a mock is called with
+nothing queued for it. This is what turns the expected interaction sequence
+into a checked specification rather than a loose suggestion.
+
+Because each entry covers a single call, a function that is expected to be
+called more than once is programmed by pushing the entries that many times, in
+the order the calls will occur:
+
+```c
+/* the SUT is expected to call BIO_socket twice */
+expect_BIO_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, INVALID_SOCKET);
+expect_BIO_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, FAKE_SOCKET);
+```
+
+cmocka also offers `_count` variants (such as `will_return_count()`,
+`expect_value_count()` and `expect_function_calls()`) that program one entry
+for a given number of calls in a single statement. These are not just a
+shorthand for repeating the macro: they carry a different ordering semantics.
+Repeating `expect_function_call()` pins each call's position in the overall
+sequence, so any other expected call programmed between two of them must
+actually occur between them. `expect_function_calls(f, 2)`, by contrast, only
+requires that `f` is called twice somewhere; other calls may fall before,
+after, or in between without failing the test. Prefer repeating the plain
+macros when the interleaving matters (which is the common case), and reach for
+the count variants only when a function is genuinely called many times and its
+position relative to the others is not what the test is checking.
+
+Two consequences are worth keeping in mind:
+
+  * **cmocka has nothing to do with `--wrap`.** The expectation machinery is
+    just these queues plus the `check_expected`/`mock`/`function_called`
+    macros. It works in any function whose body calls them. `--wrap` is merely
+    the linker trick OpenSSL uses to *substitute* a dependency with a mock; the
+    two are independent. The same `expect_*` style is used below for purpose-
+    built fake objects that are never wrapped at all (see Fixtures).
+
+  * Because matching is per parameter and in order, a mock must call
+    `check_expected()` for exactly the parameters the test programs with
+    `expect_*()`, and the `will_return`/`mock_type` counts must balance.
+
+Mocking Dependencies with --wrap
+--------------------------------
+
+The core technique is link-time function interception. When a binary is linked
+with `-Wl,--wrap=foo`, every call to `foo` is redirected to a function named
+`__wrap_foo`, and the original is still reachable as `__real_foo`. This lets a
+test replace the function under test's dependencies with mocks that record how
+they were called and return whatever the test dictates.
+
+### Declaring the wraps
+
+The set of wrapped symbols for a test binary is declared in the `build.info`
+file (see below). For each wrapped symbol the test file provides a
+`__wrap_<name>` function with exactly the same signature as the real one. A
+prototype is also needed to satisfy `-Wmissing-prototypes`:
+
+```c
+int __wrap_BIO_socket(int domain, int socktype, int protocol, int options);
+
+int __wrap_BIO_socket(int domain, int socktype, int protocol, int options)
+{
+    function_called();
+    check_expected(domain);
+    check_expected(socktype);
+    check_expected(protocol);
+    check_expected(options);
+    return mock_type(int);
+}
+```
+
+A typical mock does three things:
+
+  * `function_called()` records that the function was invoked, balanced against
+    the test's `expect_function_call()`.
+  * `check_expected(param)` (or `check_expected_ptr(param)` for pointers)
+    verifies the argument against the value the test queued. Use the `_ptr`
+    variant for pointer parameters.
+  * `mock_type(T)` (or `mock_ptr_type(T)` for pointers) returns the value the
+    test queued for this call. A `void` mock omits this.
+
+Mocks may also have deliberate side effects when the real function would
+produce one that the code under test depends on. For example, a function that
+fills a caller-supplied buffer should have its mock write into that buffer, and
+a fatal-error reporter that the code expects to flip a state flag should do so:
+
+```c
+int __wrap_ssl_fill_hello_random(SSL_CONNECTION *s, int server,
+    unsigned char *field, size_t len, DOWNGRADE dgrd)
+{
+    function_called();
+    check_expected_ptr(s);
+    check_expected(server);
+    check_expected_ptr(field);
+    check_expected(len);
+    check_expected(dgrd);
+
+    if (field != NULL)
+        memset(field, 0xAB, len);
+
+    return mock_type(int);
+}
+```
+
+Keep these side effects minimal and confined to what the function under test
+genuinely observes; the goal is to reproduce the contract of the real function,
+not to re-implement it.
+
+### Programming the mocks: expectations
+
+Rather than scatter `expect_function_call`/`expect_value`/`will_return` calls
+through every test, wrap each mock in a small `expect_<name>` helper that takes
+the expected arguments and the return value. This keeps the tests readable and,
+crucially, gives a single place to update when a function's signature or call
+contract changes:
+
+```c
+static void expect_BIO_socket(int domain, int socktype, int protocol,
+    int options, int rc)
+{
+    expect_function_call(__wrap_BIO_socket);
+    expect_value(__wrap_BIO_socket, domain, domain);
+    expect_value(__wrap_BIO_socket, socktype, socktype);
+    expect_value(__wrap_BIO_socket, protocol, protocol);
+    expect_value(__wrap_BIO_socket, options, options);
+    will_return(__wrap_BIO_socket, rc);
+}
+```
+
+The most useful cmocka primitives here are:
+
+  * `expect_function_call(f)`: expect one call to `f`. Pair every
+    `function_called()` in a mock with one of these.
+  * `expect_value(f, param, value)`: the argument must equal `value`. This is
+    consumed by `check_expected(param)`.
+  * `expect_any(f, param)`: the argument may be anything; still consumed by
+    `check_expected(param)`, so it must be present whenever the mock checks
+    that parameter.
+  * `will_return(f, value)`: queue a return value for the next call,
+    retrieved by `mock_type`/`mock_ptr_type`. Queue several in order if the
+    mock pulls more than one value (e.g. a return code followed by an
+    out-parameter payload).
+
+When a mock conditionally retrieves a second value, the matching expectation
+must queue it under the same condition, so the queues stay aligned:
+
+```c
+static void expect_BIO_lookup(BIO_ADDRINFO *res, int rc)
+{
+    expect_function_call(__wrap_BIO_lookup);
+    expect_any(__wrap_BIO_lookup, host);
+    expect_any(__wrap_BIO_lookup, service);
+    expect_value(__wrap_BIO_lookup, lookup_type, BIO_LOOKUP_SERVER);
+    expect_any(__wrap_BIO_lookup, family);
+    expect_any(__wrap_BIO_lookup, socktype);
+    will_return(__wrap_BIO_lookup, rc);
+    if (rc == 1)
+        will_return(__wrap_BIO_lookup, res);
+}
+```
+
+### Writing a test against the mocks
+
+With the helpers in place, a test reads as: arrange the object, declare the
+expected sequence of calls, invoke the function under test, and assert on the
+result and any observable state.
+
+```c
+static void test_socket_then_listen_fails(void **state)
+{
+    BIO *bio = *state;
+
+    expect_BIO_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, FAKE_SOCKET);
+    expect_BIO_listen(FAKE_SOCKET, &expected_addr, 0, 0);
+    expect_BIO_closesocket(FAKE_SOCKET, 0);
+
+    assert_true(BIO_do_accept(bio) <= 0);
+}
+```
+
+If the function under test makes a call that was not programmed, or fails to
+make one that was, or passes an argument that does not match, cmocka fails the
+test and reports the mismatch.
+
+Fixtures and Real Fake Objects
+------------------------------
+
+A `setup` function allocates or initialises whatever object the test needs and
+stores it through `*state`; the matching `teardown` releases it. Returning
+non-zero from either aborts the test as an error.
+
+```c
+static int setup(void **state)
+{
+    BIO *bio = BIO_new(BIO_s_accept());
+
+    assert_non_null(bio);
+    *state = bio;
+    return 0;
+}
+
+static int teardown(void **state)
+{
+    if (*state != NULL)
+        BIO_free(*state);
+    return 0;
+}
+```
+
+A group-level setup/teardown (the last two arguments to
+`cmocka_run_group_tests`) is the place for one-time work shared by every test,
+such as initialising static fixtures used across the file.
+
+Two practical cautions apply when fixtures interact with wrapped functions:
+
+  * Teardown can itself trigger wrapped calls. If freeing the object under test
+    would invoke a wrapped function (for instance, closing a socket), reset the
+    relevant fields to safe sentinels before the free so no *unexpected* mock
+    call is made, or program the expectation for it. A common pattern is a
+    small `reset_for_teardown()` helper called at the end of any test that left
+    such state behind.
+
+  * It is often cleaner to drive an object through its public interface with a
+    *real* minimal fake than to mock everything. For example, building a small
+    fake `BIO_METHOD` whose read/write callbacks are themselves cmocka mocks
+    (using the same `function_called`/`check_expected`/`mock_type` machinery,
+    even though nothing is wrapped) lets a test exercise the forwarding logic
+    of the object under test without wrapping low-level syscalls. Choose
+    whichever boundary keeps the test focused on the function actually under
+    examination.
+
+Conditional Compilation
+------------------------
+
+Mirror the `#ifdef`/`#ifndef` guards of the code under test. If a function only
+exists under a build option, guard both the test function and its registration
+in `main()` with the same condition, so the suite still builds in every
+configuration:
+
+```c
+#ifndef OPENSSL_NO_UNIX_SOCK
+static void test_addr_make_unix(void **state)
+{
+    ...
+}
+#endif
+
+int main(void)
+{
+    const struct CMUnitTest tests[] = {
+#ifndef OPENSSL_NO_UNIX_SOCK
+        cmocka_unit_test(test_addr_make_unix),
+#endif
+        ...
+    };
+    ...
+}
+```
+
+When an entire test file only makes sense under some option, guard the whole
+body and provide a trivial `main()` for the disabled case so the binary still
+links and the recipe still finds something to run:
+
+```c
+#ifndef OPENSSL_NO_SOCK
+
+/* ... the tests ... */
+
+#else
+
+int main(void)
+{
+    return 0;
+}
+
+#endif
+```
+
+Wiring a New Test into the Build
+--------------------------------
+
+Test binaries are declared in `test/unit/build.info`. A unit test that wraps no
+symbols would not be linked against cmocka, so **every** unit test must declare
+at least one `WRAP[]` entry: this is what causes both the `--wrap` link flags
+and `-lcmocka` to be added for that binary. The directives needed per test are
+`PROGRAMS`, `SOURCE`, `INCLUDE`, `DEPEND`, and `WRAP`:
+
+```text
+PROGRAMS{noinst}=crypto/foo/test_bar
+SOURCE[crypto/foo/test_bar]=crypto/foo/test_bar.c
+INCLUDE[crypto/foo/test_bar]=../../include ../../include/internal \
+                             ../../crypto/foo
+DEPEND[crypto/foo/test_bar]=../../libcrypto.a
+WRAP[crypto/foo/test_bar]=BIO_socket BIO_listen BIO_closesocket
+```
+
+Notes:
+
+  * `PROGRAMS{noinst}` marks the binary as not installed.
+  * `INCLUDE[]` lists the directories needed to reach the headers the test
+    uses, including any internal directory that declares the types or the
+    function under test. The cmocka include path is added automatically to
+    every target that has a `WRAP[]` entry, so it need not be listed.
+  * `DEPEND[]` links the appropriate static libraries: `../../libcrypto.a`,
+    and `../../libssl.a` as well for libssl code.
+  * `WRAP[]` is the whitespace-separated list of symbols to intercept; it may
+    be split over several lines with trailing backslashes. List exactly the
+    dependencies the test mocks.
+
+Because the recipe discovers binaries by name, no change to the Perl recipe is
+needed; building the new `test_*` binary is enough for it to run under
+`test_unit`.
+
+Generating Mock Stubs with mkwraps.pl
+-------------------------------------
+
+Writing the `__wrap_*` and `expect_*` boilerplate by hand for a long `WRAP[]`
+list is tedious and error-prone, so the helper script `util/mkwraps.pl`
+generates a first draft from the `build.info` declaration. It reads the
+`WRAP[<target>]` list, searches the headers under the target's `INCLUDE[]`
+directories for each function's prototype, and emits matching wrap functions
+and expectation helpers.
+
+```console
+$ ./util/mkwraps.pl --build-info test/unit/build.info \
+                    --target crypto/foo/test_bar
+```
+
+Useful options:
+
+  * `--mode wraps|expects|both`: emit only the `__wrap_*` functions, only the
+    `expect_*` helpers, or both (the default).
+  * `--include DIR`: add an extra header search directory beyond those in
+    `INCLUDE[]`. Cumulative.
+  * `--output FILE`: write to a file instead of standard output.
+  * `--verbose`: report progress and where each prototype was found.
+
+The output is a *starting point*, not a finished test. The generated mocks call
+`function_called()`, check every parameter, and return a `mock_type` value, but
+any real behaviour still has to be added by hand: side effects on
+out-parameters, variadic forwarding, conditional `will_return` payloads, and
+the use of internal headers for opaque types. Generated `#include` lines and
+parameter checks frequently need adjusting. Treat the script as a way to skip
+the mechanical typing, then review and edit every generated function.
+
+Conventions
+-----------
+
+Unit tests follow the usual OpenSSL C coding style (enforced via
+clang-format), so it is not repeated here. A few conventions specific to unit
+tests keep them consistent and maintainable:
+
+  * Order the file as wraps, expectations, helpers, fixtures, tests, then
+    `main()`, with the short section-header comments shown above.
+  * Name test functions `test_<area>_<behaviour>` so the suite reads as a list
+    of behaviours, and add a brief comment on any test whose setup or expected
+    sequence is not obvious from the name.
+  * Give every mock a matching `expect_<name>` helper and route all programming
+    of that mock through it rather than inlining `expect_value`/`will_return`
+    in the tests, so a change to a function's contract is fixed in one place.
+  * Keep each test focused on one behaviour, and prefer several small tests
+    over one test with many branches.
+  * Always emit TAP output, and always reset fixture state that would otherwise
+    cause an unexpected wrapped call during teardown.
diff --git a/test/unit/build.info b/test/unit/build.info
new file mode 100644 (file)
index 0000000..b31f186
--- /dev/null
@@ -0,0 +1 @@
+# Make sure all tests have WRAP otherwise -lcmocka is not added.