yams/tests/unit/core/magic_numbers_test.cpp
Trevon 3f37e8f564
Sync experimental into main: Search retrieval improvements (#60)
* fix(graph): handle qualified C++ symbols

Signed-off-by: trvon <me@trevon.dev>

* refactor(vector)!: remove dead transition APIs

Split Simeon vector and retrieval linkage so consumers inherit only the runtime surface they use.

BREAKING CHANGE: remove unused async/model/config helpers and the undefined vector-backend factory from public headers.

Signed-off-by: trvon <me@trevon.dev>

* fix(vector): harden PQ snapshot lifecycle

Validate and atomically install persisted generations, revalidate after races, and fall back to exact search when a snapshot is stale. Add the exact-scan reference lane and distinguish observed work counters from unavailable backend metrics.

Signed-off-by: trvon <me@trevon.dev>

* feat(topology): expose route coordinates

Persist chart distortion and local-dimension evidence, retain pre-scalarization route axes, and emit detailed trace rows without treating unavailable vector work as zero.

Signed-off-by: trvon <me@trevon.dev>

* feat(xplan): add vector decision gates

Track binary and corpus identity, preserve missing-work semantics, and add repeated PQ routing, crossover, and rerank plans with quality and cost summaries.

Signed-off-by: trvon <me@trevon.dev>

* feat(topology): formalize coordinate routing

Separate chart geometry from route-risk admission and prove candidate-stage recall and strict-focus boundaries. Record the measured atlas limitations and literature basis for the next construction experiment.

Signed-off-by: trvon <me@trevon.dev>

* fix(search): serialize SymSpell readers

SQLite-backed lookups reuse mutable prepared statements, so shared-reader locking is unsafe. Advance the owned SymSpell revision for batched delete insertion and cover concurrent root lookups under TSAN.

Signed-off-by: trvon <me@trevon.dev>

* fix(vector): make mutations atomic

Preserve stored vectors on replacement and commit failures, reject cross-thread writes during explicit transactions, and restore exact reranking for quantized-primary PQ rows.

* fix(search)!: surface SymSpell write errors

Propagate persistent dictionary failures through metadata ingestion, roll back failed batches, and make clear remove stored terms.

BREAKING CHANGE: SymSpellSearch mutations now return Result values.

* feat(topology): formalize candidate expansion

* feat(vector): add coordinate-aware embeddings

* feat(topology): preserve protected relation cover

* feat(search): add graph candidate rescue

* feat(daemon): expose retrieval hydration telemetry

* feat(xplan): add rescue and hydration gates

* refactor(content): dedupe media handlers

Reuse shared graph presenter helpers and centralize repeated audio/video/archive buffer processing, validation, lifecycle, and stats helper logic.

* feat(lifecycle): prune stale corpus data

Keep cleanup preview-first while retention stays opt-in and long prune work no longer holds IPC responses open.

* feat(search): add topology candidate rescue

Preserve high-value routed candidates across vector backends while keeping exact rerank diagnostics explicit.

* feat(search): expose evidence paths

* docs: define mobile host contract

* formal: model bounded candidate rescue

* fix(prune): inspect extensionless signatures

* fix(prune): tolerate stale extensions

---------

Signed-off-by: trvon <me@trevon.dev>
2026-07-24 19:00:22 -04:00

459 lines
18 KiB
C++

// Copyright (c) 2025 YAMS Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <cstring>
#include <span>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <yams/core/magic_numbers.hpp>
using namespace yams::magic;
// Helper to create byte vectors from hex strings
std::vector<uint8_t> hex_to_vector(const std::string& hex) {
std::vector<uint8_t> result;
for (size_t i = 0; i < hex.length(); i += 2) {
uint8_t byte = std::stoi(hex.substr(i, 2), nullptr, 16);
result.push_back(byte);
}
return result;
}
//=============================================================================
// Pattern Matching Tests
//=============================================================================
TEST_CASE("Image format detection", "[core][magic][image]") {
SECTION("JPEG detection") {
auto jpeg_data = hex_to_vector("FFD8FF");
REQUIRE(detect_mime_type(std::span(jpeg_data)) == "image/jpeg");
}
SECTION("PNG detection") {
auto png_data = hex_to_vector("89504E470D0A1A0A");
REQUIRE(detect_mime_type(std::span(png_data)) == "image/png");
}
SECTION("GIF87a detection") {
auto gif87_data = hex_to_vector("474946383761");
REQUIRE(detect_mime_type(std::span(gif87_data)) == "image/gif");
}
SECTION("GIF89a detection") {
auto gif89_data = hex_to_vector("474946383961");
REQUIRE(detect_mime_type(std::span(gif89_data)) == "image/gif");
}
SECTION("BMP detection") {
auto bmp_data = hex_to_vector("424D");
REQUIRE(detect_mime_type(std::span(bmp_data)) == "image/bmp");
}
SECTION("TIFF detection") {
auto tiff_data = hex_to_vector("49492A00");
REQUIRE(detect_mime_type(std::span(tiff_data)) == "image/tiff");
}
}
TEST_CASE("Archive format detection", "[core][magic][archive]") {
SECTION("PDF detection") {
auto pdf_data = hex_to_vector("255044462D");
REQUIRE(detect_mime_type(std::span(pdf_data)) == "application/pdf");
}
SECTION("ZIP detection") {
auto zip_data = hex_to_vector("504B0304");
REQUIRE(detect_mime_type(std::span(zip_data)) == "application/zip");
}
SECTION("GZIP detection") {
auto gzip_data = hex_to_vector("1F8B");
REQUIRE(detect_mime_type(std::span(gzip_data)) == "application/gzip");
}
SECTION("BZIP2 detection") {
auto bzip2_data = hex_to_vector("425A68");
REQUIRE(detect_mime_type(std::span(bzip2_data)) == "application/x-bzip2");
}
SECTION("7-Zip detection") {
auto sevenz_data = hex_to_vector("377ABCAF271C");
REQUIRE(detect_mime_type(std::span(sevenz_data)) == "application/x-7z-compressed");
}
SECTION("RAR detection") {
auto rar_data = hex_to_vector("526172211A0700");
REQUIRE(detect_mime_type(std::span(rar_data)) == "application/x-rar-compressed");
}
SECTION("XZ detection") {
auto xz_data = hex_to_vector("FD377A585A00");
REQUIRE(detect_mime_type(std::span(xz_data)) == "application/x-xz");
}
}
TEST_CASE("Executable format detection", "[core][magic][executable]") {
SECTION("ELF detection") {
auto elf_data = hex_to_vector("7F454C46");
REQUIRE(detect_mime_type(std::span(elf_data)) == "application/x-executable");
}
SECTION("Windows PE detection") {
auto pe_data = hex_to_vector("4D5A");
REQUIRE(detect_mime_type(std::span(pe_data)) == "application/x-msdownload");
}
SECTION("Java class file detection") {
// Java class file: magic (CAFEBABE) + minor version (0000) + major version (0034 = Java 8)
// The version bytes distinguish it from Mach-O fat binary which also uses CAFEBABE
auto class_data = hex_to_vector("CAFEBABE00000034");
REQUIRE(detect_mime_type(std::span(class_data)) == "application/java-archive");
}
}
TEST_CASE("Audio format detection", "[core][magic][audio]") {
SECTION("MP3 detection") {
auto mp3_data = hex_to_vector("494433");
REQUIRE(detect_mime_type(std::span(mp3_data)) == "audio/mpeg");
}
SECTION("FLAC detection") {
auto flac_data = hex_to_vector("664C6143");
REQUIRE(detect_mime_type(std::span(flac_data)) == "audio/flac");
}
SECTION("Ogg detection") {
auto ogg_data = hex_to_vector("4F676753");
REQUIRE(detect_mime_type(std::span(ogg_data)) == "audio/ogg");
}
}
TEST_CASE("Video format detection", "[core][magic][video]") {
SECTION("MP4 detection") {
auto mp4_data = hex_to_vector("000000146674797069736F6D");
REQUIRE(detect_mime_type(std::span(mp4_data)) == "video/mp4");
}
SECTION("Matroska detection") {
auto mkv_data = hex_to_vector("1A45DFA3");
REQUIRE(detect_mime_type(std::span(mkv_data)) == "video/x-matroska");
}
SECTION("FLV detection") {
auto flv_data = hex_to_vector("464C56");
REQUIRE(detect_mime_type(std::span(flv_data)) == "video/x-flv");
}
}
TEST_CASE("Font format detection", "[core][magic][font]") {
SECTION("TrueType detection") {
auto ttf_data = hex_to_vector("0001000000");
REQUIRE(detect_mime_type(std::span(ttf_data)) == "font/ttf");
}
SECTION("OpenType detection") {
auto otf_data = hex_to_vector("4F54544F");
REQUIRE(detect_mime_type(std::span(otf_data)) == "font/otf");
}
SECTION("WOFF detection") {
auto woff_data = hex_to_vector("774F4646");
REQUIRE(detect_mime_type(std::span(woff_data)) == "font/woff");
}
}
TEST_CASE("Database format detection", "[core][magic][database]") {
auto sqlite_data = hex_to_vector("53514C69746520666F726D6174");
REQUIRE(detect_mime_type(std::span(sqlite_data)) == "application/x-sqlite3");
}
TEST_CASE("Text encoding detection", "[core][magic][text]") {
SECTION("UTF-8 BOM detection") {
auto utf8_bom = hex_to_vector("EFBBBF");
REQUIRE(detect_mime_type(std::span(utf8_bom)) == "text/plain");
}
SECTION("UTF-16 BE BOM detection") {
auto utf16be_bom = hex_to_vector("FEFF");
REQUIRE(detect_mime_type(std::span(utf16be_bom)) == "text/plain");
}
SECTION("UTF-16 LE BOM detection") {
auto utf16le_bom = hex_to_vector("FFFE");
REQUIRE(detect_mime_type(std::span(utf16le_bom)) == "text/plain");
}
}
//=============================================================================
// Prune Category Tests
//=============================================================================
TEST_CASE("Prune category detection", "[core][magic][prune]") {
SECTION("Build output directories") {
REQUIRE(matchesPruneGroup(getPruneCategory("build/output.o"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("dist/bundle.js"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("cmake-build-debug/CMakeFiles/app.dir/main.obj"),
"build"));
REQUIRE(matchesPruneGroup(
getPruneCategory("builddir-nosan/tests/catch2_idle_zero_concurrency"), "build"));
REQUIRE(matchesPruneGroup(
getPruneCategory("subprojects/foo/builddir-asan/tests/helper_binary"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("target/release/app"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory(".build/debug.yaml"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("DerivedData/App/Logs/build.log"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("buck-out/v2/gen/app"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("bazel-out/darwin-fastbuild/bin/app"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory(".next/static/chunks/app.js"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("obj/Debug/app.pdb"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("x64/Release/app.ilk"), "build-artifacts"));
REQUIRE(matchesPruneGroup(
getPruneCategory("build/Debug/app.dSYM/Contents/Resources/DWARF/app"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("build-release/tools/yams"), "build"));
REQUIRE(matchesPruneGroup(getPruneCategory("build-release/tools/yams"), "build"));
}
SECTION("Package caches and deps") {
REQUIRE(matchesPruneGroup(getPruneCategory("node_modules/pkg/index.js"), "packages"));
REQUIRE(matchesPruneGroup(getPruneCategory(".pnpm-store/v3/files/ab/cd"), "packages"));
REQUIRE(matchesPruneGroup(getPruneCategory(".yarn/cache/react-npm.zip"), "packages"));
REQUIRE(matchesPruneGroup(getPruneCategory(".pytest_cache/v/cache.db"), "packages"));
REQUIRE(matchesPruneGroup(getPruneCategory(".hypothesis/examples/123"), "packages"));
REQUIRE(
matchesPruneGroup(getPruneCategory(".ipynb_checkpoints/notebook.ipynb"), "packages"));
REQUIRE(matchesPruneGroup(getPruneCategory(".gradle/caches/modules.bin"), "packages"));
REQUIRE(matchesPruneGroup(getPruneCategory("ios/Pods/Alamofire/Source.swift"), "packages"));
REQUIRE(matchesPruneGroup(
getPruneCategory(".terraform/providers/registry.terraform.io/provider"), "packages"));
REQUIRE(matchesPruneGroup(getPruneCategory("ios/Pods/Alamofire/Source.swift"), "packages"));
REQUIRE(matchesPruneGroup(
getPruneCategory(".terraform/providers/registry.terraform.io/provider"), "packages"));
}
SECTION("Coverage and IDE outputs") {
REQUIRE(getPruneCategory("coverage/lcov.info") == PruneCategory::Coverage);
REQUIRE(getPruneCategory(".nyc_output/processinfo/index.json") == PruneCategory::Coverage);
REQUIRE(getPruneCategory(".vs/slnx.sqlite") == PruneCategory::IdeProject);
REQUIRE(getPruneCategory("meson-private/coredata.dat") == PruneCategory::SystemMeson);
REQUIRE(getPruneCategory("CMakeFiles/rules.ninja") == PruneCategory::SystemCMake);
REQUIRE(getPruneCategory(".cache/tool/entry") == PruneCategory::Cache);
REQUIRE(getPruneCategory(".cache/tool/entry") == PruneCategory::Cache);
}
SECTION("Stored extensions retain their leading dot") {
REQUIRE(getPruneCategory("/corpus/daemon.log", ".log") == PruneCategory::Logs);
REQUIRE(getPruneCategory("/corpus/module.o", ".o") == PruneCategory::BuildObject);
REQUIRE(getPruneCategory("/corpus/default.gcda", ".gcda") == PruneCategory::Coverage);
}
SECTION("MIME evidence identifies extensionless build artifacts") {
REQUIRE(getPruneCategory("/corpus/worker", "", "application/x-executable") ==
PruneCategory::BuildExecutable);
REQUIRE(getPruneCategory("/corpus/module", "", "application/x-llvm-bc") ==
PruneCategory::BuildObject);
}
SECTION("Git artifacts") {
REQUIRE(getPruneCategory(".git/objects/aa/bb") == PruneCategory::GitArtifacts);
}
}
//=============================================================================
// Pattern Structure Tests
//=============================================================================
TEST_CASE("Pattern database is valid", "[core][magic]") {
SECTION("Database is not empty") {
const auto& patterns = get_magic_patterns();
#if YAMS_HAS_CONSTEXPR_CONTAINERS
REQUIRE(patterns.size() > 0);
REQUIRE(patterns.size() == 97); // High-confidence patterns only
#endif
}
SECTION("Magic database info is accessible") {
size_t count = MagicDatabaseInfo::pattern_count();
const char* mode = MagicDatabaseInfo::mode();
REQUIRE(count > 0);
REQUIRE(mode != nullptr);
REQUIRE(strlen(mode) > 0);
#if YAMS_HAS_CONSTEXPR_CONTAINERS
std::string mode_str(mode);
REQUIRE(mode_str.find("compile-time") != std::string::npos);
#endif
}
SECTION("Pattern fields are valid") {
const auto& patterns = get_magic_patterns();
#if YAMS_HAS_CONSTEXPR_CONTAINERS
REQUIRE(patterns.size() > 0);
const auto& first = patterns[0];
REQUIRE(first.length > 0);
REQUIRE(first.length <= 32);
REQUIRE_FALSE(first.mime_type.empty());
REQUIRE_FALSE(first.file_type.empty());
REQUIRE_FALSE(first.description.empty());
REQUIRE(first.confidence >= 0.0f);
REQUIRE(first.confidence <= 1.0f);
#endif
}
}
//=============================================================================
// Edge Cases
//=============================================================================
TEST_CASE("Edge case handling", "[core][magic][edge]") {
SECTION("Empty data returns default") {
std::vector<uint8_t> empty;
REQUIRE(detect_mime_type(std::span(empty)) == "application/octet-stream");
}
SECTION("Unknown data returns default") {
auto unknown_data = hex_to_vector("DEADBEEFCAFE");
REQUIRE(detect_mime_type(std::span(unknown_data)) == "application/octet-stream");
}
SECTION("Partial match fails") {
// PNG signature is 8 bytes, provide only 4
auto partial_png = hex_to_vector("89504E47");
auto mime = detect_mime_type(std::span(partial_png));
REQUIRE(mime != "image/png");
}
SECTION("Short data handled gracefully") {
std::vector<uint8_t> short_data = {0x00};
REQUIRE(detect_mime_type(std::span(short_data)) == "application/octet-stream");
}
}
//=============================================================================
// Hex Conversion Tests
//=============================================================================
TEST_CASE("Hex conversion utilities", "[core][magic][hex]") {
SECTION("hex_char_to_byte") {
REQUIRE(hex_char_to_byte('0') == 0x0);
REQUIRE(hex_char_to_byte('9') == 0x9);
REQUIRE(hex_char_to_byte('A') == 0xA);
REQUIRE(hex_char_to_byte('F') == 0xF);
REQUIRE(hex_char_to_byte('a') == 0xa);
REQUIRE(hex_char_to_byte('f') == 0xf);
}
SECTION("hex_to_bytes") {
auto bytes = hex_to_bytes("FFD8FF");
REQUIRE(bytes[0] == 0xFF);
REQUIRE(bytes[1] == 0xD8);
REQUIRE(bytes[2] == 0xFF);
}
SECTION("hex_length") {
REQUIRE(hex_length("FF") == 1);
REQUIRE(hex_length("FFD8") == 2);
REQUIRE(hex_length("FFD8FF") == 3);
REQUIRE(hex_length("89504E470D0A1A0A") == 8);
}
}
//=============================================================================
// Pattern Matching Logic Tests
//=============================================================================
TEST_CASE("Pattern matching logic", "[core][magic][matching]") {
SECTION("Pattern matches at correct offset") {
MagicPattern pattern{hex_to_bytes("7573746172"),
hex_length("7573746172"),
257, // TAR magic at offset 257
"archive",
"application/x-tar",
"POSIX tar archive",
1.0f};
// Create buffer with pattern at offset 257
std::vector<uint8_t> buffer(300, 0x00);
auto tar_magic = hex_to_vector("7573746172");
std::copy(tar_magic.begin(), tar_magic.end(), buffer.begin() + 257);
// Should NOT match at offset 0
REQUIRE_FALSE(pattern.matches(std::span(buffer.data(), 10), 0));
// Should match at offset 257
REQUIRE(pattern.matches(std::span(buffer.data() + 257, 10), 257));
}
SECTION("Pattern match fails on mismatch") {
MagicPattern pattern{hex_to_bytes("FFD8FF"), 3, 0, "image", "image/jpeg", "JPEG", 1.0f};
auto wrong_data = hex_to_vector("89504E");
REQUIRE_FALSE(pattern.matches(std::span(wrong_data), 0));
}
SECTION("Pattern match fails on insufficient data") {
MagicPattern pattern{
hex_to_bytes("89504E470D0A1A0A"), 8, 0, "image", "image/png", "PNG", 1.0f};
// Only 4 bytes provided, pattern needs 8
auto short_data = hex_to_vector("89504E47");
REQUIRE_FALSE(pattern.matches(std::span(short_data), 0));
}
}
//=============================================================================
// C++23 Constexpr Tests
//=============================================================================
#if YAMS_HAS_CONSTEXPR_CONTAINERS
TEST_CASE("Constexpr magic number detection", "[core][magic][constexpr]") {
SECTION("Constexpr detection works") {
std::array<uint8_t, 3> jpeg_magic = {0xFF, 0xD8, 0xFF};
auto mime = detect_mime_type(std::span(jpeg_magic.data(), jpeg_magic.size()));
REQUIRE(mime == "image/jpeg");
}
SECTION("Pattern count is compile-time") {
size_t count = MagicDatabaseInfo::pattern_count();
REQUIRE(count > 80);
}
SECTION("Patterns are accessible at compile-time") {
const auto& patterns = get_magic_patterns();
REQUIRE(patterns.size() > 0);
const auto& first = patterns[0];
REQUIRE(first.length > 0);
}
}
#endif
//=============================================================================
// Performance/Stress Tests
//=============================================================================
TEST_CASE("Performance and stress testing", "[core][magic][performance]") {
SECTION("Large data handled efficiently") {
// Create 1MB of data
std::vector<uint8_t> large_data(1024 * 1024, 0x00);
// Put JPEG magic at start
large_data[0] = 0xFF;
large_data[1] = 0xD8;
large_data[2] = 0xFF;
// Should still detect correctly
REQUIRE(detect_mime_type(std::span(large_data.data(), 100)) == "image/jpeg");
}
SECTION("Multiple detections are consistent") {
auto jpeg_data = hex_to_vector("FFD8FF");
// Run detection 100 times, should always return same result
for (int i = 0; i < 100; ++i) {
REQUIRE(detect_mime_type(std::span(jpeg_data)) == "image/jpeg");
}
}
}