js-waku/packages/sds/src/bloom.spec.ts
Arseniy Klempner be93e4b71f
feat(sds): migrate bloomfilter to bigint and import hashn function from nim
Uses an array of bigint to store sufficient bits in bloom filter.
Updates all arithmetic to explicitly cast to bigint where necessary.
Makes the hashn function for bloomfilter a parameter.
Adds an implementation of hashn generated using nim compiler.
Adds tests.
2025-02-05 17:27:41 -08:00

158 lines
4.1 KiB
TypeScript

import { expect } from "chai";
import { BloomFilter } from "./bloom.js";
import { hashN } from "./nim_hashn/nim_hashn.mjs";
const n = 10000;
const sampleChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const specialPatterns = [
"shortstr",
"a".repeat(1000), // Very long string
"special@#$%^&*()", // Special characters
"unicode→★∑≈", // Unicode characters
"pattern".repeat(10) // Repeating pattern
];
describe("BloomFilter", () => {
let bloomFilter: BloomFilter;
let testElements: string[];
beforeEach(() => {
bloomFilter = new BloomFilter(
{
capacity: n,
errorRate: 0.001
},
hashN
);
testElements = new Array<string>(n);
for (let i = 0; i < n; i++) {
let newString = "";
for (let j = 0; j < 7; j++) {
newString += sampleChars[Math.floor(Math.random() * 51)];
}
testElements[i] = newString;
}
for (const item of testElements) {
bloomFilter.insert(item);
}
expect(bloomFilter.lookup("nonexistent")).to.equal(
false,
"look up for an element yet to be added should return false"
);
expect(bloomFilter.lookup(testElements[0])).to.equal(
true,
"look up for an element that was added should return true"
);
});
it("should initialize bloom filter with correct parameters", () => {
expect(bloomFilter.kHashes).to.equal(10);
expect(bloomFilter.totalBits / n).to.equal(15);
const bloomFilter2 = new BloomFilter(
{
capacity: 10000,
errorRate: 0.001,
kHashes: 4,
forceNBitsPerElem: 20
},
hashN
);
expect(bloomFilter2.kHashes).to.equal(4);
expect(bloomFilter2.totalBits).to.equal(200000);
});
it("should insert elements correctly", () => {
expect(bloomFilter.lookup("test string")).to.equal(
false,
"look up for an element yet to be added should return false"
);
bloomFilter.insert("test string");
expect(bloomFilter.lookup("test string")).to.equal(
true,
"look up for an element that was added should return true"
);
expect(bloomFilter.lookup("different string")).to.equal(
false,
"look up for an element that was not added should return false"
);
});
it("should maintain desired error rate", () => {
let falsePositives = 0;
const testSize = n / 2;
for (let i = 0; i < testSize; i++) {
let testString = "";
for (let j = 0; j < 8; j++) {
// Different length than setup
testString += sampleChars[Math.floor(Math.random() * 51)];
}
if (bloomFilter.lookup(testString)) {
falsePositives++;
}
}
const actualErrorRate = falsePositives / testSize;
expect(actualErrorRate).to.be.lessThan(bloomFilter.errorRate * 1.5);
});
it("should never report false negatives", () => {
for (const item of testElements) {
expect(bloomFilter.lookup(item)).to.equal(true);
}
});
});
describe("BloomFilter with special patterns", () => {
let bloomFilter: BloomFilter;
const inserted: string[] = [];
beforeEach(() => {
bloomFilter = new BloomFilter(
{
capacity: n,
errorRate: 0.001
},
hashN
);
});
it("should handle special patterns correctly", () => {
for (const pattern of specialPatterns) {
bloomFilter.insert(pattern);
expect(bloomFilter.lookup(pattern)).to.equal(true);
}
});
it("should handle general insertion and lookup correctly", () => {
for (let i = 0; i < n; i++) {
inserted[i] = `${i}test${Math.random().toString(36).substring(2, 15)}`;
bloomFilter.insert(inserted[i]);
}
for (const item of inserted) {
expect(bloomFilter.lookup(item)).to.equal(true);
}
});
it("should check false positive rate", () => {
const testSize = n / 2;
let falsePositives = 0;
for (let i = 0; i < testSize; i++) {
const testItem = `notpresent${i}${Math.random().toString(36).substring(2, 15)}`;
if (bloomFilter.lookup(testItem)) {
falsePositives++;
}
}
const fpRate = falsePositives / testSize;
expect(fpRate).to.be.lessThan(bloomFilter.errorRate * 1.5);
});
});