fix(capture): populate contentHash on captured samples to fix always-prompting remove
contentHash was left empty on every capture; hashReferencedElsewhere returns false for empty hashes, so every remove looked like a last reference. Add FNV-1a hashBytes to capture_paths, wire into both commit paths. NOTE: this activates index dedup-by-hash in production — a bit-identical re-capture now collapses onto the existing entry instead of adding a duplicate (spec-intended).
This commit is contained in:
@@ -5,8 +5,10 @@
|
||||
|
||||
#include "../src/capture_paths.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace reasampler;
|
||||
|
||||
@@ -293,6 +295,57 @@ static void testTransitionInPlaceSaveIsNoOp() {
|
||||
== ProjectTransition::NoOp);
|
||||
}
|
||||
|
||||
// --- hashBytes (FNV-1a content hash) ----------------------------------------
|
||||
//
|
||||
// The fix for the confirm-on-last-reference bug: hashBytes produces a 16-char hex
|
||||
// string that capture.cpp and capture_realtime.cpp store on Sample::contentHash so
|
||||
// BankBook::hashReferencedElsewhere can detect copies and suppress the confirm when
|
||||
// another bank still holds the same file.
|
||||
|
||||
static void testHashBytesOutputFormat() {
|
||||
// Output is always 16 lowercase hex characters.
|
||||
const std::uint8_t bytes[] = {0x01, 0x02, 0x03};
|
||||
const std::string h = hashBytes(bytes, 3);
|
||||
CHECK(h.size() == 16);
|
||||
for (char c : h) {
|
||||
CHECK((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'));
|
||||
}
|
||||
}
|
||||
|
||||
static void testHashBytesDeterministic() {
|
||||
// Same input always produces the same output (bit-identical captures get
|
||||
// the same hash, so hashReferencedElsewhere fires correctly for copies).
|
||||
const std::uint8_t bytes[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01};
|
||||
CHECK(hashBytes(bytes, 5) == hashBytes(bytes, 5));
|
||||
}
|
||||
|
||||
static void testHashBytesDistinct() {
|
||||
// Different inputs produce different hashes (no accidental dedup of distinct
|
||||
// files). This covers the "one-bit-flip changes the hash" property.
|
||||
std::uint8_t a[] = {0x00, 0x00};
|
||||
std::uint8_t b[] = {0x00, 0x01};
|
||||
CHECK(hashBytes(a, 2) != hashBytes(b, 2));
|
||||
|
||||
std::uint8_t c[] = {0xFF, 0xFF, 0xFF};
|
||||
std::uint8_t d[] = {0xFF, 0xFF, 0xFE};
|
||||
CHECK(hashBytes(c, 3) != hashBytes(d, 3));
|
||||
}
|
||||
|
||||
static void testHashBytesEmptyBufferIsNonEmpty() {
|
||||
// An empty buffer returns the FNV-1a offset basis in hex (stable, non-empty
|
||||
// sentinel) — capturing the contract that even empty inputs yield a 16-char hash.
|
||||
const std::string h = hashBytes(nullptr, 0);
|
||||
CHECK(h.size() == 16);
|
||||
}
|
||||
|
||||
static void testHashBytesLargerBufferDiffersFromSmaller() {
|
||||
// Padding a buffer with a zero byte must change the hash (order + length
|
||||
// sensitivity so two differently-sized WAV files don't accidentally collide).
|
||||
const std::uint8_t short_buf[] = {0xAB, 0xCD};
|
||||
const std::uint8_t long_buf[] = {0xAB, 0xCD, 0x00};
|
||||
CHECK(hashBytes(short_buf, 2) != hashBytes(long_buf, 3));
|
||||
}
|
||||
|
||||
int main() {
|
||||
testNormalizeSlashes();
|
||||
testSanitizeStem();
|
||||
@@ -318,6 +371,11 @@ int main() {
|
||||
testTransitionTwoUnsavedProjectsSwitchLoads();
|
||||
testTransitionFirstSaveOfUnsavedRelocatesButPlanNoOps();
|
||||
testTransitionInPlaceSaveIsNoOp();
|
||||
testHashBytesOutputFormat();
|
||||
testHashBytesDeterministic();
|
||||
testHashBytesDistinct();
|
||||
testHashBytesEmptyBufferIsNonEmpty();
|
||||
testHashBytesLargerBufferDiffersFromSmaller();
|
||||
|
||||
if (g_fail == 0) std::printf("capture_paths: all tests passed\n");
|
||||
else std::printf("capture_paths: %d CHECK(s) FAILED\n", g_fail);
|
||||
|
||||
Reference in New Issue
Block a user