commit 4c700b68454e1122cf38722505389124ca167189
parent ed311622bb71bccb5e5e98fe9c6dfff8f058c999
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 2 Jun 2026 11:17:53 -0700
test: cover the new byte-utility tools + hash API
- test/api/hash_test.c (test-hash): cfree_hash against known vectors
(SHA-256 empty/abc, CRC-32 0xcbf43926, BLAKE2b-256 abc) plus streaming/
one-shot equivalence for all three algos. 24 checks.
- test/tools/run.sh (test-driver-tools): self-checking CLI harness for
xxd (round-trips + -p/-i), cmp (identical/differ/-s + message), hash,
disas (aa64/x64 + bad-hex), and mc (encoding + mc -p|disas round-trip +
reloc). 16 checks.
Both added to the default gate and the test-driver aggregate.
Diffstat:
4 files changed, 181 insertions(+), 2 deletions(-)
diff --git a/test/api/hash_test.c b/test/api/hash_test.c
@@ -0,0 +1,87 @@
+/* hash_test — public <cfree/hash.h> surface: one-shot cfree_hash against known
+ * vectors (SHA-256 empty/"abc", CRC-32 of "123456789", BLAKE2b-256 "abc") and
+ * streaming/one-shot equivalence (two cfree_hasher_update chunks == one shot)
+ * for all three algorithms.
+ *
+ * Run by: make test-hash
+ */
+
+#include <cfree/core.h>
+#include <cfree/hash.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "lib/cfree_unit.h"
+
+static CfreeUnit g_u;
+#define EXPECT(c, ...) CU_EXPECT(&g_u, c, __VA_ARGS__)
+
+static int hexval(char c) {
+ if (c >= '0' && c <= '9') return c - '0';
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+ return -1;
+}
+
+static void parse_hex(const char* hex, uint8_t* out, size_t n) {
+ size_t i;
+ for (i = 0; i < n; ++i)
+ out[i] = (uint8_t)((hexval(hex[2 * i]) << 4) | hexval(hex[2 * i + 1]));
+}
+
+static void check_oneshot(CfreeHashAlgo algo, const char* name, const char* msg,
+ size_t len, const char* exphex) {
+ uint8_t digest[CFREE_HASH_MAX_LEN];
+ uint8_t want[CFREE_HASH_MAX_LEN];
+ size_t dlen = 0;
+ size_t wn = strlen(exphex) / 2;
+ EXPECT(cfree_hash(algo, (const uint8_t*)msg, len, digest, &dlen) == CFREE_OK,
+ "%s: cfree_hash returned OK", name);
+ EXPECT(dlen == cfree_hash_len(algo) && dlen == wn,
+ "%s: digest length %zu == %zu", name, dlen, wn);
+ parse_hex(exphex, want, wn);
+ EXPECT(memcmp(digest, want, wn) == 0, "%s: digest matches %s", name, exphex);
+}
+
+/* Hashing "abc"+"def" in two streamed chunks must equal a one-shot of
+ * "abcdef". */
+static void check_stream_equiv(CfreeHashAlgo algo, const char* name) {
+ uint8_t one[CFREE_HASH_MAX_LEN];
+ uint8_t two[CFREE_HASH_MAX_LEN];
+ size_t l1 = 0, l2 = 0;
+ CfreeHasher* h = NULL;
+ EXPECT(cfree_hash(algo, (const uint8_t*)"abcdef", 6, one, &l1) == CFREE_OK,
+ "%s: one-shot OK", name);
+ EXPECT(cfree_hasher_new(&g_u.ctx, algo, &h) == CFREE_OK, "%s: hasher_new",
+ name);
+ cfree_hasher_update(h, (const uint8_t*)"abc", 3);
+ cfree_hasher_update(h, (const uint8_t*)"def", 3);
+ EXPECT(cfree_hasher_final(h, two, &l2) == CFREE_OK, "%s: hasher_final", name);
+ cfree_hasher_free(h);
+ EXPECT(l1 == l2 && memcmp(one, two, l1) == 0,
+ "%s: streamed digest == one-shot", name);
+}
+
+int main(void) {
+ cfree_unit_init(&g_u);
+
+ check_oneshot(
+ CFREE_HASH_SHA256, "sha256(\"\")", "", 0,
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+ check_oneshot(
+ CFREE_HASH_SHA256, "sha256(\"abc\")", "abc", 3,
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
+ check_oneshot(CFREE_HASH_CRC32, "crc32(\"123456789\")", "123456789", 9,
+ "cbf43926");
+ check_oneshot(
+ CFREE_HASH_BLAKE2B, "blake2b(\"abc\")", "abc", 3,
+ "bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319");
+
+ check_stream_equiv(CFREE_HASH_SHA256, "sha256");
+ check_stream_equiv(CFREE_HASH_BLAKE2B, "blake2b");
+ check_stream_equiv(CFREE_HASH_CRC32, "crc32");
+
+ cfree_unit_summary(&g_u, "hash_test");
+ return cfree_unit_status(&g_u);
+}
diff --git a/test/lib/unit.mk b/test/lib/unit.mk
@@ -30,9 +30,10 @@ UNIT_CFLAGS_INTERNAL = $(HOST_CFLAGS) -Iinclude -Isrc -Itest
# ---- registrations: stem lists + per-stem source ---------------------------
UNIT_TESTS_PUBLIC := \
- ar_test cg_api_test cg_switch_test cg_fp_cmp_test rv64_jit_test \
+ ar_test cg_api_test cg_switch_test cg_fp_cmp_test hash_test rv64_jit_test \
aa64_inline_test rv64_inline_test x64_inline_test
ar_test_SRC := test/ar/ar_test.c
+hash_test_SRC := test/api/hash_test.c
cg_api_test_SRC := test/api/cg_type_test.c
cg_switch_test_SRC := test/api/cg_switch_test.c
cg_fp_cmp_test_SRC := test/api/cg_fp_cmp_test.c
diff --git a/test/test.mk b/test/test.mk
@@ -67,6 +67,8 @@ TEST_TARGETS = \
test-driver-objdump \
test-driver-pkg \
test-driver-strings \
+ test-driver-tools \
+ test-hash \
test-driver-strip \
test-dwarf \
test-elf \
@@ -183,7 +185,7 @@ test-images:
test-cf-corpus-selftest:
@bash test/lib/cf_corpus_selftest.sh
-test-driver: test-driver-cc test-driver-ar test-driver-cas test-driver-strip test-driver-objcopy test-driver-objdump test-driver-pkg test-driver-strings
+test-driver: test-driver-cc test-driver-ar test-driver-cas test-driver-strip test-driver-objcopy test-driver-objdump test-driver-pkg test-driver-strings test-driver-tools
test-driver-cc: bin
@CFREE=$(abspath $(BIN)) sh test/driver/run.sh
@@ -263,6 +265,9 @@ test-driver-pkg: bin
test-driver-strings: bin
@CFREE=$(abspath $(BIN)) sh test/strings/run.sh
+test-driver-tools: bin
+ @CFREE=$(abspath $(BIN)) sh test/tools/run.sh
+
# DWARF consumer unit test: builds a hand-crafted DWARF-bearing ELF in
# memory and exercises every cfree_dwarf_* entry. It reaches into the
# internal object builder to synthesize the fixture, so link individual
@@ -374,6 +379,7 @@ test-interp-toy: bin
CG_API_TEST_BIN = build/test/cg_api_test
CG_SWITCH_TEST_BIN = build/test/cg_switch_test
CG_FP_CMP_TEST_BIN = build/test/cg_fp_cmp_test
+HASH_TEST_BIN = build/test/hash_test
ABI_CLASSIFY_TEST_BIN = build/test/abi_classify_test
IR_RECORDER_TEST_BIN = build/test/ir_recorder_test
NATIVE_DIRECT_TARGET_TEST_BIN = build/test/native_direct_target_test
@@ -383,6 +389,9 @@ test-cg-api: $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) $(CG_FP_CMP_TEST_BIN)
$(CG_SWITCH_TEST_BIN)
$(CG_FP_CMP_TEST_BIN)
+test-hash: $(HASH_TEST_BIN)
+ $(HASH_TEST_BIN)
+
test-abi-classify: $(ABI_CLASSIFY_TEST_BIN)
$(ABI_CLASSIFY_TEST_BIN)
diff --git a/test/tools/run.sh b/test/tools/run.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+# Driver-level checks for the byte-utility tools: xxd, cmp, hash, disas, mc.
+# Self-checking (no golden files): round-trips and known encodings/digests are
+# asserted inline via the shared cf_* verbs (ok/run_ok/run_fail/contains/
+# same_file) recorded over $work.
+
+set -u
+
+script_dir=$(cd "$(dirname "$0")" && pwd)
+repo_root=$(cd "$script_dir/../.." && pwd)
+
+CFREE="${CFREE:-$repo_root/build/cfree}"
+
+if [ ! -x "$CFREE" ]; then
+ echo "tools: cfree binary not found at $CFREE" >&2
+ exit 2
+fi
+
+work=$(mktemp -d "${TMPDIR:-/tmp}/cfree-tools-test.XXXXXX")
+trap 'rm -rf "$work"' EXIT
+
+CF_KIT_DIR="$repo_root/test/lib"
+. "$repo_root/test/lib/cfree_sh_kit.sh"
+cf_report_init
+
+# A fixture with text, newlines, NUL and high bytes — exercises the ASCII
+# column and binary round-tripping.
+printf 'Hello, cfree!\nLine two.\n\000\001\377\376' > "$work/data.bin"
+
+# ---- xxd -------------------------------------------------------------------
+"$CFREE" xxd "$work/data.bin" > "$work/dump.txt" 2> "$work/xxd.err"
+"$CFREE" xxd -r "$work/dump.txt" > "$work/rt.bin" 2>> "$work/xxd.err"
+same_file xxd-roundtrip "$work/data.bin" "$work/rt.bin"
+
+"$CFREE" xxd -p "$work/data.bin" > "$work/p.txt" 2>> "$work/xxd.err"
+"$CFREE" xxd -r -p "$work/p.txt" > "$work/rtp.bin" 2>> "$work/xxd.err"
+same_file xxd-plain-roundtrip "$work/data.bin" "$work/rtp.bin"
+
+"$CFREE" xxd -i "$work/data.bin" > "$work/inc.txt" 2>> "$work/xxd.err"
+contains xxd-include-array "$work/inc.txt" "unsigned char"
+contains xxd-include-len "$work/inc.txt" "_len = 28"
+
+# ---- cmp -------------------------------------------------------------------
+cp "$work/data.bin" "$work/data2.bin"
+run_ok cmp-identical "$CFREE" cmp "$work/data.bin" "$work/data2.bin"
+
+printf 'Hello, cfree!\nLine TWO.\n\000\001\377\376' > "$work/diff.bin"
+run_fail cmp-differ-exit "$CFREE" cmp "$work/data.bin" "$work/diff.bin"
+"$CFREE" cmp "$work/data.bin" "$work/diff.bin" > "$work/cmp.out" 2>&1
+contains cmp-differ-message "$work/cmp.out" "differ: char"
+run_fail cmp-silent-exit "$CFREE" cmp -s "$work/data.bin" "$work/diff.bin"
+
+# ---- hash (CLI smoke; vectors covered by the test-hash unit test) ----------
+printf '123456789' > "$work/crc.in"
+"$CFREE" hash -a crc32 "$work/crc.in" > "$work/crc.out" 2>&1
+contains hash-crc32 "$work/crc.out" "cbf43926"
+printf 'abc' | "$CFREE" hash -a sha256 > "$work/sha.out" 2>&1
+contains hash-sha256 "$work/sha.out" \
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
+
+# ---- disas -----------------------------------------------------------------
+"$CFREE" disas -target aarch64 -x "1f 20 03 d5" > "$work/d-aa64.out" 2>&1
+contains disas-aa64-nop "$work/d-aa64.out" "nop"
+"$CFREE" disas -target x86_64 -x "c3" > "$work/d-x64.out" 2>&1
+contains disas-x64-ret "$work/d-x64.out" "ret"
+run_fail disas-bad-hex "$CFREE" disas -target aarch64 -x "zz"
+
+# ---- mc --------------------------------------------------------------------
+"$CFREE" mc -target aarch64 "add x0, x1, x2" > "$work/mc.out" 2>&1
+contains mc-aa64-encoding "$work/mc.out" "encoding: [0x20,0x00,0x02,0x8b]"
+
+# mc -p (raw hex) piped back through disas must recover the mnemonic.
+"$CFREE" mc -target aarch64 -p "add x0, x1, x2" > "$work/mc.hex" 2>&1
+"$CFREE" disas -target aarch64 -x "$(cat "$work/mc.hex")" > "$work/mc-rt.out" 2>&1
+contains mc-disas-roundtrip "$work/mc-rt.out" "add x0, x1, x2"
+
+# A branch to an undefined symbol should surface a relocation, not an error.
+"$CFREE" mc -target aarch64 "b somewhere" > "$work/mc-rel.out" 2>&1
+contains mc-reloc "$work/mc-rel.out" "reloc"
+
+cf_summary tools-driver
+cf_exit