mirror of
https://github.com/ruvnet/RuView
synced 2026-08-04 19:31:42 +00:00
feat: vendor midstream and sublinear-time-solver libraries
Add ruvnet/midstream (AIMDS real-time inference) and ruvnet/sublinear-time-solver (sublinear optimization algorithms) as vendored dependencies under vendor/. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,511 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Integration tests for CLI functionality
|
||||
* Run with: node tests/integration/cli.test.js
|
||||
*/
|
||||
|
||||
const { strict: assert } = require('assert');
|
||||
const { spawn, exec } = require('child_process');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
class CLITestRunner {
|
||||
constructor() {
|
||||
this.tests = [];
|
||||
this.passed = 0;
|
||||
this.failed = 0;
|
||||
this.verbose = process.argv.includes('--verbose');
|
||||
this.tempDir = null;
|
||||
this.cliPath = path.join(__dirname, '../../bin/cli.js');
|
||||
}
|
||||
|
||||
async setup() {
|
||||
// Create temporary directory for test files
|
||||
this.tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sublinear-test-'));
|
||||
|
||||
// Create test matrix files
|
||||
await this.createTestMatrices();
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
if (this.tempDir) {
|
||||
try {
|
||||
await fs.rm(this.tempDir, { recursive: true, force: true });
|
||||
} catch (error) {
|
||||
console.warn('Failed to cleanup temp directory:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createTestMatrices() {
|
||||
// Create a simple 2x2 matrix in JSON format
|
||||
const matrix2x2 = {
|
||||
rows: 2,
|
||||
cols: 2,
|
||||
data: [2, 1, 1, 2],
|
||||
format: 'dense'
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(this.tempDir, 'matrix2x2.json'),
|
||||
JSON.stringify(matrix2x2, null, 2)
|
||||
);
|
||||
|
||||
// Create corresponding vector
|
||||
const vector2x2 = [3, 3];
|
||||
await fs.writeFile(
|
||||
path.join(this.tempDir, 'vector2x2.json'),
|
||||
JSON.stringify(vector2x2, null, 2)
|
||||
);
|
||||
|
||||
// Create a CSV matrix
|
||||
const csvMatrix = '1,0,0\n0,1,0\n0,0,1';
|
||||
await fs.writeFile(
|
||||
path.join(this.tempDir, 'identity3x3.csv'),
|
||||
csvMatrix
|
||||
);
|
||||
|
||||
// Create Matrix Market format
|
||||
const mtxMatrix = `%%MatrixMarket matrix coordinate real general
|
||||
3 3 3
|
||||
1 1 1.0
|
||||
2 2 1.0
|
||||
3 3 1.0`;
|
||||
await fs.writeFile(
|
||||
path.join(this.tempDir, 'identity3x3.mtx'),
|
||||
mtxMatrix
|
||||
);
|
||||
|
||||
// Create a larger sparse matrix in COO format
|
||||
const sparseMatrix = {
|
||||
rows: 5,
|
||||
cols: 5,
|
||||
entries: 8,
|
||||
data: {
|
||||
values: [4, -1, -1, 4, -1, -1, 4, -1],
|
||||
rowIndices: [0, 0, 1, 1, 1, 2, 2, 2],
|
||||
colIndices: [0, 1, 0, 1, 2, 1, 2, 3]
|
||||
},
|
||||
format: 'coo'
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(this.tempDir, 'sparse5x5.json'),
|
||||
JSON.stringify(sparseMatrix, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
test(name, fn) {
|
||||
this.tests.push({ name, fn });
|
||||
}
|
||||
|
||||
async run() {
|
||||
console.log('🧪 Running CLI Integration Tests');
|
||||
console.log('================================\n');
|
||||
|
||||
await this.setup();
|
||||
|
||||
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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.cleanup();
|
||||
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)}%`);
|
||||
}
|
||||
|
||||
// Helper method to execute CLI commands
|
||||
async execCLI(args, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn('node', [this.cliPath, ...args], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
...options
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
resolve({
|
||||
code,
|
||||
stdout,
|
||||
stderr
|
||||
});
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
// Set timeout to prevent hanging tests
|
||||
setTimeout(() => {
|
||||
child.kill('SIGTERM');
|
||||
reject(new Error('CLI command timed out'));
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const runner = new CLITestRunner();
|
||||
|
||||
// Basic CLI Tests
|
||||
runner.test('CLI displays help message', async () => {
|
||||
const result = await runner.execCLI(['--help']);
|
||||
|
||||
assert.equal(result.code, 0);
|
||||
assert.ok(result.stdout.includes('Advanced Sublinear Time Sparse Linear System Solver'));
|
||||
assert.ok(result.stdout.includes('solve'));
|
||||
assert.ok(result.stdout.includes('serve'));
|
||||
assert.ok(result.stdout.includes('benchmark'));
|
||||
});
|
||||
|
||||
runner.test('CLI displays version', async () => {
|
||||
const result = await runner.execCLI(['--version']);
|
||||
|
||||
// Version command might exit with 0 or display version in help
|
||||
assert.ok(result.code === 0 || result.stdout.length > 0);
|
||||
});
|
||||
|
||||
runner.test('CLI handles invalid command', async () => {
|
||||
const result = await runner.execCLI(['invalid-command']);
|
||||
|
||||
// Should exit with non-zero code for invalid commands
|
||||
assert.notEqual(result.code, 0);
|
||||
});
|
||||
|
||||
// Solve Command Tests
|
||||
runner.test('CLI solve command requires matrix file', async () => {
|
||||
const result = await runner.execCLI(['solve']);
|
||||
|
||||
assert.notEqual(result.code, 0);
|
||||
assert.ok(result.stderr.includes('required') || result.stdout.includes('required'));
|
||||
});
|
||||
|
||||
runner.test('CLI solve command with valid matrix (should fail gracefully without WASM)', async () => {
|
||||
const matrixFile = path.join(runner.tempDir, 'matrix2x2.json');
|
||||
const result = await runner.execCLI(['solve', '-m', matrixFile]);
|
||||
|
||||
// This should fail because WASM isn't built, but it should fail gracefully
|
||||
assert.notEqual(result.code, 0);
|
||||
// Should show a helpful error message
|
||||
assert.ok(result.stderr.length > 0 || result.stdout.includes('Error'));
|
||||
});
|
||||
|
||||
runner.test('CLI solve command with output file specification', async () => {
|
||||
const matrixFile = path.join(runner.tempDir, 'matrix2x2.json');
|
||||
const outputFile = path.join(runner.tempDir, 'solution.json');
|
||||
|
||||
const result = await runner.execCLI([
|
||||
'solve',
|
||||
'-m', matrixFile,
|
||||
'-o', outputFile
|
||||
]);
|
||||
|
||||
// Should fail gracefully without WASM but show proper argument parsing
|
||||
assert.notEqual(result.code, 0);
|
||||
});
|
||||
|
||||
runner.test('CLI solve command with custom parameters', async () => {
|
||||
const matrixFile = path.join(runner.tempDir, 'matrix2x2.json');
|
||||
|
||||
const result = await runner.execCLI([
|
||||
'solve',
|
||||
'-m', matrixFile,
|
||||
'--method', 'cg',
|
||||
'--tolerance', '1e-8',
|
||||
'--max-iterations', '500'
|
||||
]);
|
||||
|
||||
// Should fail without WASM but arguments should be parsed correctly
|
||||
assert.notEqual(result.code, 0);
|
||||
});
|
||||
|
||||
// Verify Command Tests
|
||||
runner.test('CLI verify command requires all files', async () => {
|
||||
const result = await runner.execCLI(['verify']);
|
||||
|
||||
assert.notEqual(result.code, 0);
|
||||
// Should mention required arguments
|
||||
assert.ok(result.stderr.includes('required') || result.stdout.includes('required'));
|
||||
});
|
||||
|
||||
runner.test('CLI verify command argument parsing', async () => {
|
||||
const matrixFile = path.join(runner.tempDir, 'matrix2x2.json');
|
||||
const solutionFile = path.join(runner.tempDir, 'solution.json');
|
||||
const vectorFile = path.join(runner.tempDir, 'vector2x2.json');
|
||||
|
||||
// Create a dummy solution file
|
||||
await fs.writeFile(solutionFile, JSON.stringify([1, 1]));
|
||||
|
||||
const result = await runner.execCLI([
|
||||
'verify',
|
||||
'-m', matrixFile,
|
||||
'-x', solutionFile,
|
||||
'-b', vectorFile,
|
||||
'--tolerance', '1e-6'
|
||||
]);
|
||||
|
||||
// May fail on implementation details but arguments should parse
|
||||
// We're mainly testing the CLI interface here
|
||||
assert.ok(result.code !== undefined);
|
||||
});
|
||||
|
||||
// Convert Command Tests
|
||||
runner.test('CLI convert command requires input and output', async () => {
|
||||
const result = await runner.execCLI(['convert']);
|
||||
|
||||
assert.notEqual(result.code, 0);
|
||||
assert.ok(result.stderr.includes('required') || result.stdout.includes('required'));
|
||||
});
|
||||
|
||||
runner.test('CLI convert command with format specification', async () => {
|
||||
const inputFile = path.join(runner.tempDir, 'matrix2x2.json');
|
||||
const outputFile = path.join(runner.tempDir, 'matrix2x2.csv');
|
||||
|
||||
const result = await runner.execCLI([
|
||||
'convert',
|
||||
'-i', inputFile,
|
||||
'-o', outputFile,
|
||||
'--format', 'csv'
|
||||
]);
|
||||
|
||||
// This might work if conversion logic is implemented
|
||||
// We're testing the interface
|
||||
assert.ok(result.code !== undefined);
|
||||
});
|
||||
|
||||
// Benchmark Command Tests
|
||||
runner.test('CLI benchmark command with custom parameters', async () => {
|
||||
const result = await runner.execCLI([
|
||||
'benchmark',
|
||||
'--size', '10',
|
||||
'--sparsity', '0.1',
|
||||
'--methods', 'jacobi,cg',
|
||||
'--iterations', '2'
|
||||
]);
|
||||
|
||||
// Should fail without WASM but arguments should parse
|
||||
assert.notEqual(result.code, 0);
|
||||
});
|
||||
|
||||
runner.test('CLI benchmark command output file', async () => {
|
||||
const outputFile = path.join(runner.tempDir, 'benchmark_results.json');
|
||||
|
||||
const result = await runner.execCLI([
|
||||
'benchmark',
|
||||
'--size', '5',
|
||||
'--output', outputFile
|
||||
]);
|
||||
|
||||
// Should fail without WASM implementation
|
||||
assert.notEqual(result.code, 0);
|
||||
});
|
||||
|
||||
// Serve Command Tests
|
||||
runner.test('CLI serve command with default port', async () => {
|
||||
// Start server in background and kill it quickly
|
||||
const child = spawn('node', [runner.cliPath, 'serve'], {
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
// Give it a moment to start
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Kill the server
|
||||
child.kill('SIGTERM');
|
||||
|
||||
// Wait for it to exit
|
||||
const exitCode = await new Promise(resolve => {
|
||||
child.on('close', resolve);
|
||||
});
|
||||
|
||||
// The server might fail to start due to missing WASM, which is expected
|
||||
assert.ok(exitCode !== undefined);
|
||||
});
|
||||
|
||||
runner.test('CLI serve command with custom port', async () => {
|
||||
const child = spawn('node', [runner.cliPath, 'serve', '--port', '3001'], {
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
child.kill('SIGTERM');
|
||||
|
||||
const exitCode = await new Promise(resolve => {
|
||||
child.on('close', resolve);
|
||||
});
|
||||
|
||||
assert.ok(exitCode !== undefined);
|
||||
});
|
||||
|
||||
// Flow-Nexus Command Tests
|
||||
runner.test('CLI flow-nexus command structure', async () => {
|
||||
const result = await runner.execCLI(['flow-nexus', '--help']);
|
||||
|
||||
// Should show flow-nexus specific help or fail gracefully
|
||||
assert.ok(result.code !== undefined);
|
||||
});
|
||||
|
||||
// File Format Tests
|
||||
runner.test('CLI handles JSON matrix format', async () => {
|
||||
const matrixFile = path.join(runner.tempDir, 'matrix2x2.json');
|
||||
|
||||
// Verify the file exists and is readable by the CLI
|
||||
const stats = await fs.stat(matrixFile);
|
||||
assert.ok(stats.isFile());
|
||||
|
||||
const content = await fs.readFile(matrixFile, 'utf8');
|
||||
const matrix = JSON.parse(content);
|
||||
assert.equal(matrix.rows, 2);
|
||||
assert.equal(matrix.cols, 2);
|
||||
});
|
||||
|
||||
runner.test('CLI handles CSV matrix format', async () => {
|
||||
const matrixFile = path.join(runner.tempDir, 'identity3x3.csv');
|
||||
|
||||
const stats = await fs.stat(matrixFile);
|
||||
assert.ok(stats.isFile());
|
||||
|
||||
const content = await fs.readFile(matrixFile, 'utf8');
|
||||
const lines = content.trim().split('\n');
|
||||
assert.equal(lines.length, 3);
|
||||
assert.equal(lines[0], '1,0,0');
|
||||
});
|
||||
|
||||
runner.test('CLI handles Matrix Market format', async () => {
|
||||
const matrixFile = path.join(runner.tempDir, 'identity3x3.mtx');
|
||||
|
||||
const stats = await fs.stat(matrixFile);
|
||||
assert.ok(stats.isFile());
|
||||
|
||||
const content = await fs.readFile(matrixFile, 'utf8');
|
||||
assert.ok(content.includes('%%MatrixMarket'));
|
||||
assert.ok(content.includes('3 3 3'));
|
||||
});
|
||||
|
||||
// Error Handling Tests
|
||||
runner.test('CLI handles missing matrix file', async () => {
|
||||
const result = await runner.execCLI([
|
||||
'solve',
|
||||
'-m', '/nonexistent/matrix.json'
|
||||
]);
|
||||
|
||||
assert.notEqual(result.code, 0);
|
||||
assert.ok(result.stderr.includes('Error') || result.stdout.includes('Error'));
|
||||
});
|
||||
|
||||
runner.test('CLI handles invalid JSON matrix', async () => {
|
||||
const invalidFile = path.join(runner.tempDir, 'invalid.json');
|
||||
await fs.writeFile(invalidFile, '{ invalid json }');
|
||||
|
||||
const result = await runner.execCLI([
|
||||
'solve',
|
||||
'-m', invalidFile
|
||||
]);
|
||||
|
||||
assert.notEqual(result.code, 0);
|
||||
});
|
||||
|
||||
// Verbose and Debug Mode Tests
|
||||
runner.test('CLI verbose mode', async () => {
|
||||
const result = await runner.execCLI([
|
||||
'--verbose',
|
||||
'solve',
|
||||
'-m', path.join(runner.tempDir, 'matrix2x2.json')
|
||||
]);
|
||||
|
||||
// Should produce more output in verbose mode
|
||||
assert.notEqual(result.code, 0); // Will fail without WASM
|
||||
// In verbose mode, there might be more detailed error information
|
||||
});
|
||||
|
||||
runner.test('CLI debug mode', async () => {
|
||||
const result = await runner.execCLI([
|
||||
'--debug',
|
||||
'solve',
|
||||
'-m', path.join(runner.tempDir, 'matrix2x2.json')
|
||||
]);
|
||||
|
||||
assert.notEqual(result.code, 0); // Will fail without WASM
|
||||
// Debug mode should provide stack traces
|
||||
});
|
||||
|
||||
runner.test('CLI quiet mode', async () => {
|
||||
const result = await runner.execCLI([
|
||||
'--quiet',
|
||||
'solve',
|
||||
'-m', path.join(runner.tempDir, 'matrix2x2.json')
|
||||
]);
|
||||
|
||||
assert.notEqual(result.code, 0); // Will fail without WASM
|
||||
// Output should be minimal in quiet mode
|
||||
});
|
||||
|
||||
// Signal Handling Tests
|
||||
runner.test('CLI handles SIGTERM gracefully', async () => {
|
||||
const child = spawn('node', [runner.cliPath, 'serve'], {
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
// Let it start
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Send SIGTERM
|
||||
child.kill('SIGTERM');
|
||||
|
||||
// Wait for graceful shutdown
|
||||
const exitCode = await new Promise(resolve => {
|
||||
child.on('close', resolve);
|
||||
setTimeout(() => {
|
||||
child.kill('SIGKILL');
|
||||
resolve(-1);
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
// Should exit (might be 0 or error code depending on implementation)
|
||||
assert.ok(exitCode !== undefined);
|
||||
});
|
||||
|
||||
// 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 = { CLITestRunner, runner };
|
||||
@@ -0,0 +1,747 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* MCP (Model Context Protocol) compliance tests
|
||||
* Tests the MCP server interface and protocol compliance
|
||||
* Run with: node tests/integration/mcp.test.js
|
||||
*/
|
||||
|
||||
const { strict: assert } = require('assert');
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
class MCPTestRunner {
|
||||
constructor() {
|
||||
this.tests = [];
|
||||
this.passed = 0;
|
||||
this.failed = 0;
|
||||
this.verbose = process.argv.includes('--verbose');
|
||||
this.mcpConfigPath = path.join(__dirname, '../../.mcp.json');
|
||||
}
|
||||
|
||||
test(name, fn) {
|
||||
this.tests.push({ name, fn });
|
||||
}
|
||||
|
||||
async run() {
|
||||
console.log('🧪 Running MCP Protocol Compliance Tests');
|
||||
console.log('=========================================\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)}%`);
|
||||
}
|
||||
|
||||
// Simulate MCP client communication
|
||||
async sendMCPMessage(message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// This would normally be a real MCP connection
|
||||
// For testing, we simulate the protocol
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
jsonrpc: "2.0",
|
||||
id: message.id || 1,
|
||||
result: { status: "ok" }
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// Mock MCP server implementation for testing
|
||||
createMockMCPServer() {
|
||||
return {
|
||||
async initialize() {
|
||||
return {
|
||||
capabilities: {
|
||||
tools: {
|
||||
listChanged: true
|
||||
},
|
||||
resources: {
|
||||
subscribe: true,
|
||||
listChanged: true
|
||||
}
|
||||
},
|
||||
serverInfo: {
|
||||
name: "sublinear-time-solver",
|
||||
version: "0.1.0"
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async listTools() {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "solve_linear_system",
|
||||
description: "Solve a sparse linear system using sublinear algorithms",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
matrix: {
|
||||
type: "object",
|
||||
description: "Sparse matrix in COO format"
|
||||
},
|
||||
vector: {
|
||||
type: "array",
|
||||
description: "Right-hand side vector"
|
||||
},
|
||||
method: {
|
||||
type: "string",
|
||||
enum: ["jacobi", "gauss-seidel", "cg", "hybrid"],
|
||||
default: "hybrid"
|
||||
},
|
||||
tolerance: {
|
||||
type: "number",
|
||||
default: 1e-10
|
||||
},
|
||||
maxIterations: {
|
||||
type: "number",
|
||||
default: 1000
|
||||
}
|
||||
},
|
||||
required: ["matrix", "vector"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "benchmark_solver",
|
||||
description: "Run performance benchmarks on solver algorithms",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
size: {
|
||||
type: "number",
|
||||
description: "Matrix size for benchmark"
|
||||
},
|
||||
sparsity: {
|
||||
type: "number",
|
||||
description: "Matrix sparsity (0-1)"
|
||||
},
|
||||
methods: {
|
||||
type: "array",
|
||||
items: { type: "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "validate_solution",
|
||||
description: "Validate a solution to a linear system",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
matrix: { type: "object" },
|
||||
solution: { type: "array" },
|
||||
vector: { type: "array" },
|
||||
tolerance: { type: "number", default: 1e-8 }
|
||||
},
|
||||
required: ["matrix", "solution", "vector"]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
async listResources() {
|
||||
return {
|
||||
resources: [
|
||||
{
|
||||
uri: "solver://algorithms",
|
||||
name: "Available Algorithms",
|
||||
description: "List of available solver algorithms and their properties",
|
||||
mimeType: "application/json"
|
||||
},
|
||||
{
|
||||
uri: "solver://benchmarks",
|
||||
name: "Benchmark Results",
|
||||
description: "Historical benchmark data and performance metrics",
|
||||
mimeType: "application/json"
|
||||
},
|
||||
{
|
||||
uri: "solver://examples",
|
||||
name: "Example Problems",
|
||||
description: "Pre-configured example linear systems",
|
||||
mimeType: "application/json"
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
async callTool(name, args) {
|
||||
switch (name) {
|
||||
case "solve_linear_system":
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Linear system solved successfully"
|
||||
},
|
||||
{
|
||||
type: "application/json",
|
||||
data: {
|
||||
solution: new Array(args.vector.length).fill(1.0),
|
||||
iterations: 42,
|
||||
residual: 1e-12,
|
||||
method: args.method || "hybrid",
|
||||
convergence: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
case "benchmark_solver":
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Benchmark completed"
|
||||
},
|
||||
{
|
||||
type: "application/json",
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
method: "jacobi",
|
||||
avgTime: 45.2,
|
||||
iterations: 123,
|
||||
convergenceRate: 0.95
|
||||
},
|
||||
{
|
||||
method: "cg",
|
||||
avgTime: 28.7,
|
||||
iterations: 67,
|
||||
convergenceRate: 0.98
|
||||
}
|
||||
],
|
||||
matrixSize: args.size || 1000,
|
||||
sparsity: args.sparsity || 0.01
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
case "validate_solution":
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Solution validation completed"
|
||||
},
|
||||
{
|
||||
type: "application/json",
|
||||
data: {
|
||||
valid: true,
|
||||
maxError: 1e-10,
|
||||
meanError: 5e-11,
|
||||
tolerance: args.tolerance || 1e-8
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
},
|
||||
|
||||
async readResource(uri) {
|
||||
switch (uri) {
|
||||
case "solver://algorithms":
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri,
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify({
|
||||
algorithms: [
|
||||
{
|
||||
name: "jacobi",
|
||||
description: "Jacobi iterative method",
|
||||
complexity: "O(nnz * k)",
|
||||
convergence: "diagonal dominance required"
|
||||
},
|
||||
{
|
||||
name: "gauss-seidel",
|
||||
description: "Gauss-Seidel iterative method",
|
||||
complexity: "O(nnz * k)",
|
||||
convergence: "faster than Jacobi for many problems"
|
||||
},
|
||||
{
|
||||
name: "cg",
|
||||
description: "Conjugate Gradient method",
|
||||
complexity: "O(sqrt(κ) * nnz * k)",
|
||||
convergence: "SPD matrices only"
|
||||
},
|
||||
{
|
||||
name: "hybrid",
|
||||
description: "Adaptive hybrid algorithm selection",
|
||||
complexity: "O(log n) for analysis + optimal solver",
|
||||
convergence: "automatic method selection"
|
||||
}
|
||||
]
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
case "solver://benchmarks":
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri,
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify({
|
||||
benchmarks: [
|
||||
{
|
||||
date: "2024-01-15",
|
||||
matrixSize: 1000,
|
||||
sparsity: 0.01,
|
||||
results: {
|
||||
jacobi: { time: 45.2, iterations: 123 },
|
||||
cg: { time: 28.7, iterations: 67 },
|
||||
hybrid: { time: 22.1, iterations: 45 }
|
||||
}
|
||||
}
|
||||
]
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
case "solver://examples":
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri,
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify({
|
||||
examples: [
|
||||
{
|
||||
name: "Heat Equation 2D",
|
||||
description: "2D heat equation discretization",
|
||||
matrix: {
|
||||
rows: 4,
|
||||
cols: 4,
|
||||
format: "coo",
|
||||
data: {
|
||||
values: [4, -1, -1, 4, -1, -1, 4, -1],
|
||||
rowIndices: [0, 0, 1, 1, 1, 2, 2, 2],
|
||||
colIndices: [0, 1, 0, 1, 2, 1, 2, 3]
|
||||
}
|
||||
},
|
||||
vector: [1, 0, 0, 1]
|
||||
}
|
||||
]
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown resource: ${uri}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const runner = new MCPTestRunner();
|
||||
|
||||
// MCP Configuration Tests
|
||||
runner.test('MCP configuration file exists and is valid', async () => {
|
||||
const configContent = await fs.readFile(runner.mcpConfigPath, 'utf8');
|
||||
const config = JSON.parse(configContent);
|
||||
|
||||
assert.ok(config.mcpServers);
|
||||
assert.ok(typeof config.mcpServers === 'object');
|
||||
});
|
||||
|
||||
runner.test('MCP configuration includes required servers', async () => {
|
||||
const configContent = await fs.readFile(runner.mcpConfigPath, 'utf8');
|
||||
const config = JSON.parse(configContent);
|
||||
|
||||
// Check for expected MCP server entries
|
||||
assert.ok(config.mcpServers['claude-flow'] || config.mcpServers['ruv-swarm']);
|
||||
|
||||
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
||||
assert.ok(serverConfig.command);
|
||||
assert.ok(serverConfig.args);
|
||||
assert.ok(serverConfig.type);
|
||||
}
|
||||
});
|
||||
|
||||
// MCP Protocol Compliance Tests
|
||||
runner.test('MCP server initialization follows protocol', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const initResult = await server.initialize();
|
||||
|
||||
// Check required initialization response structure
|
||||
assert.ok(initResult.capabilities);
|
||||
assert.ok(initResult.serverInfo);
|
||||
assert.ok(initResult.serverInfo.name);
|
||||
assert.ok(initResult.serverInfo.version);
|
||||
});
|
||||
|
||||
runner.test('MCP server supports required capabilities', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const initResult = await server.initialize();
|
||||
|
||||
// Check for tools capability
|
||||
assert.ok(initResult.capabilities.tools);
|
||||
assert.ok(typeof initResult.capabilities.tools.listChanged === 'boolean');
|
||||
|
||||
// Check for resources capability
|
||||
assert.ok(initResult.capabilities.resources);
|
||||
assert.ok(typeof initResult.capabilities.resources.subscribe === 'boolean');
|
||||
assert.ok(typeof initResult.capabilities.resources.listChanged === 'boolean');
|
||||
});
|
||||
|
||||
// MCP Tools Tests
|
||||
runner.test('MCP server lists available tools', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const toolsResult = await server.listTools();
|
||||
|
||||
assert.ok(toolsResult.tools);
|
||||
assert.ok(Array.isArray(toolsResult.tools));
|
||||
assert.ok(toolsResult.tools.length > 0);
|
||||
|
||||
// Verify each tool has required properties
|
||||
for (const tool of toolsResult.tools) {
|
||||
assert.ok(tool.name);
|
||||
assert.ok(tool.description);
|
||||
assert.ok(tool.inputSchema);
|
||||
assert.equal(tool.inputSchema.type, 'object');
|
||||
}
|
||||
});
|
||||
|
||||
runner.test('MCP server provides solve_linear_system tool', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const toolsResult = await server.listTools();
|
||||
const solveTool = toolsResult.tools.find(tool => tool.name === 'solve_linear_system');
|
||||
|
||||
assert.ok(solveTool);
|
||||
assert.ok(solveTool.description.includes('linear system'));
|
||||
assert.ok(solveTool.inputSchema.properties.matrix);
|
||||
assert.ok(solveTool.inputSchema.properties.vector);
|
||||
assert.ok(solveTool.inputSchema.required.includes('matrix'));
|
||||
assert.ok(solveTool.inputSchema.required.includes('vector'));
|
||||
});
|
||||
|
||||
runner.test('MCP server provides benchmark_solver tool', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const toolsResult = await server.listTools();
|
||||
const benchmarkTool = toolsResult.tools.find(tool => tool.name === 'benchmark_solver');
|
||||
|
||||
assert.ok(benchmarkTool);
|
||||
assert.ok(benchmarkTool.description.includes('benchmark'));
|
||||
assert.ok(benchmarkTool.inputSchema.properties.size);
|
||||
assert.ok(benchmarkTool.inputSchema.properties.sparsity);
|
||||
});
|
||||
|
||||
runner.test('MCP server provides validate_solution tool', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const toolsResult = await server.listTools();
|
||||
const validateTool = toolsResult.tools.find(tool => tool.name === 'validate_solution');
|
||||
|
||||
assert.ok(validateTool);
|
||||
assert.ok(validateTool.description.includes('validate'));
|
||||
assert.ok(validateTool.inputSchema.properties.matrix);
|
||||
assert.ok(validateTool.inputSchema.properties.solution);
|
||||
assert.ok(validateTool.inputSchema.properties.vector);
|
||||
});
|
||||
|
||||
// MCP Tool Execution Tests
|
||||
runner.test('MCP solve_linear_system tool execution', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const args = {
|
||||
matrix: {
|
||||
rows: 2,
|
||||
cols: 2,
|
||||
format: 'coo',
|
||||
data: {
|
||||
values: [2, 1, 1, 2],
|
||||
rowIndices: [0, 0, 1, 1],
|
||||
colIndices: [0, 1, 0, 1]
|
||||
}
|
||||
},
|
||||
vector: [3, 3],
|
||||
method: 'cg',
|
||||
tolerance: 1e-10
|
||||
};
|
||||
|
||||
const result = await server.callTool('solve_linear_system', args);
|
||||
|
||||
assert.ok(result.content);
|
||||
assert.ok(Array.isArray(result.content));
|
||||
|
||||
// Check for text response
|
||||
const textContent = result.content.find(c => c.type === 'text');
|
||||
assert.ok(textContent);
|
||||
|
||||
// Check for JSON data response
|
||||
const jsonContent = result.content.find(c => c.type === 'application/json');
|
||||
assert.ok(jsonContent);
|
||||
assert.ok(jsonContent.data.solution);
|
||||
assert.ok(typeof jsonContent.data.iterations === 'number');
|
||||
assert.ok(typeof jsonContent.data.residual === 'number');
|
||||
});
|
||||
|
||||
runner.test('MCP benchmark_solver tool execution', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const args = {
|
||||
size: 1000,
|
||||
sparsity: 0.01,
|
||||
methods: ['jacobi', 'cg']
|
||||
};
|
||||
|
||||
const result = await server.callTool('benchmark_solver', args);
|
||||
|
||||
assert.ok(result.content);
|
||||
const jsonContent = result.content.find(c => c.type === 'application/json');
|
||||
assert.ok(jsonContent);
|
||||
assert.ok(jsonContent.data.results);
|
||||
assert.ok(Array.isArray(jsonContent.data.results));
|
||||
assert.equal(jsonContent.data.matrixSize, 1000);
|
||||
});
|
||||
|
||||
runner.test('MCP validate_solution tool execution', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const args = {
|
||||
matrix: {
|
||||
rows: 2,
|
||||
cols: 2,
|
||||
data: [1, 0, 0, 1],
|
||||
format: 'dense'
|
||||
},
|
||||
solution: [1, 1],
|
||||
vector: [1, 1],
|
||||
tolerance: 1e-8
|
||||
};
|
||||
|
||||
const result = await server.callTool('validate_solution', args);
|
||||
|
||||
assert.ok(result.content);
|
||||
const jsonContent = result.content.find(c => c.type === 'application/json');
|
||||
assert.ok(jsonContent);
|
||||
assert.ok(typeof jsonContent.data.valid === 'boolean');
|
||||
assert.ok(typeof jsonContent.data.maxError === 'number');
|
||||
});
|
||||
|
||||
// MCP Resources Tests
|
||||
runner.test('MCP server lists available resources', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const resourcesResult = await server.listResources();
|
||||
|
||||
assert.ok(resourcesResult.resources);
|
||||
assert.ok(Array.isArray(resourcesResult.resources));
|
||||
assert.ok(resourcesResult.resources.length > 0);
|
||||
|
||||
// Verify each resource has required properties
|
||||
for (const resource of resourcesResult.resources) {
|
||||
assert.ok(resource.uri);
|
||||
assert.ok(resource.name);
|
||||
assert.ok(resource.description);
|
||||
assert.ok(resource.mimeType);
|
||||
}
|
||||
});
|
||||
|
||||
runner.test('MCP server provides algorithms resource', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const resourcesResult = await server.listResources();
|
||||
const algorithmsResource = resourcesResult.resources.find(r => r.uri === 'solver://algorithms');
|
||||
|
||||
assert.ok(algorithmsResource);
|
||||
assert.ok(algorithmsResource.name.includes('Algorithm'));
|
||||
assert.equal(algorithmsResource.mimeType, 'application/json');
|
||||
});
|
||||
|
||||
runner.test('MCP server can read algorithms resource', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const result = await server.readResource('solver://algorithms');
|
||||
|
||||
assert.ok(result.contents);
|
||||
assert.ok(Array.isArray(result.contents));
|
||||
|
||||
const content = result.contents[0];
|
||||
assert.equal(content.uri, 'solver://algorithms');
|
||||
assert.equal(content.mimeType, 'application/json');
|
||||
|
||||
const algorithms = JSON.parse(content.text);
|
||||
assert.ok(algorithms.algorithms);
|
||||
assert.ok(Array.isArray(algorithms.algorithms));
|
||||
});
|
||||
|
||||
runner.test('MCP server can read benchmarks resource', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const result = await server.readResource('solver://benchmarks');
|
||||
|
||||
assert.ok(result.contents);
|
||||
const content = result.contents[0];
|
||||
assert.equal(content.uri, 'solver://benchmarks');
|
||||
|
||||
const benchmarks = JSON.parse(content.text);
|
||||
assert.ok(benchmarks.benchmarks);
|
||||
});
|
||||
|
||||
runner.test('MCP server can read examples resource', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
const result = await server.readResource('solver://examples');
|
||||
|
||||
assert.ok(result.contents);
|
||||
const content = result.contents[0];
|
||||
assert.equal(content.uri, 'solver://examples');
|
||||
|
||||
const examples = JSON.parse(content.text);
|
||||
assert.ok(examples.examples);
|
||||
assert.ok(Array.isArray(examples.examples));
|
||||
});
|
||||
|
||||
// MCP Error Handling Tests
|
||||
runner.test('MCP server handles unknown tool gracefully', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
try {
|
||||
await server.callTool('unknown_tool', {});
|
||||
assert.fail('Should have thrown error for unknown tool');
|
||||
} catch (error) {
|
||||
assert.ok(error.message.includes('Unknown tool'));
|
||||
}
|
||||
});
|
||||
|
||||
runner.test('MCP server handles unknown resource gracefully', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
try {
|
||||
await server.readResource('solver://unknown');
|
||||
assert.fail('Should have thrown error for unknown resource');
|
||||
} catch (error) {
|
||||
assert.ok(error.message.includes('Unknown resource'));
|
||||
}
|
||||
});
|
||||
|
||||
// MCP JSON-RPC Compliance Tests
|
||||
runner.test('MCP messages follow JSON-RPC 2.0 format', async () => {
|
||||
const message = {
|
||||
jsonrpc: "2.0",
|
||||
method: "tools/list",
|
||||
id: 1
|
||||
};
|
||||
|
||||
const response = await runner.sendMCPMessage(message);
|
||||
|
||||
assert.equal(response.jsonrpc, "2.0");
|
||||
assert.equal(response.id, 1);
|
||||
assert.ok(response.result !== undefined || response.error !== undefined);
|
||||
});
|
||||
|
||||
// MCP Schema Validation Tests
|
||||
runner.test('MCP tool schemas are valid JSON Schema', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
const toolsResult = await server.listTools();
|
||||
|
||||
for (const tool of toolsResult.tools) {
|
||||
const schema = tool.inputSchema;
|
||||
|
||||
// Basic JSON Schema validation
|
||||
assert.equal(schema.type, 'object');
|
||||
assert.ok(schema.properties);
|
||||
assert.ok(typeof schema.properties === 'object');
|
||||
|
||||
if (schema.required) {
|
||||
assert.ok(Array.isArray(schema.required));
|
||||
|
||||
// All required properties should exist in properties
|
||||
for (const required of schema.required) {
|
||||
assert.ok(schema.properties[required]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// MCP Integration Tests
|
||||
runner.test('MCP server integration workflow', async () => {
|
||||
const server = runner.createMockMCPServer();
|
||||
|
||||
// 1. Initialize server
|
||||
const init = await server.initialize();
|
||||
assert.ok(init.capabilities);
|
||||
|
||||
// 2. List available tools
|
||||
const tools = await server.listTools();
|
||||
assert.ok(tools.tools.length > 0);
|
||||
|
||||
// 3. Execute a tool
|
||||
const solveTool = tools.tools.find(t => t.name === 'solve_linear_system');
|
||||
assert.ok(solveTool);
|
||||
|
||||
const result = await server.callTool('solve_linear_system', {
|
||||
matrix: {
|
||||
rows: 2,
|
||||
cols: 2,
|
||||
format: 'dense',
|
||||
data: [1, 0, 0, 1]
|
||||
},
|
||||
vector: [1, 1]
|
||||
});
|
||||
|
||||
assert.ok(result.content);
|
||||
|
||||
// 4. List and read resources
|
||||
const resources = await server.listResources();
|
||||
assert.ok(resources.resources.length > 0);
|
||||
|
||||
const algorithmsResource = resources.resources.find(r => r.uri === 'solver://algorithms');
|
||||
const algorithmsContent = await server.readResource(algorithmsResource.uri);
|
||||
assert.ok(algorithmsContent.contents);
|
||||
});
|
||||
|
||||
// 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 = { MCPTestRunner, runner };
|
||||
@@ -0,0 +1,549 @@
|
||||
#!/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 };
|
||||
Reference in New Issue
Block a user