Lindenii Project Forge
Login

hare-ds

Data structures for Hare
Commit info
ID
27039807de47fbc2af589f67c5a3f78f0087e228
Author
Runxi Yu <me@runxiyu.org>
Author date
Wed, 17 Sep 2025 01:10:34 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Wed, 17 Sep 2025 01:31:46 +0800
Actions
Add tests
use ds::map;
use errors;

@test fn test() void = {
	const ts: [3]size = [2z, 3z, 8z];
	for (let i = 0z; i < len(ts); i += 1) {
		let m: *map = match (new(ts[i])) {
		case let p: *map => yield p;
		case errors::invalid => abort("btree: invalid t");
		case nomem => abort("btree: nomem");
		};
		defer finish(m);
		map::stress_test(m, 200000);
	};
};
use crypto::random;
use ds::map;
use ds::map::slice_basic;
use ds::map::slice_sorted;
use ds::map::btree;
use ds::map::rbtree;
use ds::map::swiss_siphash;
use errors;

fn mk_slice_basic() (*map::map | nomem) = {
	match (slice_basic::new()) {
	case let p: *slice_basic::map => return (p: *map::map);
	case nomem => return nomem;
	};
};

fn mk_slice_sorted() (*map::map | nomem) = {
	match (slice_sorted::new()) {
	case let p: *slice_sorted::map => return (p: *map::map);
	case nomem => return nomem;
	};
};

fn mk_btree2() (*map::map | nomem) = {
	match (btree::new(2)) {
	case let p: *btree::map => return (p: *map::map);
	case errors::invalid => abort("btree(2) invalid");
	case nomem => return nomem;
	};
};

fn mk_rbtree() (*map::map | nomem) = {
	match (rbtree::new()) {
	case let p: *rbtree::map => return (p: *map::map);
	case nomem => return nomem;
	};
};

fn mk_swiss() (*map::map | nomem) = {
	let key: [16]u8 = [0...];
	random::buffer(&key);
	match (swiss_siphash::new(1, key)) {
	case let p: *swiss_siphash::map => return (p: *map::map);
	case errors::invalid => abort("swiss: invalid");
	case nomem => return nomem;
	};
};

@test fn test() void = {
	const buckets: [2]size = [128z, 256z];
	const makers: [5]*fn() (*map::map | nomem) = [&mk_slice_basic, &mk_slice_sorted, &mk_btree2, &mk_rbtree, &mk_swiss];

	for (let bi = 0z; bi < len(buckets); bi += 1) {
		for (let mi = 0z; mi < len(makers); mi += 1) {
			let m: *map = match (new(makers[mi], buckets[bi])) {
			case let p: *map => yield p;
			case errors::invalid => abort("fnv: invalid");
			case nomem => abort("fnv: nomem");
			};
			defer finish(m);
			map::stress_test(m, 20000);
		};
	};
};
use crypto::random;
use ds::map;
use ds::map::slice_basic;
use ds::map::slice_sorted;
use ds::map::btree;
use ds::map::rbtree;
use ds::map::swiss_siphash;
use errors;
use hash;
use hash::fnv;
use hash::siphash;

fn test_fnv64(_params: nullable *opaque, key: []u8) size = {
	let h = fnv::fnv64a();
	hash::write(&h, key);
	return fnv::sum64(&h): size;
};

fn test_siphash64(params: nullable *opaque, key: []u8) size = {
	let k = match (params) {
	case null => abort("siphash test hash: missing key");
	case let p: *opaque => yield (p: *[16]u8);
	};
	let h = siphash::siphash(2, 4, k);
	defer hash::close(&h);
	hash::write(&h, key);
	return siphash::sum(&h): size;
};

