549 lines
17 KiB
JavaScript
549 lines
17 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* WASM interface tests (run after WASM build)
|
|
* Tests the WebAssembly integration and performance
|
|
* Run with: node tests/integration/wasm.test.js
|
|
*/
|
|
|
|
const { strict: assert } = require('assert');
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
class WASMTestRunner {
|
|
constructor() {
|
|
this.tests = [];
|
|
this.passed = 0;
|
|
this.failed = 0;
|
|
this.verbose = process.argv.includes('--verbose');
|
|
this.wasmBuilt = false;
|
|
this.solverModule = null;
|
|
}
|
|
|
|
async setup() {
|
|
// Check if WASM has been built
|
|
const wasmPkgPath = path.join(__dirname, '../../pkg');
|
|
const jsWrapperPath = path.join(__dirname, '../../js/solver.js');
|
|
|
|
try {
|
|
await fs.access(wasmPkgPath);
|
|
await fs.access(jsWrapperPath);
|
|
this.wasmBuilt = true;
|
|
|
|
// Try to import the solver module
|
|
try {
|
|
this.solverModule = await import(jsWrapperPath);
|
|
} catch (error) {
|
|
console.warn('Warning: Could not import solver module:', error.message);
|
|
this.wasmBuilt = false;
|
|
}
|
|
} catch (error) {
|
|
this.wasmBuilt = false;
|
|
}
|
|
}
|
|
|
|
test(name, fn) {
|
|
this.tests.push({ name, fn });
|
|
}
|
|
|
|
async run() {
|
|
console.log('🧪 Running WASM Interface Tests');
|
|
console.log('================================\n');
|
|
|
|
await this.setup();
|
|
|
|
if (!this.wasmBuilt) {
|
|
console.log('⚠️ WASM package not built. Run the following to build:');
|
|
console.log(' 1. Install Rust: curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh');
|
|
console.log(' 2. Add WASM target: rustup target add wasm32-unknown-unknown');
|
|
console.log(' 3. Install wasm-pack: cargo install wasm-pack');
|
|
console.log(' 4. Build WASM: ./scripts/build.sh');
|
|
console.log('\n📝 Running mock tests instead...\n');
|
|
}
|
|
|
|
for (const { name, fn } of this.tests) {
|
|
try {
|
|
await fn();
|
|
this.passed++;
|
|
console.log(`✅ ${name}`);
|
|
} catch (error) {
|
|
this.failed++;
|
|
console.log(`❌ ${name}`);
|
|
if (this.verbose) {
|
|
console.log(` Error: ${error.message}`);
|
|
console.log(` Stack: ${error.stack}\n`);
|
|
} else {
|
|
console.log(` Error: ${error.message}\n`);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.printSummary();
|
|
return this.failed === 0;
|
|
}
|
|
|
|
printSummary() {
|
|
console.log('\n📊 Test Summary');
|
|
console.log('===============');
|
|
console.log(`✅ Passed: ${this.passed}`);
|
|
console.log(`❌ Failed: ${this.failed}`);
|
|
console.log(`📈 Total: ${this.tests.length}`);
|
|
console.log(`🎯 Success Rate: ${((this.passed / this.tests.length) * 100).toFixed(1)}%`);
|
|
|
|
if (!this.wasmBuilt) {
|
|
console.log('\n🔧 Build Requirements:');
|
|
console.log(' • Rust toolchain (rustc, cargo)');
|
|
console.log(' • wasm-pack');
|
|
console.log(' • wasm32-unknown-unknown target');
|
|
console.log(' • Run: npm run build');
|
|
}
|
|
}
|
|
|
|
// Create a mock WASM interface for testing when WASM is not built
|
|
createMockWASMInterface() {
|
|
return {
|
|
Matrix: class {
|
|
constructor(data, rows, cols) {
|
|
this.data = data instanceof Float64Array ? data : new Float64Array(data);
|
|
this.rows = rows;
|
|
this.cols = cols;
|
|
}
|
|
|
|
static zeros(rows, cols) {
|
|
return new this(new Float64Array(rows * cols), rows, cols);
|
|
}
|
|
|
|
static identity(size) {
|
|
const data = new Float64Array(size * size);
|
|
for (let i = 0; i < size; i++) {
|
|
data[i * size + i] = 1.0;
|
|
}
|
|
return new this(data, size, size);
|
|
}
|
|
|
|
get(row, col) {
|
|
return this.data[row * this.cols + col];
|
|
}
|
|
|
|
set(row, col, value) {
|
|
this.data[row * this.cols + col] = value;
|
|
}
|
|
},
|
|
|
|
SublinearSolver: class {
|
|
constructor(config = {}) {
|
|
this.config = config;
|
|
this.initialized = false;
|
|
}
|
|
|
|
async initialize() {
|
|
this.initialized = true;
|
|
}
|
|
|
|
async solve(matrix, vector) {
|
|
if (!this.initialized) await this.initialize();
|
|
// Mock solution: identity mapping
|
|
return new Float64Array(vector);
|
|
}
|
|
|
|
getMemoryUsage() {
|
|
return {
|
|
used: 1024,
|
|
capacity: 2048,
|
|
js: { allocations: 0, totalBytes: 0 }
|
|
};
|
|
}
|
|
|
|
dispose() {
|
|
this.initialized = false;
|
|
}
|
|
},
|
|
|
|
Utils: {
|
|
async getFeatures() {
|
|
return { simd: false, threads: 1, mock: true };
|
|
},
|
|
|
|
async isSIMDEnabled() {
|
|
return false;
|
|
},
|
|
|
|
async benchmarkMatrixMultiply(size) {
|
|
return { time: size * 0.001, operations: size * size };
|
|
},
|
|
|
|
async getWasmMemoryUsage() {
|
|
return { used: 0, total: 0 };
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
getModule() {
|
|
return this.wasmBuilt ? this.solverModule : this.createMockWASMInterface();
|
|
}
|
|
}
|
|
|
|
const runner = new WASMTestRunner();
|
|
|
|
// WASM Build Verification Tests
|
|
runner.test('WASM package structure exists', async () => {
|
|
if (!runner.wasmBuilt) {
|
|
// Mock test - verify expected structure would exist
|
|
const expectedFiles = [
|
|
'pkg/sublinear_time_solver.js',
|
|
'pkg/sublinear_time_solver_bg.wasm',
|
|
'pkg/sublinear_time_solver.d.ts',
|
|
'pkg/package.json'
|
|
];
|
|
|
|
console.log(' Expected files after build:', expectedFiles.join(', '));
|
|
return; // Skip actual verification
|
|
}
|
|
|
|
const pkgPath = path.join(__dirname, '../../pkg');
|
|
const files = await fs.readdir(pkgPath);
|
|
|
|
// Check for essential WASM files
|
|
assert.ok(files.some(f => f.endsWith('.wasm')));
|
|
assert.ok(files.some(f => f.endsWith('.js')));
|
|
assert.ok(files.some(f => f.endsWith('.d.ts')));
|
|
assert.ok(files.includes('package.json'));
|
|
});
|
|
|
|
runner.test('JavaScript wrapper exists and is importable', async () => {
|
|
const module = runner.getModule();
|
|
assert.ok(module);
|
|
|
|
if (runner.wasmBuilt) {
|
|
assert.ok(module.Matrix);
|
|
assert.ok(module.SublinearSolver);
|
|
assert.ok(module.Utils);
|
|
} else {
|
|
// Mock verification
|
|
assert.ok(module.Matrix);
|
|
assert.ok(module.SublinearSolver);
|
|
assert.ok(module.Utils);
|
|
}
|
|
});
|
|
|
|
// WASM Matrix Interface Tests
|
|
runner.test('WASM Matrix creation and basic operations', async () => {
|
|
const module = runner.getModule();
|
|
const { Matrix } = module;
|
|
|
|
// Test matrix creation
|
|
const matrix = new Matrix([1, 2, 3, 4], 2, 2);
|
|
assert.equal(matrix.rows, 2);
|
|
assert.equal(matrix.cols, 2);
|
|
assert.equal(matrix.get(0, 0), 1);
|
|
assert.equal(matrix.get(1, 1), 4);
|
|
|
|
// Test static methods
|
|
const zeros = Matrix.zeros(3, 3);
|
|
assert.equal(zeros.rows, 3);
|
|
assert.equal(zeros.get(1, 1), 0);
|
|
|
|
const identity = Matrix.identity(2);
|
|
assert.equal(identity.get(0, 0), 1);
|
|
assert.equal(identity.get(0, 1), 0);
|
|
assert.equal(identity.get(1, 0), 0);
|
|
assert.equal(identity.get(1, 1), 1);
|
|
});
|
|
|
|
runner.test('WASM Matrix memory efficiency', async () => {
|
|
const module = runner.getModule();
|
|
const { Matrix } = module;
|
|
|
|
const size = 100;
|
|
const matrix = Matrix.zeros(size, size);
|
|
|
|
assert.ok(matrix.data instanceof Float64Array);
|
|
assert.equal(matrix.data.length, size * size);
|
|
|
|
if (runner.wasmBuilt) {
|
|
// In real WASM, memory should be efficiently managed
|
|
assert.equal(matrix.data.byteLength, size * size * 8);
|
|
}
|
|
});
|
|
|
|
// WASM Solver Interface Tests
|
|
runner.test('WASM SublinearSolver initialization', async () => {
|
|
const module = runner.getModule();
|
|
const { SublinearSolver } = module;
|
|
|
|
const solver = new SublinearSolver({
|
|
maxIterations: 1000,
|
|
tolerance: 1e-10,
|
|
simdEnabled: true
|
|
});
|
|
|
|
await solver.initialize();
|
|
assert.equal(solver.initialized, true);
|
|
});
|
|
|
|
runner.test('WASM SublinearSolver basic solve operation', async () => {
|
|
const module = runner.getModule();
|
|
const { SublinearSolver, Matrix } = module;
|
|
|
|
const solver = new SublinearSolver();
|
|
const matrix = Matrix.identity(3);
|
|
const vector = new Float64Array([1, 2, 3]);
|
|
|
|
const solution = await solver.solve(matrix, vector);
|
|
|
|
assert.ok(solution instanceof Float64Array);
|
|
assert.equal(solution.length, 3);
|
|
|
|
if (runner.wasmBuilt) {
|
|
// With real WASM, we expect accurate solutions
|
|
// For identity matrix, solution should equal input vector
|
|
assert.ok(Math.abs(solution[0] - 1) < 1e-10);
|
|
assert.ok(Math.abs(solution[1] - 2) < 1e-10);
|
|
assert.ok(Math.abs(solution[2] - 3) < 1e-10);
|
|
}
|
|
});
|
|
|
|
runner.test('WASM memory usage tracking', async () => {
|
|
const module = runner.getModule();
|
|
const { SublinearSolver } = module;
|
|
|
|
const solver = new SublinearSolver();
|
|
await solver.initialize();
|
|
|
|
const memoryUsage = solver.getMemoryUsage();
|
|
assert.ok(typeof memoryUsage.used === 'number');
|
|
assert.ok(typeof memoryUsage.capacity === 'number');
|
|
assert.ok(memoryUsage.js);
|
|
|
|
if (runner.wasmBuilt) {
|
|
assert.ok(memoryUsage.used > 0);
|
|
assert.ok(memoryUsage.capacity > 0);
|
|
}
|
|
});
|
|
|
|
// WASM Utils Interface Tests
|
|
runner.test('WASM Utils feature detection', async () => {
|
|
const module = runner.getModule();
|
|
const { Utils } = module;
|
|
|
|
const features = await Utils.getFeatures();
|
|
assert.ok(typeof features === 'object');
|
|
|
|
if (runner.wasmBuilt) {
|
|
assert.ok(typeof features.simd === 'boolean');
|
|
assert.ok(typeof features.threads === 'number');
|
|
} else {
|
|
assert.ok(features.mock === true);
|
|
}
|
|
});
|
|
|
|
runner.test('WASM Utils SIMD detection', async () => {
|
|
const module = runner.getModule();
|
|
const { Utils } = module;
|
|
|
|
const simdEnabled = await Utils.isSIMDEnabled();
|
|
assert.ok(typeof simdEnabled === 'boolean');
|
|
});
|
|
|
|
runner.test('WASM Utils matrix multiply benchmark', async () => {
|
|
const module = runner.getModule();
|
|
const { Utils } = module;
|
|
|
|
const result = await Utils.benchmarkMatrixMultiply(100);
|
|
assert.ok(typeof result.time === 'number');
|
|
assert.ok(typeof result.operations === 'number');
|
|
assert.ok(result.time > 0);
|
|
assert.ok(result.operations > 0);
|
|
});
|
|
|
|
runner.test('WASM Utils memory usage', async () => {
|
|
const module = runner.getModule();
|
|
const { Utils } = module;
|
|
|
|
const memoryUsage = await Utils.getWasmMemoryUsage();
|
|
assert.ok(typeof memoryUsage === 'object');
|
|
assert.ok(typeof memoryUsage.used === 'number');
|
|
assert.ok(typeof memoryUsage.total === 'number');
|
|
});
|
|
|
|
// WASM Performance Tests
|
|
runner.test('WASM vs JS performance comparison', async () => {
|
|
const module = runner.getModule();
|
|
const { Matrix, SublinearSolver } = module;
|
|
|
|
const size = 50;
|
|
const matrix = Matrix.identity(size);
|
|
const vector = new Float64Array(size).fill(1);
|
|
|
|
// Time WASM solver
|
|
const solver = new SublinearSolver();
|
|
const startTime = Date.now();
|
|
await solver.solve(matrix, vector);
|
|
const wasmTime = Date.now() - startTime;
|
|
|
|
assert.ok(wasmTime >= 0);
|
|
|
|
if (runner.wasmBuilt) {
|
|
// WASM should be reasonably fast
|
|
assert.ok(wasmTime < 1000, `WASM solve took too long: ${wasmTime}ms`);
|
|
}
|
|
|
|
console.log(` WASM solve time: ${wasmTime}ms`);
|
|
});
|
|
|
|
runner.test('WASM large matrix handling', async () => {
|
|
const module = runner.getModule();
|
|
const { Matrix, SublinearSolver } = module;
|
|
|
|
const size = runner.wasmBuilt ? 200 : 50; // Smaller for mock tests
|
|
const matrix = Matrix.identity(size);
|
|
const vector = new Float64Array(size).fill(1);
|
|
|
|
const solver = new SublinearSolver({
|
|
maxIterations: 100,
|
|
tolerance: 1e-8
|
|
});
|
|
|
|
const solution = await solver.solve(matrix, vector);
|
|
assert.equal(solution.length, size);
|
|
|
|
const memoryUsage = solver.getMemoryUsage();
|
|
assert.ok(memoryUsage.used > 0);
|
|
|
|
console.log(` Matrix size: ${size}x${size}, Memory used: ${memoryUsage.used} bytes`);
|
|
});
|
|
|
|
// WASM Error Handling Tests
|
|
runner.test('WASM graceful error handling', async () => {
|
|
const module = runner.getModule();
|
|
const { SublinearSolver } = module;
|
|
|
|
const solver = new SublinearSolver();
|
|
|
|
if (runner.wasmBuilt) {
|
|
// Test with incompatible matrix/vector dimensions
|
|
try {
|
|
const matrix = module.Matrix.identity(3);
|
|
const vector = new Float64Array([1, 2]); // Wrong size
|
|
|
|
await solver.solve(matrix, vector);
|
|
assert.fail('Should have thrown error for dimension mismatch');
|
|
} catch (error) {
|
|
assert.ok(error.message.length > 0);
|
|
}
|
|
} else {
|
|
// Mock test - just verify error handling structure exists
|
|
assert.ok(typeof solver.solve === 'function');
|
|
}
|
|
});
|
|
|
|
// WASM Resource Cleanup Tests
|
|
runner.test('WASM resource cleanup', async () => {
|
|
const module = runner.getModule();
|
|
const { SublinearSolver } = module;
|
|
|
|
const solver = new SublinearSolver();
|
|
await solver.initialize();
|
|
|
|
const memoryBefore = solver.getMemoryUsage();
|
|
assert.ok(memoryBefore.used >= 0);
|
|
|
|
solver.dispose();
|
|
assert.equal(solver.initialized, false);
|
|
|
|
if (runner.wasmBuilt) {
|
|
// After disposal, memory should be cleaned up
|
|
// Note: This test might need adjustment based on actual WASM implementation
|
|
const memoryAfter = solver.getMemoryUsage();
|
|
assert.ok(memoryAfter.used >= 0);
|
|
}
|
|
});
|
|
|
|
// WASM Integration Tests
|
|
runner.test('WASM full workflow integration', async () => {
|
|
const module = runner.getModule();
|
|
const { Matrix, SublinearSolver } = module;
|
|
|
|
// Create a linear system
|
|
const size = 4;
|
|
const matrix = Matrix.identity(size);
|
|
matrix.set(0, 1, 0.5);
|
|
matrix.set(1, 0, 0.5);
|
|
|
|
const vector = new Float64Array([1, 2, 3, 4]);
|
|
|
|
// Solve the system
|
|
const solver = new SublinearSolver({
|
|
maxIterations: 100,
|
|
tolerance: 1e-10
|
|
});
|
|
|
|
const solution = await solver.solve(matrix, vector);
|
|
|
|
// Verify solution
|
|
assert.equal(solution.length, size);
|
|
|
|
// Check memory usage
|
|
const memory = solver.getMemoryUsage();
|
|
assert.ok(memory.used > 0);
|
|
|
|
// Get features
|
|
const features = await module.Utils.getFeatures();
|
|
assert.ok(features);
|
|
|
|
// Cleanup
|
|
solver.dispose();
|
|
assert.equal(solver.initialized, false);
|
|
|
|
console.log(` Features: ${JSON.stringify(features)}`);
|
|
console.log(` Memory used: ${memory.used} bytes`);
|
|
});
|
|
|
|
// WASM Build Information Tests
|
|
runner.test('WASM build information validation', async () => {
|
|
if (!runner.wasmBuilt) {
|
|
console.log(' Would validate build info after WASM build');
|
|
return;
|
|
}
|
|
|
|
const pkgPath = path.join(__dirname, '../../pkg/package.json');
|
|
try {
|
|
const content = await fs.readFile(pkgPath, 'utf8');
|
|
const pkg = JSON.parse(content);
|
|
|
|
assert.ok(pkg.name);
|
|
assert.ok(pkg.version);
|
|
assert.ok(pkg.files);
|
|
} catch (error) {
|
|
console.warn(' Could not read package.json from pkg directory');
|
|
}
|
|
|
|
// Check for build info if available
|
|
const buildInfoPath = path.join(__dirname, '../../pkg/build_info.json');
|
|
try {
|
|
const content = await fs.readFile(buildInfoPath, 'utf8');
|
|
const buildInfo = JSON.parse(content);
|
|
|
|
assert.ok(buildInfo.build_date);
|
|
assert.ok(buildInfo.rust_version);
|
|
assert.ok(buildInfo.target);
|
|
|
|
console.log(` Build date: ${buildInfo.build_date}`);
|
|
console.log(` Rust version: ${buildInfo.rust_version}`);
|
|
} catch (error) {
|
|
console.log(' Build info not available (expected for mock tests)');
|
|
}
|
|
});
|
|
|
|
// Run all tests
|
|
if (require.main === module) {
|
|
runner.run().then(success => {
|
|
process.exit(success ? 0 : 1);
|
|
}).catch(error => {
|
|
console.error('Test runner failed:', error);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
module.exports = { WASMTestRunner, runner }; |