feat: make results directory output optional by pcarleton · Pull Request #107 · modelcontextprotocol/conformance · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/index.ts
68 changes: 45 additions & 23 deletions src/runner/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { promises as fs } from 'fs';
import path from 'path';
import { ConformanceCheck } from '../types';
import { getScenario } from '../scenarios';
import { ensureResultsDir, createResultDir, formatPrettyChecks } from './utils';
import { createResultDir, formatPrettyChecks } from './utils';

export interface ClientExecutionResult {
exitCode: number;
Expand Down Expand Up @@ -91,15 +91,19 @@ async function executeClient(
export async function runConformanceTest(
clientCommand: string,
scenarioName: string,
timeout: number = 30000
timeout: number = 30000,
outputDir?: string
): Promise<{
checks: ConformanceCheck[];
clientOutput: ClientExecutionResult;
resultDir: string;
resultDir?: string;
}> {
await ensureResultsDir();
const resultDir = createResultDir(scenarioName);
await fs.mkdir(resultDir, { recursive: true });
let resultDir: string | undefined;

if (outputDir) {
resultDir = createResultDir(outputDir, scenarioName);
await fs.mkdir(resultDir, { recursive: true });
}

// Scenario is guaranteed to exist by CLI validation
const scenario = getScenario(scenarioName)!;
Expand Down Expand Up @@ -138,16 +142,24 @@ export async function runConformanceTest(

const checks = scenario.getChecks();

await fs.writeFile(
path.join(resultDir, 'checks.json'),
JSON.stringify(checks, null, 2)
);
if (resultDir) {
await fs.writeFile(
path.join(resultDir, 'checks.json'),
JSON.stringify(checks, null, 2)
);

await fs.writeFile(path.join(resultDir, 'stdout.txt'), clientOutput.stdout);
await fs.writeFile(
path.join(resultDir, 'stdout.txt'),
clientOutput.stdout
);

await fs.writeFile(path.join(resultDir, 'stderr.txt'), clientOutput.stderr);
await fs.writeFile(
path.join(resultDir, 'stderr.txt'),
clientOutput.stderr
);

console.error(`Results saved to ${resultDir}`);
console.error(`Results saved to ${resultDir}`);
}

return {
checks,
Expand Down Expand Up @@ -244,11 +256,15 @@ export function printClientResults(

export async function runInteractiveMode(
scenarioName: string,
verbose: boolean = false
verbose: boolean = false,
outputDir?: string
): Promise<void> {
await ensureResultsDir();
const resultDir = createResultDir(scenarioName);
await fs.mkdir(resultDir, { recursive: true });
let resultDir: string | undefined;

if (outputDir) {
resultDir = createResultDir(outputDir, scenarioName);
await fs.mkdir(resultDir, { recursive: true });
}

// Scenario is guaranteed to exist by CLI validation
const scenario = getScenario(scenarioName)!;
Expand All @@ -257,23 +273,29 @@ export async function runInteractiveMode(
const urls = await scenario.start();

console.log(`Server URL: ${urls.serverUrl}`);
console.log('Press Ctrl+C to stop and save checks...');
console.log('Press Ctrl+C to stop...');

const handleShutdown = async () => {
console.log('\nShutting down...');

const checks = scenario.getChecks();
await fs.writeFile(
path.join(resultDir, 'checks.json'),
JSON.stringify(checks, null, 2)
);

if (resultDir) {
await fs.writeFile(
path.join(resultDir, 'checks.json'),
JSON.stringify(checks, null, 2)
);
}

if (verbose) {
console.log(`\nChecks:\n${JSON.stringify(checks, null, 2)}`);
} else {
console.log(`\nChecks:\n${formatPrettyChecks(checks)}`);
}
console.log(`\nChecks saved to ${resultDir}/checks.json`);

if (resultDir) {
console.log(`\nChecks saved to ${resultDir}/checks.json`);
}

await scenario.stop();
process.exit(0);
Expand Down
1 change: 0 additions & 1 deletion src/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export {

// Export utilities
export {
ensureResultsDir,
createResultDir,
formatPrettyChecks,
getStatusColor,
Expand Down
28 changes: 17 additions & 11 deletions src/runner/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
import path from 'path';
import { ConformanceCheck } from '../types';
import { getClientScenario } from '../scenarios';
import { ensureResultsDir, createResultDir, formatPrettyChecks } from './utils';
import { createResultDir, formatPrettyChecks } from './utils';

/**
* Format markdown-style text for terminal output using ANSI codes
Expand All @@ -19,15 +19,19 @@ function formatMarkdown(text: string): string {

export async function runServerConformanceTest(
serverUrl: string,
scenarioName: string
scenarioName: string,
outputDir?: string
): Promise<{
checks: ConformanceCheck[];
resultDir: string;
resultDir?: string;
scenarioDescription: string;
}> {
await ensureResultsDir();
const resultDir = createResultDir(scenarioName, 'server');
await fs.mkdir(resultDir, { recursive: true });
let resultDir: string | undefined;

if (outputDir) {
resultDir = createResultDir(outputDir, scenarioName, 'server');
await fs.mkdir(resultDir, { recursive: true });
}

// Scenario is guaranteed to exist by CLI validation
const scenario = getClientScenario(scenarioName)!;
Expand All @@ -38,12 +42,14 @@ export async function runServerConformanceTest(

const checks = await scenario.run(serverUrl);

await fs.writeFile(
path.join(resultDir, 'checks.json'),
JSON.stringify(checks, null, 2)
);
if (resultDir) {
await fs.writeFile(
path.join(resultDir, 'checks.json'),
JSON.stringify(checks, null, 2)
);

console.log(`Results saved to ${resultDir}`);
console.log(`Results saved to ${resultDir}`);
}

return {
checks,
Expand Down
15 changes: 6 additions & 9 deletions src/runner/utils.ts
Loading