Skip to content

Commit f09f945

Browse files
committed
test(leak): env-selectable CLI mode — C standalone --leak-seconds
Adds a time-bounded encode/decode loop (--leak-seconds N) to the C standalone that self-reports RSS, so test/leak_test can drive it via IMPLEMENTATION_TO_TEST. This covers the C buffer_t alloc/free path (where read_file/make_utf8/encode_data bugs lived) — NOT exercised by the FFI harness (which uses the Zig lib). test-leak now runs both the FFI harness and the C CLI; both plateau (PASS). (Zig CLI's core is already covered by the FFI harness; Lua/Node are GC'd with noisy RSS — those CLI leak modes are a smaller follow-up.)
1 parent 6857ee6 commit f09f945

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

flake.nix

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,9 @@
340340
zig build
341341
clang -O2 -Isrc -o leak_harness test/leak_harness.c zig-out/lib/libprintable_binary.a
342342
LEAK_HARNESS=./leak_harness LEAK_SECONDS=6 bash test/leak_test
343+
# CLI mode: drive the C standalone's buffer_t path via --leak-seconds.
344+
clang -O3 -Wall -Wextra -I. -o printable-binary-c src/printable_binary.c
345+
IMPLEMENTATION_TO_TEST=./printable-binary-c LEAK_SECONDS=6 bash test/leak_test
343346
'';
344347
installPhase = "mkdir -p $out && touch $out/passed";
345348
};

src/printable_binary.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
#include <stdint.h>
1212
#include <stdbool.h>
1313
#include <unistd.h>
14+
#include <time.h>
15+
#if defined(__APPLE__)
16+
#include <mach/mach.h>
17+
#endif
1418
#include <sys/stat.h>
1519
#include <ctype.h>
1620
#include <errno.h>
@@ -1600,7 +1604,65 @@ static options_t parse_options(int argc, char *argv[]) {
16001604

16011605

16021606
#ifndef PRINTABLE_BINARY_NO_MAIN
1607+
/* ---- memory-leak suite support (driven by test/leak_test via --leak-seconds) ---- */
1608+
static long pb_now_ms(void) {
1609+
struct timespec ts;
1610+
clock_gettime(CLOCK_MONOTONIC, &ts);
1611+
return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
1612+
}
1613+
static long pb_rss_kb(void) {
1614+
#if defined(__APPLE__)
1615+
mach_task_basic_info_data_t info;
1616+
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
1617+
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &count) != KERN_SUCCESS) return -1;
1618+
return (long)(info.resident_size / 1024);
1619+
#else
1620+
FILE *f = fopen("/proc/self/statm", "r");
1621+
if (!f) return -1;
1622+
long total = 0, resident = 0;
1623+
int n = fscanf(f, "%ld %ld", &total, &resident);
1624+
fclose(f);
1625+
return n == 2 ? resident * (sysconf(_SC_PAGESIZE) / 1024) : -1;
1626+
#endif
1627+
}
1628+
/* Time-bounded encode/decode loop that self-reports RSS, so the leak suite can
1629+
* watch the C standalone's buffer_t alloc/free path for growth over time. */
1630+
static void run_leak_loop(long seconds) {
1631+
if (seconds <= 0) seconds = 8;
1632+
long start = pb_now_ms(), deadline = start + seconds * 1000L, last = start;
1633+
static uint8_t buf[8192];
1634+
uint64_t rng = 0x9E3779B97F4A7C15ULL;
1635+
options_t opts;
1636+
memset(&opts, 0, sizeof opts);
1637+
printf("RSS %ld\n", pb_rss_kb());
1638+
fflush(stdout);
1639+
for (long i = 0;; i++) {
1640+
if ((i & 0x3FF) == 0) {
1641+
long t = pb_now_ms();
1642+
if (t >= deadline) break;
1643+
if (t - last >= 100) { printf("RSS %ld\n", pb_rss_kb()); fflush(stdout); last = t; }
1644+
}
1645+
rng = rng * 6364136223846793005ULL + 1442695040888963407ULL;
1646+
size_t len = (size_t)((rng >> 33) % (sizeof(buf) + 1));
1647+
for (size_t k = 0; k < len; k++) { rng = rng * 6364136223846793005ULL + 1442695040888963407ULL; buf[k] = (uint8_t)(rng >> 33); }
1648+
buffer_t enc = encode_data(buf, len, &opts);
1649+
buffer_t dec = decode_data((uint8_t *)enc.data, enc.size, false);
1650+
buffer_free(&dec);
1651+
buffer_free(&enc);
1652+
}
1653+
printf("RSS %ld\n", pb_rss_kb());
1654+
fflush(stdout);
1655+
}
1656+
16031657
int main(int argc, char *argv[]) {
1658+
// Leak-test mode (test/leak_test): long-lived encode/decode loop, then exit.
1659+
for (int ai = 1; ai < argc; ai++) {
1660+
if (strcmp(argv[ai], "--leak-seconds") == 0 && ai + 1 < argc) {
1661+
init_tables(argv[0]);
1662+
run_leak_loop(atol(argv[ai + 1]));
1663+
return 0;
1664+
}
1665+
}
16041666
// Parse command line options first (needed for help/usage)
16051667
options_t opts = parse_options(argc, argv);
16061668
const char *program_display_name = resolve_program_name(argv[0]);

0 commit comments

Comments
 (0)