Files
libakerror/tests/err_maxval.c
Andrew Kesterson 536a269aad
All checks were successful
libakerror CI Build / cmake_build (push) Successful in 2m41s
libakerror CI Build / mutation_test (push) Successful in 6m52s
Derive err_maxval's code set from the header, not a hardcoded list
The previous err_maxval hardcoded the list of AKERR_* codes, which silently
rots the moment a code is added. Instead, parse the generated akerror.h at
runtime: discover every "#define AKERR_<NAME> (AKERR_LAST_ERRNO_VALUE + N)",
take the highest offset actually defined, and assert AKERR_MAX_ERR_VALUE covers
it. A compile-time cross-check ties the parsed ceiling to the compiled macro so
the test can't pass by reading a stale header.

CMake injects the header path as AKERR_GENERATED_HEADER. Verified: the test
fails (max_err_value >= highest_code) when pointed at a +15 header while
AKERR_BADEXC is +17, and now also strengthens header mutation coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 17:26:32 -04:00

93 lines
2.9 KiB
C

#include "akerror.h"
#include "err_capture.h"
#include <regex.h>
/*
* AKERR_MAX_ERR_VALUE sizes the __AKERR_ERROR_NAMES table and is the upper bound
* akerr_name_for_status() accepts. If it is smaller than the highest AKERR_*
* code, those codes silently lose their names (this was a real bug: the max was
* +15 while AKERR_BADEXC is +17).
*
* Rather than hardcode the list of codes (which rots the moment someone adds a
* code), this test parses the generated akerror.h, discovers every
* #define AKERR_<NAME> (AKERR_LAST_ERRNO_VALUE + <N>)
* finds the highest offset actually defined, and verifies AKERR_MAX_ERR_VALUE
* covers it. The path to the header the library was built from is injected by
* CMake as AKERR_GENERATED_HEADER.
*/
#ifndef AKERR_GENERATED_HEADER
#error "AKERR_GENERATED_HEADER (path to generated akerror.h) must be defined"
#endif
int main(void)
{
FILE *fh = fopen(AKERR_GENERATED_HEADER, "r");
AKERR_CHECK(fh != NULL);
/* #define AKERR_NAME (AKERR_LAST_ERRNO_VALUE + N) */
regex_t re;
const char *pattern =
"^[[:space:]]*#define[[:space:]]+(AKERR_[A-Za-z0-9_]+)[[:space:]]+"
"\\(AKERR_LAST_ERRNO_VALUE[[:space:]]*\\+[[:space:]]*([0-9]+)\\)";
AKERR_CHECK(regcomp(&re, pattern, REG_EXTENDED) == 0);
int highest_code = -1; /* highest offset among real error codes */
char highest_name[64] = "";
int max_err_value = -1; /* offset parsed from AKERR_MAX_ERR_VALUE */
int code_count = 0;
char line[4096];
regmatch_t m[3];
while ( fgets(line, sizeof(line), fh) != NULL ) {
if ( regexec(&re, line, 3, m, 0) != 0 ) {
continue;
}
char name[64];
int nlen = (int)(m[1].rm_eo - m[1].rm_so);
if ( nlen >= (int)sizeof(name) ) {
nlen = (int)sizeof(name) - 1;
}
memcpy(name, line + m[1].rm_so, nlen);
name[nlen] = '\0';
char num[16];
int vlen = (int)(m[2].rm_eo - m[2].rm_so);
if ( vlen >= (int)sizeof(num) ) {
vlen = (int)sizeof(num) - 1;
}
memcpy(num, line + m[2].rm_so, vlen);
num[vlen] = '\0';
int offset = atoi(num);
if ( strcmp(name, "AKERR_MAX_ERR_VALUE") == 0 ) {
max_err_value = offset;
} else {
code_count++;
if ( offset > highest_code ) {
highest_code = offset;
snprintf(highest_name, sizeof(highest_name), "%s", name);
}
}
}
regfree(&re);
fclose(fh);
/* We must have actually parsed both the codes and the ceiling. */
AKERR_CHECK(code_count > 0);
AKERR_CHECK(highest_code > 0);
AKERR_CHECK(max_err_value > 0);
/* Guard against parsing a different header than the one compiled in. */
AKERR_CHECK(max_err_value == (AKERR_MAX_ERR_VALUE - AKERR_LAST_ERRNO_VALUE));
/* The actual invariant: every defined code is indexable in the names table. */
AKERR_CHECK(max_err_value >= highest_code);
fprintf(stderr,
"err_maxval ok (%d codes parsed, highest %s at +%d, max +%d)\n",
code_count, highest_name, highest_code, max_err_value);
return 0;
}