Commit acd7679800 for openssl.org

commit acd7679800cd766bd855c4995a3d3799be9e2516
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date:   Sat Jun 20 14:06:09 2026 +0200

    Add unit testing cmocka based framework

    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)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2de5c58009..b10ab496c5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/.github/workflows/coveralls.yml b/.github/workflows/coveralls.yml
index 2a70b2a1bd..34075a3566 100644
--- a/.github/workflows/coveralls.yml
+++ b/.github/workflows/coveralls.yml
@@ -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:
diff --git a/CHANGES.md b/CHANGES.md
index 275dc6c7c8..0c9366b01e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -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.

diff --git a/Configurations/unix-Makefile.tmpl b/Configurations/unix-Makefile.tmpl
index 19455e2b11..f8e44e73f7 100644
--- a/Configurations/unix-Makefile.tmpl
+++ b/Configurations/unix-Makefile.tmpl
@@ -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 {
diff --git a/Configure b/Configure
index d3e6563c07..10a218954e 100755
--- 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) {
diff --git a/INSTALL.md b/INSTALL.md
index f907a89556..20535f2c27 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -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.

diff --git a/test/build.info b/test/build.info
index c40c14e8f1..86174be7c5 100644
--- a/test/build.info
+++ b/test/build.info
@@ -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
index 0000000000..0c43c28a36
--- /dev/null
+++ b/test/recipes/02-test_unit.t
@@ -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
index 0000000000..2576bf0319
--- /dev/null
+++ b/test/unit/.gitignore
@@ -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
index 0000000000..42fd1873d5
--- /dev/null
+++ b/test/unit/README.md
@@ -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
index 0000000000..b31f186dfa
--- /dev/null
+++ b/test/unit/build.info
@@ -0,0 +1 @@
+# Make sure all tests have WRAP otherwise -lcmocka is not added.