from __future__ import annotations
import hashlib
import json
from pathlib import Path
import pytest
from pith_text import (
FfiError,
SIGNATURE_WORDS,
find_cdylib,
fingerprint_stream,
jaccard_bits,
parse_fingerprint,
)
REPO_ROOT = Path(__file__).resolve().parents[3]
FNV_OFFSET = 0xCBF29CE484222325
FNV_PRIME = 0x100000001B3
def fnv1a64(data: bytes) -> int:
h = FNV_OFFSET
for b in data:
h = ((h ^ b) * FNV_PRIME) & 0xFFFFFFFFFFFFFFFF
return h
def vectors() -> list[dict]:
raw = json.loads((REPO_ROOT / "reference.json").read_text(encoding="utf-8"))
return raw["vectors"]
def fixture_bytes(stem: str) -> bytes:
return (REPO_ROOT / "tests" / "fixtures" / f"{stem}.txt").read_bytes()
def vector_input(v: dict) -> bytes:
if v["input_kind"] == "inline-hex":
return bytes.fromhex(v["input_hex"])
stem = Path(v["input_path"]).stem
return fixture_bytes(stem)
def signature_operand(path: str) -> list[int]:
if path == "empty-signature":
return []
stem = Path(path.removeprefix("fixture:")).stem
return list(parse_fingerprint(fingerprint_stream(fixture_bytes(stem))).signature)
def assert_fingerprint_fields(v: dict, raw: bytes) -> None:
fp = parse_fingerprint(raw)
assert fp.word_count == v["word_count"], v["name"]
assert fp.shingle_count == v["shingle_count"], v["name"]
assert hashlib.sha256(fp.canonical).hexdigest() == v["canonical_sha256"], v["name"]
assert fnv1a64(fp.canonical) == int(v["canonical_fnv1a64"], 16), v["name"]
if "signature_word_0" not in v:
return
assert len(fp.signature) == SIGNATURE_WORDS, v["name"]
assert f"{fp.signature[0]:016x}" == v["signature_word_0"], v["name"]
assert f"{fp.signature[1]:016x}" == v["signature_word_1"], v["name"]
tail = raw[len(raw) - SIGNATURE_WORDS * 8 :]
assert fnv1a64(tail) == int(v["signature_fnv1a64"], 16), v["name"]
assert hashlib.sha256(tail).hexdigest() == v["signature_sha256"], v["name"]
def test_cdylib_is_discoverable() -> None:
path = find_cdylib()
assert path.is_file(), path
@pytest.mark.parametrize("v", [x for x in vectors() if x["name"].startswith(("canon-", "fixture-"))])
def test_canonicalisation_vector_is_reproduced_hex_exact(v: dict) -> None:
raw = fingerprint_stream(vector_input(v))
assert_fingerprint_fields(v, raw)
def test_full_signature_vector_is_reproduced_hex_exact() -> None:
v = next(x for x in vectors() if x["name"] == "signature-alpha-beta-gamma")
raw = fingerprint_stream(bytes.fromhex(v["input_hex"]))
fp = parse_fingerprint(raw)
assert [f"{w:016x}" for w in fp.signature] == v["signature_words_hex"]
tail = raw[len(raw) - SIGNATURE_WORDS * 8 :]
assert fnv1a64(tail) == int(v["signature_fnv1a64"], 16)
assert hashlib.sha256(tail).hexdigest() == v["signature_sha256"]
def test_empty_sentinel_vector_is_reproduced_hex_exact() -> None:
v = next(x for x in vectors() if x["name"] == "signature-empty-sentinel")
raw = fingerprint_stream(bytes.fromhex(v["input_hex"])) fp = parse_fingerprint(raw)
assert fp.word_count == 0 and fp.shingle_count == 0
assert fp.canonical == b"\n" assert set(fp.signature) == {0xFFFFFFFFFFFFFFFF}
assert f"{fp.signature[0]:016x}" == v["signature_word_0"]
assert f"{fp.signature[1]:016x}" == v["signature_word_1"]
tail = raw[len(raw) - SIGNATURE_WORDS * 8 :]
assert fnv1a64(tail) == int(v["signature_fnv1a64"], 16)
assert hashlib.sha256(tail).hexdigest() == v["signature_sha256"]
@pytest.mark.parametrize("v", [x for x in vectors() if x["name"].startswith("jaccard-")])
def test_jaccard_vector_is_reproduced_bit_exact(v: dict) -> None:
a = signature_operand(v["a"])
b = signature_operand(v["b"])
assert jaccard_bits(a, b) == int(v["value_bits"], 16), v["name"]
def test_every_vector_kind_is_consumed() -> None:
names = [x["name"] for x in vectors()]
assert len(names) == 22
assert sum(n.startswith(("canon-", "fixture-")) for n in names) == 15
assert sum(n.startswith("signature-") for n in names) == 2
assert sum(n.startswith("jaccard-") for n in names) == 5
def test_non_utf8_bytes_are_refused() -> None:
with pytest.raises(FfiError) as err:
fingerprint_stream(b"one \xff two")
assert err.value.status == -2
def test_empty_input_is_valid_not_refused() -> None:
raw = fingerprint_stream(b"")
assert len(raw) == 8 + 1 + 1024 assert set(parse_fingerprint(raw).signature) == {0xFFFFFFFFFFFFFFFF}
def test_stream_matches_a_rust_pinned_value() -> None:
fp = parse_fingerprint(fingerprint_stream("alpha beta gamma"))
assert fp.word_count == 3
assert fp.shingle_count == 1
assert fp.signature[0] == 0x5409AADB22BB8479
assert fp.signature[1] == 0xB2E6FF4EDEBF53C6
tail = fp.raw[len(fp.raw) - SIGNATURE_WORDS * 8 :]
assert f"{fnv1a64(tail):016x}" == "46f8ab1a50c61813"
assert (
hashlib.sha256(tail).hexdigest()
== "5a3e4c4a48d55a405faced9c28d4bf12b78af5661102eb2ac3dcab843485d8df"
)
sentinel = parse_fingerprint(fingerprint_stream(b""))
stail = sentinel.raw[len(sentinel.raw) - SIGNATURE_WORDS * 8 :]
assert f"{fnv1a64(stail):016x}" == "89bfa3a928539725"
assert (
hashlib.sha256(stail).hexdigest()
== "5f4ecdb7b71c3e403983fe405cddcdc2f2576b655fdb3e80d94a6f7c32e58bc2"
)