fn mk_slice_basic() (*map::map | nomem) = {
	match (slice_basic::new()) {
	case let p: *slice_basic::map => return (p: *map::map);
	case nomem => return nomem;
	};
};
fn mk_slice_sorted() (*map::map | nomem) = {
	match (slice_sorted::new()) {
	case let p: *slice_sorted::map => return (p: *map::map);
	case nomem => return nomem;
	};
};
fn mk_btree2() (*map::map | nomem) = {
	match (btree::new(2)) {
	case let p: *btree::map => return (p: *map::map);
	case errors::invalid => abort("btree(2) invalid");
	case nomem => return nomem;
	};
};
fn mk_rbtree() (*map::map | nomem) = {
	match (rbtree::new()) {
	case let p: *rbtree::map => return (p: *map::map);
	case nomem => return nomem;
	};
};
fn mk_swiss() (*map::map | nomem) = {
	let key: [16]u8 = [0...];
	random::buffer(&key);
	match (swiss_siphash::new(1, key)) {
	case let p: *swiss_siphash::map => return (p: *map::map);
	case errors::invalid => abort("swiss: invalid");
	case nomem => return nomem;
	};
};

@test fn test() void = {
	const buckets: [2]size = [128z, 256z];
	const makers: [5]*fn() (*map::map | nomem) = [&mk_slice_basic, &mk_slice_sorted, &mk_btree2, &mk_rbtree, &mk_swiss];

	let skey1: [16]u8 = [0...];
	let skey2: [16]u8 = [0...];
	random::buffer(&skey1);
	random::buffer(&skey2);
	const hf: [2]*fn(hash_params: nullable *opaque, key: []u8) size =
		[&test_fnv64, &test_siphash64];
	const hp: [2]nullable *opaque = [null, (&skey1: *opaque)];

	for (let bi = 0z; bi < len(buckets); bi += 1) {
		for (let hi = 0z; hi < len(hf); hi += 1) {
			for (let mi = 0z; mi < len(makers); mi += 1) {
				let m: *map = match (new(makers[mi], buckets[bi], hf[hi], hp[hi])) {
				case let p: *map => yield p;
				case errors::invalid => abort("hashmap: invalid");
				case nomem => abort("hashmap: nomem");
				};
				defer finish(m);
				map::stress_test(m, 20000);
			};
		};
	};
};
use ds::map;

@test fn test() void = {
	let m: *map = match (new()) {
	case let p: *map => yield p;
	case nomem => abort("rbtree: nomem");
	};
	defer finish(m);
	map::stress_test(m, 200000);
};
use crypto::random;
use ds::map;
use ds::map::slice_basic;
use ds::map::slice_sorted;
use ds::map::btree;
use ds::map::rbtree;
use ds::map::swiss_siphash;
use errors;

fn mk_slice_basic() (*map::map | nomem) = {
	match (slice_basic::new()) {
	case let p: *slice_basic::map => return (p: *map::map);
	case nomem => return nomem;
	};
};

fn mk_slice_sorted() (*map::map | nomem) = {
	match (slice_sorted::new()) {
	case let p: *slice_sorted::map => return (p: *map::map);
	case nomem => return nomem;
	};
};

fn mk_btree2() (*map::map | nomem) = {
	match (btree::new(2)) {
	case let p: *btree::map => return (p: *map::map);
	case errors::invalid => abort("btree(2) invalid");
	case nomem => return nomem;
	};
};

fn mk_rbtree() (*map::map | nomem) = {
	match (rbtree::new()) {
	case let p: *rbtree::map => return (p: *map::map);
	case nomem => return nomem;
	};
};

fn mk_swiss() (*map::map | nomem) = {
	let key: [16]u8 = [0...];
	random::buffer(&key);
	match (swiss_siphash::new(1, key)) {
	case let p: *swiss_siphash::map => return (p: *map::map);
	case errors::invalid => abort("swiss: invalid");
	case nomem => return nomem;
	};
};

