Skip to content

Commit cb8b83a

Browse files
committed
Add pre-processing script
1 parent f9a3a57 commit cb8b83a

16 files changed

Lines changed: 781 additions & 252 deletions

.github/workflows/build-and-test.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ jobs:
3232
repository: wolfssl/wolfssl
3333
path: wolfssl
3434

35+
# Verify the committed wh_test_list.c matches what gen_test_list.py
36+
# would produce for the current set of WH_TEST_* annotations. If a
37+
# contributor adds/renames/removes a test without regenerating, this
38+
# fails with a diff and a regeneration hint.
39+
- name: Verify wh_test_list.c is up to date
40+
run: |
41+
cd test-refactor
42+
python3 gen_test_list.py --output wh_test_list.c misc server client-server
43+
if ! git diff --exit-code wh_test_list.c; then
44+
echo "::error::wh_test_list.c is stale. Regenerate with:"
45+
echo "::error:: cd test-refactor && python3 gen_test_list.py --output wh_test_list.c misc server client-server"
46+
exit 1
47+
fi
48+
3549
- name: Build and test refactor
3650
run: cd test-refactor/posix && make clean && make -j WOLFSSL_DIR=../../wolfssl && make run
3751

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ tools/static-analysis/reports/
2020
*.gcda
2121
*.gcno
2222
coverage/
23+
24+
# Test driver log (automake-style; produced by `make run` in test-refactor)
25+
test-suite.log

test-refactor/README.md

Lines changed: 243 additions & 82 deletions
Large diffs are not rendered by default.

test-refactor/client-server/wh_test_crypto.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "wolfhsm/wh_client.h"
4242

4343
#include "wh_test_common.h"
44+
#include "wh_test_list.h"
4445

4546
#ifndef NO_SHA256
4647
WH_TEST_CLIENT int whTest_CryptoSha256(whClientContext* ctx)
@@ -95,7 +96,7 @@ WH_TEST_CLIENT int whTest_CryptoSha256(whClientContext* ctx)
9596

9697

