217 lines
6.3 KiB
TypeScript
217 lines
6.3 KiB
TypeScript
/**
|
|
* Tests for @ruvector/rudag
|
|
*/
|
|
|
|
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
import assert from 'node:assert';
|
|
import { RuDag, DagOperator, AttentionMechanism, MemoryStorage, createStorage } from './index';
|
|
|
|
describe('RuDag', () => {
|
|
let dag: RuDag;
|
|
|
|
beforeEach(async () => {
|
|
dag = new RuDag({ storage: new MemoryStorage(), autoSave: false });
|
|
await dag.init();
|
|
});
|
|
|
|
afterEach(() => {
|
|
dag.dispose();
|
|
});
|
|
|
|
test('should create empty DAG', () => {
|
|
assert.strictEqual(dag.nodeCount, 0);
|
|
assert.strictEqual(dag.edgeCount, 0);
|
|
});
|
|
|
|
test('should add nodes', () => {
|
|
const id1 = dag.addNode(DagOperator.SCAN, 10.0);
|
|
const id2 = dag.addNode(DagOperator.FILTER, 2.0);
|
|
|
|
assert.strictEqual(id1, 0);
|
|
assert.strictEqual(id2, 1);
|
|
assert.strictEqual(dag.nodeCount, 2);
|
|
});
|
|
|
|
test('should add edges', () => {
|
|
const n1 = dag.addNode(DagOperator.SCAN, 10.0);
|
|
const n2 = dag.addNode(DagOperator.FILTER, 2.0);
|
|
|
|
const success = dag.addEdge(n1, n2);
|
|
assert.strictEqual(success, true);
|
|
assert.strictEqual(dag.edgeCount, 1);
|
|
});
|
|
|
|
test('should reject cycles', () => {
|
|
const n1 = dag.addNode(DagOperator.SCAN, 1.0);
|
|
const n2 = dag.addNode(DagOperator.FILTER, 1.0);
|
|
const n3 = dag.addNode(DagOperator.PROJECT, 1.0);
|
|
|
|
dag.addEdge(n1, n2);
|
|
dag.addEdge(n2, n3);
|
|
|
|
// This should fail - would create cycle
|
|
const success = dag.addEdge(n3, n1);
|
|
assert.strictEqual(success, false);
|
|
});
|
|
|
|
test('should compute topological sort', () => {
|
|
const n1 = dag.addNode(DagOperator.SCAN, 1.0);
|
|
const n2 = dag.addNode(DagOperator.FILTER, 1.0);
|
|
const n3 = dag.addNode(DagOperator.PROJECT, 1.0);
|
|
|
|
dag.addEdge(n1, n2);
|
|
dag.addEdge(n2, n3);
|
|
|
|
const topo = dag.topoSort();
|
|
assert.deepStrictEqual(topo, [0, 1, 2]);
|
|
});
|
|
|
|
test('should find critical path', () => {
|
|
const n1 = dag.addNode(DagOperator.SCAN, 10.0);
|
|
const n2 = dag.addNode(DagOperator.FILTER, 2.0);
|
|
const n3 = dag.addNode(DagOperator.PROJECT, 1.0);
|
|
|
|
dag.addEdge(n1, n2);
|
|
dag.addEdge(n2, n3);
|
|
|
|
const result = dag.criticalPath();
|
|
assert.deepStrictEqual(result.path, [0, 1, 2]);
|
|
assert.strictEqual(result.cost, 13); // 10 + 2 + 1
|
|
});
|
|
|
|
test('should compute attention scores', () => {
|
|
dag.addNode(DagOperator.SCAN, 1.0);
|
|
dag.addNode(DagOperator.FILTER, 2.0);
|
|
dag.addNode(DagOperator.PROJECT, 3.0);
|
|
|
|
const uniform = dag.attention(AttentionMechanism.UNIFORM);
|
|
assert.strictEqual(uniform.length, 3);
|
|
// All should be approximately 0.333
|
|
assert.ok(Math.abs(uniform[0] - 0.333) < 0.01);
|
|
|
|
const topo = dag.attention(AttentionMechanism.TOPOLOGICAL);
|
|
assert.strictEqual(topo.length, 3);
|
|
|
|
const critical = dag.attention(AttentionMechanism.CRITICAL_PATH);
|
|
assert.strictEqual(critical.length, 3);
|
|
});
|
|
|
|
test('should serialize to JSON', () => {
|
|
dag.addNode(DagOperator.SCAN, 1.0);
|
|
dag.addNode(DagOperator.FILTER, 2.0);
|
|
dag.addEdge(0, 1);
|
|
|
|
const json = dag.toJSON();
|
|
assert.ok(json.includes('nodes'));
|
|
assert.ok(json.includes('edges'));
|
|
});
|
|
|
|
test('should serialize to bytes', () => {
|
|
dag.addNode(DagOperator.SCAN, 1.0);
|
|
dag.addNode(DagOperator.FILTER, 2.0);
|
|
dag.addEdge(0, 1);
|
|
|
|
const bytes = dag.toBytes();
|
|
assert.ok(bytes instanceof Uint8Array);
|
|
assert.ok(bytes.length > 0);
|
|
});
|
|
|
|
test('should round-trip through JSON', async () => {
|
|
const n1 = dag.addNode(DagOperator.SCAN, 10.0);
|
|
const n2 = dag.addNode(DagOperator.FILTER, 2.0);
|
|
dag.addEdge(n1, n2);
|
|
|
|
const json = dag.toJSON();
|
|
const restored = await RuDag.fromJSON(json, { storage: null });
|
|
|
|
assert.strictEqual(restored.nodeCount, 2);
|
|
assert.strictEqual(restored.edgeCount, 1);
|
|
|
|
restored.dispose();
|
|
});
|
|
|
|
test('should round-trip through bytes', async () => {
|
|
const n1 = dag.addNode(DagOperator.SCAN, 10.0);
|
|
const n2 = dag.addNode(DagOperator.FILTER, 2.0);
|
|
dag.addEdge(n1, n2);
|
|
|
|
const bytes = dag.toBytes();
|
|
const restored = await RuDag.fromBytes(bytes, { storage: null });
|
|
|
|
assert.strictEqual(restored.nodeCount, 2);
|
|
assert.strictEqual(restored.edgeCount, 1);
|
|
|
|
restored.dispose();
|
|
});
|
|
});
|
|
|
|
describe('MemoryStorage', () => {
|
|
let storage: MemoryStorage;
|
|
|
|
beforeEach(async () => {
|
|
storage = new MemoryStorage();
|
|
await storage.init();
|
|
});
|
|
|
|
test('should save and retrieve DAG', async () => {
|
|
const data = new Uint8Array([1, 2, 3, 4]);
|
|
await storage.save('test-dag', data, { name: 'Test DAG' });
|
|
|
|
const retrieved = await storage.get('test-dag');
|
|
assert.ok(retrieved);
|
|
assert.strictEqual(retrieved.id, 'test-dag');
|
|
assert.strictEqual(retrieved.name, 'Test DAG');
|
|
assert.deepStrictEqual(Array.from(retrieved.data), [1, 2, 3, 4]);
|
|
});
|
|
|
|
test('should list all DAGs', async () => {
|
|
await storage.save('dag-1', new Uint8Array([1]));
|
|
await storage.save('dag-2', new Uint8Array([2]));
|
|
|
|
const list = await storage.list();
|
|
assert.strictEqual(list.length, 2);
|
|
});
|
|
|
|
test('should delete DAG', async () => {
|
|
await storage.save('to-delete', new Uint8Array([1]));
|
|
assert.ok(await storage.get('to-delete'));
|
|
|
|
await storage.delete('to-delete');
|
|
assert.strictEqual(await storage.get('to-delete'), null);
|
|
});
|
|
|
|
test('should find by name', async () => {
|
|
await storage.save('dag-1', new Uint8Array([1]), { name: 'query' });
|
|
await storage.save('dag-2', new Uint8Array([2]), { name: 'query' });
|
|
await storage.save('dag-3', new Uint8Array([3]), { name: 'other' });
|
|
|
|
const results = await storage.findByName('query');
|
|
assert.strictEqual(results.length, 2);
|
|
});
|
|
|
|
test('should calculate stats', async () => {
|
|
await storage.save('dag-1', new Uint8Array(100));
|
|
await storage.save('dag-2', new Uint8Array(200));
|
|
|
|
const stats = await storage.stats();
|
|
assert.strictEqual(stats.count, 2);
|
|
assert.strictEqual(stats.totalSize, 300);
|
|
});
|
|
|
|
test('should clear all', async () => {
|
|
await storage.save('dag-1', new Uint8Array([1]));
|
|
await storage.save('dag-2', new Uint8Array([2]));
|
|
|
|
await storage.clear();
|
|
const list = await storage.list();
|
|
assert.strictEqual(list.length, 0);
|
|
});
|
|
});
|
|
|
|
describe('createStorage', () => {
|
|
test('should create MemoryStorage in Node.js', () => {
|
|
const storage = createStorage();
|
|
assert.ok(storage instanceof MemoryStorage);
|
|
});
|
|
});
|