@test fn test() void = {
	const buckets: [2]size = [128z, 256z];
	const makers: [5]*fn() (*map::map | nomem) = [&mk_slice_basic, &mk_slice_sorted, &mk_btree2, &mk_rbtree, &mk_swiss];

	let key1: [16]u8 = [0...];
	let key2: [16]u8 = [0...];
	random::buffer(&key1);
	random::buffer(&key2);
	const keys: [2]*[16]u8 = [&key1, &key2];

	for (let bi = 0z; bi < len(buckets); bi += 1) {
		for (let ki = 0z; ki < len(keys); ki += 1) {
			for (let mi = 0z; mi < len(makers); mi += 1) {
				let m: *map = match (new(makers[mi], buckets[bi], *keys[ki])) {
				case let p: *map => yield p;
				case errors::invalid => abort("siphash: invalid");
				case nomem => abort("siphash: nomem");
				};
				defer finish(m);
				map::stress_test(m, 20000);
			};
		};
	};
};
use ds::map;

@test fn test() void = {
	let m: *map = match (new()) {
	case let p: *map => yield p;
	case nomem => abort("slice_basic: nomem");
	};
	defer finish(m);
	map::stress_test(m, 2000);
};
use ds::map;

@test fn test() void = {
	let m: *map = match (new()) {
	case let p: *map => yield p;
	case nomem => abort("slice_sorted: nomem");
	};
	defer finish(m);
	map::stress_test(m, 1000);
};
use crypto::random;
use ds::map;
use errors;

@test fn test() void = {
	let key1: [16]u8 = [0...];
	let key2: [16]u8 = [0...];
	random::buffer(&key1);
	random::buffer(&key2);

	const groups: [2]size = [1z, 32z];
	const keys: [2]*[16]u8 = [&key1, &key2];

	for (let gi = 0z; gi < len(groups); gi += 1) {
		for (let ki = 0z; ki < len(keys); ki += 1) {
			let m: *map = match (new(groups[gi], *keys[ki])) {
			case let p: *map => yield p;
			case errors::invalid => abort("swiss_siphash: invalid groups");
			case nomem => abort("swiss_siphash: nomem");
			};
			defer finish(m);
			map::stress_test(m, 200000);
		};
	};
};
// SPDX-License-Identifier: MPL-2.0

def KEY_LEN: size = 16z;

fn put_le64(dst: []u8, x: u64) void = {
	for (let i = 0z; i < 8z; i += 1) {
		dst[i] = ((x >> (8u64 * (i: u64))) & 0xFFu64): u8;
	};
};

type oracle = []nullable *opaque;

fn must_set(m: *map, key: []u8, v: *opaque) void = {
	match (set(m, key, v)) {
	case void => void;
	case nomem => abort("set: out of memory");
	};
};
fn must_get(m: *map, key: []u8) (*opaque | void) = get(m, key);
fn must_del(m: *map, key: []u8) (*opaque | void) = del(m, key);