9798
#if !defined(NO_AES) && defined(HAVE_AES_CBC)
98-
WH_TEST_CLIENT int whTestCrypto_Aes(whClientContext* ctx)
99+
WH_TEST_CLIENT int whTest_CryptoAes(whClientContext* ctx)
99100
{
100101
int devId = WH_DEV_ID;
101102
int ret = 0;
@@ -161,7 +162,7 @@ WH_TEST_CLIENT int whTestCrypto_Aes(whClientContext* ctx)
161162

162163

163164
#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY)
164-
WH_TEST_CLIENT int whTestCrypto_Ecc256(whClientContext* ctx)
165+
WH_TEST_CLIENT int whTest_CryptoEcc256(whClientContext* ctx)
165166
{
166167
int devId = WH_DEV_ID;
167168
int ret = 0;

test-refactor/client-server/wh_test_echo.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@
3131
#include "wolfhsm/wh_client.h"
3232

3333
#include "wh_test_common.h"
34+
#include "wh_test_list.h"
3435

3536
#define REPEAT_COUNT 10
3637

3738
/*
3839
* Echo a message to the server and verify the response
3940
* matches. Repeats several times with different payloads.
4041
*/
41-
WH_TEST_CLIENT int test_echo(whClientContext* ctx)
42+
WH_TEST_CLIENT int whTest_Echo(whClientContext* ctx)
4243
{
4344
char send_buf[WOLFHSM_CFG_COMM_DATA_LEN];
4445
char recv_buf[WOLFHSM_CFG_COMM_DATA_LEN];

test-refactor/client-server/wh_test_server_info.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@
3131
#include "wolfhsm/wh_message_comm.h"
3232

3333
#include "wh_test_common.h"
34+
#include "wh_test_list.h"
3435

3536

3637
/*
3738
* Query server info and verify the response contains
3839
* valid data.
3940
*/
40-
WH_TEST_CLIENT int test_server_info(whClientContext* ctx)
41+
WH_TEST_CLIENT int whTest_ServerInfo(whClientContext* ctx)
4142
{
4243
uint8_t version[WH_INFO_VERSION_LEN + 1] = {0};
4344
uint8_t build[WH_INFO_VERSION_LEN + 1] = {0};

test-refactor/gen_test_list.py

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
#!/usr/bin/env python3
2+
# Copyright (C) 2026 wolfSSL Inc.
3+
#
4+
# This file is part of wolfHSM.
5+
#
6+
# wolfHSM is free software; you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation; either version 3 of the License, or
9+
# (at your option) any later version.
10+
"""
11+
Generate wh_test_list.c from WH_TEST_{MISC,CLIENT,SERVER} annotations.
12+
13+
For every test found it emits:
14+
- A `WH_TEST_DECL(name);` line, which expands (see
15+
wh_test_list.h) to a forward prototype plus a weak skip
16+
implementation returning WH_TEST_SKIPPED. When the real
17+
test's feature gate is on, its strong definition overrides
18+
this stub at link time; otherwise the stub wins and the
19+
test surfaces as SKIPPED at runtime.
20+
- A row in whTests[].
21+
22+
Preprocessor conditionals around the annotation are deliberately
23+
ignored: the link-time weak override handles the gating. All the
24+
script needs is the set of annotated function names and the group
25+
each belongs to.
26+
27+
Usage:
28+
gen_test_list.py --output <path> <source-dir>...
29+
"""
30+
31+
import argparse
32+
import os
33+
import re
34+
import sys
35+
36+
ANNOTATION_RE = re.compile(
37+
r'^\s*WH_TEST_(MISC|CLIENT|SERVER)\s+int\s+(\w+)\s*\(',
38+
re.MULTILINE,
39+
)
40+
41+
# Strip // line comments and /* ... */ block comments so an
42+
# annotation appearing in commented-out code doesn't register a
43+
# phantom test. Strings aren't stripped; an annotation inside a
44+
# string literal would be an exotic footgun we're not solving.
45+
LINE_COMMENT_RE = re.compile(r'//[^\n]*')
46+
BLOCK_COMMENT_RE = re.compile(r'/\*.*?\*/', re.DOTALL)
47+
48+
# Each group emits its own whTests<Group>[] registry in the
49+
# generated file. Order here fixes the section ordering in
50+
# wh_test_list.c.
51+
GROUPS = [
52+
('MISC', 'whTestsMisc', 'whTestsMiscCount'),
53+
('SERVER', 'whTestsServer', 'whTestsServerCount'),
54+
('CLIENT', 'whTestsClient', 'whTestsClientCount'),
55+
]
56+
57+
58+
def strip_comments(src):
59+
src = BLOCK_COMMENT_RE.sub('', src)
60+
src = LINE_COMMENT_RE.sub('', src)
61+
return src
62+
63+
64+
def scan_file(path):
65+
"""Return [(group, fn_name)] from one source file."""
66+
with open(path, 'r') as f:
67+
src = strip_comments(f.read())
68+
return [(m.group(1), m.group(2)) for m in ANNOTATION_RE.finditer(src)]
69+
70+
71+
def discover(dirs):
72+
"""Walk dirs, scan .c files, return sorted unique tests with stable group-major order."""
73+
found = []
74+
seen_names = {}
75+
for d in dirs:
76+
for root, _, files in os.walk(d):
77+
for name in sorted(files):
78+
if not name.endswith('.c'):
79+
continue
80+
path = os.path.join(root, name)
81+
for group, fn in scan_file(path):
82+
if fn in seen_names:
83+
prev = seen_names[fn]
84+
sys.stderr.write(
85+
"gen_test_list.py: duplicate test name '{}' "
86+
"(in {} and {})\n".format(fn, prev, path))
87+
sys.exit(1)
88+
seen_names[fn] = path
89+
found.append((group, fn, path))
90+
# Stable order: group (misc/server/client), then discovery order.
91+
group_order = {g[0]: i for i, g in enumerate(GROUPS)}
92+
found.sort(key=lambda t: (group_order[t[0]], t[2], t[1]))
93+
return found
94+
95+
96+
HEADER = """\
97+
/*
98+
* Copyright (C) 2026 wolfSSL Inc.
99+
*
100+
* This file is part of wolfHSM.
101+
*
102+
* wolfHSM is free software; you can redistribute it and/or modify
103+
* it under the terms of the GNU General Public License as published by
104+
* the Free Software Foundation; either version 3 of the License, or
105+
* (at your option) any later version.
106+
*
107+
* wolfHSM is distributed in the hope that it will be useful,
108+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
109+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
110+
* GNU General Public License for more details.
111+
*
112+
* You should have received a copy of the GNU General Public License
113+
* along with wolfHSM. If not, see <http://www.gnu.org/licenses/>.
114+
*/
115+
116+
/*
117+
*************************************************************************
118+
* wh_test_list.c -- GENERATED BY gen_test_list.py. DO NOT EDIT.
119+
*************************************************************************
120+
*/
121+
122+
/*
123+
* Registry of every test function found via WH_TEST_{MISC,
124+
* CLIENT,SERVER} annotations. Each discovered test gets a weak
125+
* stub that returns WH_TEST_SKIPPED; the real test, when
126+
* compiled in, provides a strong symbol that the linker picks
127+
* instead.
128+
*/
129+
130+
#include "wh_test_list.h"
131+
132+
"""
133+
134+
135+
def render(tests):
136+
out = [HEADER]
137+
138+
# Declarations and weak skip implementations -- one per test.
139+
# WH_TEST_DECL expands to a forward prototype plus a weak
140+
# stub returning WH_TEST_SKIPPED; the real test's strong
141+
# definition overrides it at link time when the feature gate
142+
# is on.
143+
out.append(
144+
'/* Test declarations and weak skip implementations. '
145+
'*/\n')
146+
for group, fn, _ in tests:
147+
out.append('WH_TEST_DECL({name});\n'.format(name=fn))
148+
out.append('\n')
149+
150+
# Per-group registration tables.
151+
by_group = {g[0]: [] for g in GROUPS}
152+
for group, fn, _ in tests:
153+
by_group[group].append(fn)
154+
155+
for i, (group, array, count) in enumerate(GROUPS):
156+
fns = by_group[group]
157+
if i > 0:
158+
out.append('\n')
159+
if not fns:
160+
# C forbids zero-length arrays at file scope; emit a
161+
# single NULL placeholder and a hardcoded count of 0
162+
# so the runner treats this group as empty.
163+
out.append(
164+
'const whTestCase {array}[] = '
165+
'{{ {{ NULL, NULL }} }};\n'.format(array=array))
166+
out.append('const size_t {count} = 0;\n'.format(count=count))
167+
continue
168+
out.append('const whTestCase {array}[] = {{\n'.format(array=array))
169+
for fn in fns:
170+
out.append(
171+
' {{ "{name}", {name} }},\n'.format(name=fn))
172+
out.append('};\n')
173+
out.append(
174+
'const size_t {count} = '
175+
'sizeof({array}) / sizeof({array}[0]);\n'.format(
176+
array=array, count=count))
177+
178+
return ''.join(out)
179+
180+
181+
def main():
182+
ap = argparse.ArgumentParser()
183+
ap.add_argument('--output', required=True,
184+
help='path to wh_test_list.c to (re)generate')
185+
ap.add_argument('sources', nargs='+',
186+
help='test source directories to scan')
187+
args = ap.parse_args()
188+
189+
tests = discover(args.sources)
190+
if not tests:
191+
sys.stderr.write('gen_test_list.py: no WH_TEST_* annotations found\n')
192+
sys.exit(1)
193+
194+
new_content = render(tests)
195+
196+
# Only rewrite when content actually changes, so make doesn't
197+
# rebuild the world on every invocation.
198+
try:
199+
with open(args.output, 'r') as f:
200+
if f.read() == new_content:
201+
return
202+
except IOError:
203+
pass
204+
205+
with open(args.output, 'w') as f:
206+
f.write(new_content)
207+
208+
209+
if __name__ == '__main__':
210+
main()

test-refactor/misc/wh_test_flash_ramsim.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#include "wolfhsm/wh_flash_ramsim.h"
3434

3535
#include "wh_test_common.h"
36-
#include "wh_test_flash_ramsim.h"
36+
#include "wh_test_list.h"
3737

3838
#define TEST_FLASH_SIZE (1024 * 1024)
3939
#define TEST_SECTOR_SIZE (4096)
@@ -54,7 +54,7 @@ static void _fillTestData(uint8_t* buf, uint32_t size,
5454
* Verify that write-lock prevents erase and program, and that
5555
* unlock restores access.
5656
*/
57-
WH_TEST_MISC int test_flash_write_lock(void* ctx)
57+
WH_TEST_MISC int whTest_FlashWriteLock(void* ctx)
5858
{
5959
int ret;
6060
whFlashRamsimCtx fctx;
@@ -120,7 +120,7 @@ WH_TEST_MISC int test_flash_write_lock(void* ctx)
120120
* Erase every sector, program every page with known data,
121121
* verify, then erase again and blank-check.
122122
*/
123-
WH_TEST_MISC int test_flash_erase_program_verify(void* ctx)
123+
WH_TEST_MISC int whTest_FlashEraseProgramVerify(void* ctx)
124124
{
125125
int ret;
126126
whFlashRamsimCtx fctx;

test-refactor/misc/wh_test_nvm_flash.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
#include "wolfhsm/wh_nvm_flash.h"
3838

3939
#include "wh_test_common.h"
40-
#include "wh_test_nvm_flash.h"
40+
#include "wh_test_list.h"
4141

4242
#define NVM_FLASH_SIZE (1024 * 1024)
4343
#define NVM_FLASH_SECTOR_SZ (4096)
@@ -96,7 +96,7 @@ static void _setup(void)
9696
* Exercises flash unit program/read/erase/blank-check
9797
* and byte-level read/write including unaligned access.
9898
*/
99-
WH_TEST_MISC int test_flash_unit_ops(void* ctx)
99+
WH_TEST_MISC int whTest_FlashUnitOps(void* ctx)
100100
{
101101
whTestNvmFlashCtx* c = &_ctx;
102102
uint8_t write_bytes[8] = {
@@ -224,7 +224,7 @@ static int _addAndCheck(const whNvmCb* cb, void* context,
224224
* Add objects, overwrite, reclaim, destroy, verify
225225
* data integrity throughout.
226226
*/
227-
WH_TEST_MISC int test_nvm_add_overwrite_destroy(void* ctx)
227+
WH_TEST_MISC int whTest_NvmAddOverwriteDestroy(void* ctx)
228228
{
229229
whTestNvmFlashCtx* c = &_ctx;
230230
const whNvmCb* cb = &c->nvmCb;

0 commit comments

Comments
 (0)