export fn stress_test(m: *map, key_space: size) void = {
	let empty: [KEY_LEN]u8 = [0...];
	let keybufs: [][KEY_LEN]u8 = alloc([empty...], key_space)!;
	let keys: [][]u8 = alloc([[0...]...], key_space)!;

	for (let i = 0z; i < key_space; i += 1) {
		for (let j = 8z; j < KEY_LEN; j += 1) keybufs[i][j] = 0xABu8;
		put_le64((keybufs[i][..8]), (i: u64));
		keys[i] = keybufs[i][..];
	};

	let vals: []int = alloc([0...], key_space)!;
	for (let i = 0z; i < key_space; i += 1) vals[i] = (i: int);

	let exp: oracle = alloc([null...], key_space)!;

	// Sequential inserts with immediate verification
	for (let i = 0z; i < key_space; i += 1) {
		let vp: *opaque = (&vals[i]: *opaque);
		must_set(m, keys[i], vp);
		exp[i] = vp;

		match (must_get(m, keys[i])) {
		case let got: *opaque => assert(got == vp, "phase1: get != set");
		case void => abort("phase1: get void after set");
		};
	};

	// Forward read sweep (all should be present)
	for (let i = 0z; i < key_space; i += 1) {
		match (must_get(m, keys[i])) {
		case let got: *opaque =>
			let want = match (exp[i]) {
			case null => abort("phase2: expect null but found value");
			case let p: *opaque => yield p;
			};
			assert(got == want, "phase2: value mismatch");
		case void =>
			abort("phase2: get void but expect value");
		};
	};

	// Strided overwrites with immediate verification
	for (let step = 0z; step < key_space; step += 1) {
		let i = (step * 7z) % key_space;
		let new_ix = (i + 12345z) % key_space;
		let vp: *opaque = (&vals[new_ix]: *opaque);
		must_set(m, keys[i], vp);
		exp[i] = vp;

		match (must_get(m, keys[i])) {
		case let got: *opaque => assert(got == vp, "phase3: replace mismatch");
		case void => abort("phase3: get void after replace");
		};
	};

	// Sparse deletes, every 3rd key
	for (let i = 0z; i < key_space; i += 1) {
		if (i % 3z != 0z) continue;

		let want = exp[i];
		match (must_del(m, keys[i])) {
		case let ret: *opaque =>
			match (want) {
			case null => abort("phase4: del returned value but expect null");
			case let p: *opaque => assert(ret == p, "phase4: del value mismatch");
			};
			exp[i] = null;

			match (must_del(m, keys[i])) {
			case void => void;
			case *opaque => abort("phase4: second delete returned value");
			};
		case void =>
			match (want) {
			case null => void;
			case *opaque => abort("phase4: del void but expect value");
			};
		};
	};

	// Insert again every 6th key
	for (let i = 0z; i < key_space; i += 1) {
		if (i % 6z != 0z) continue;
		let vp: *opaque = (&vals[(i * 5z + 1z) % key_space]: *opaque);
		must_set(m, keys[i], vp);
		exp[i] = vp;
	};

	// Even indices read, odd indices write
	for (let i = 0z; i < key_space; i += 1) {
		if ((i & 1z) == 0z) {
			// Read check
			match (must_get(m, keys[i])) {
			case let got: *opaque =>
				match (exp[i]) {
				case null => abort("phase6: even get found value; expect null");
				case let p: *opaque => assert(got == p, "phase6: even mismatch");
				};
			case void =>
				match (exp[i]) {
				case null => void;
				case *opaque => abort("phase6: even get void; expect value");
				};
			};
		} else {
			// Write (overwrite or create)
			let vp: *opaque = (&vals[(i * 3z + 7z) % key_space]: *opaque);
			must_set(m, keys[i], vp);
			exp[i] = vp;
		};
	};

	// Reading in reverse order
	for (let r = key_space; r > 0z; r -= 1) {
		let i = r - 1z;
		match (must_get(m, keys[i])) {
		case let got: *opaque =>
			match (exp[i]) {
			case null => abort("phase7: get found value; expect null");
			case let p: *opaque => assert(got == p, "phase7: reverse mismatch");
			};
		case void =>
			match (exp[i]) {
			case null => void;
			case *opaque => abort("phase7: reverse get void; expect value");
			};
		};
	};

	// Clear in reverse order
	for (let r = key_space; r > 0z; r -= 1) {
		let i = r - 1z;
		match (must_del(m, keys[i])) {
		case let ret: *opaque =>
			match (exp[i]) {
			case null => abort("phase8: del returned value; expect null");
			case let p: *opaque => assert(ret == p, "phase8: final del mismatch");
			};
			exp[i] = null;
		case void =>
			match (exp[i]) {
			case null => void;
			case *opaque => abort("phase8: del void; expect value");
			};
		};
	};

	// Read sweep ensure empty
	for (let i = 0z; i < key_space; i += 1) {
		match (must_get(m, keys[i])) {
		case void => void;
		case *opaque => abort("final: get returned value after full clear");
		};
		match (must_del(m, keys[i])) {
		case void => void;
		case *opaque => abort("final: del returned value after full clear");
		};
	};
};