feat(runtime): Add runtime adapters, filename runtime parser and migration tool; integrate runtime selection into TsTest and add tests

This commit is contained in:
2025-10-10 16:35:22 +00:00
parent ee0aca9ff7
commit 36715c9139
12 changed files with 2106 additions and 23 deletions

View File

@@ -10,6 +10,14 @@ import { TestExecutionMode } from './index.js';
import { TsTestLogger } from './tstest.logging.js';
import type { LogOptions } from './tstest.logging.js';
// Runtime adapters
import { parseTestFilename } from './tstest.classes.runtime.parser.js';
import { RuntimeAdapterRegistry } from './tstest.classes.runtime.adapter.js';
import { NodeRuntimeAdapter } from './tstest.classes.runtime.node.js';
import { ChromiumRuntimeAdapter } from './tstest.classes.runtime.chromium.js';
import { DenoRuntimeAdapter } from './tstest.classes.runtime.deno.js';
import { BunRuntimeAdapter } from './tstest.classes.runtime.bun.js';
export class TsTest {
public testDir: TestDirectory;
public executionMode: TestExecutionMode;
@@ -28,6 +36,8 @@ export class TsTest {
public tsbundleInstance = new plugins.tsbundle.TsBundle();
public runtimeRegistry = new RuntimeAdapterRegistry();
constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = [], startFromFile: number | null = null, stopAtFile: number | null = null, timeoutSeconds: number | null = null) {
this.executionMode = executionModeArg;
this.testDir = new TestDirectory(cwdArg, testPathArg, executionModeArg);
@@ -36,6 +46,20 @@ export class TsTest {
this.startFromFile = startFromFile;
this.stopAtFile = stopAtFile;
this.timeoutSeconds = timeoutSeconds;
// Register runtime adapters
this.runtimeRegistry.register(
new NodeRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags)
);
this.runtimeRegistry.register(
new ChromiumRuntimeAdapter(this.logger, this.tsbundleInstance, this.smartbrowserInstance, this.timeoutSeconds)
);
this.runtimeRegistry.register(
new DenoRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags)
);
this.runtimeRegistry.register(
new BunRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags)
);
}
async run() {
@@ -175,29 +199,50 @@ export class TsTest {
}
private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) {
switch (true) {
case process.env.CI && fileNameArg.includes('.nonci.'):
this.logger.tapOutput(`Skipping ${fileNameArg} - marked as non-CI`);
break;
case fileNameArg.endsWith('.browser.ts') || fileNameArg.endsWith('.browser.nonci.ts'):
const tapParserBrowser = await this.runInChrome(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParserBrowser);
break;
case fileNameArg.endsWith('.both.ts') || fileNameArg.endsWith('.both.nonci.ts'):
this.logger.sectionStart('Part 1: Chrome');
const tapParserBothBrowser = await this.runInChrome(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParserBothBrowser);
// Parse the filename to determine runtimes and modifiers
const fileName = plugins.path.basename(fileNameArg);
const parsed = parseTestFilename(fileName, { strictUnknownRuntime: false });
// Check for nonci modifier in CI environment
if (process.env.CI && parsed.modifiers.includes('nonci')) {
this.logger.tapOutput(`Skipping ${fileNameArg} - marked as non-CI`);
return;
}
// Show deprecation warning for legacy naming
if (parsed.isLegacy) {
console.warn('');
console.warn(cs('⚠️ DEPRECATION WARNING', 'orange'));
console.warn(cs(` File: ${fileName}`, 'orange'));
console.warn(cs(` Legacy naming detected. Please migrate to new naming convention.`, 'orange'));
console.warn(cs(` Suggested: ${fileName.replace('.browser.', '.chromium.').replace('.both.', '.node+chromium.')}`, 'green'));
console.warn(cs(` Run: tstest migrate --dry-run`, 'cyan'));
console.warn('');
}
// Get adapters for the specified runtimes
const adapters = this.runtimeRegistry.getAdaptersForRuntimes(parsed.runtimes);
if (adapters.length === 0) {
this.logger.tapOutput(`Skipping ${fileNameArg} - no runtime adapters available`);
return;
}
// Execute tests for each runtime
if (adapters.length === 1) {
// Single runtime - no sections needed
const adapter = adapters[0];
const tapParser = await adapter.run(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParser);
} else {
// Multiple runtimes - use sections
for (let i = 0; i < adapters.length; i++) {
const adapter = adapters[i];
this.logger.sectionStart(`Part ${i + 1}: ${adapter.displayName}`);
const tapParser = await adapter.run(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParser);
this.logger.sectionEnd();
this.logger.sectionStart('Part 2: Node');
const tapParserBothNode = await this.runInNode(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParserBothNode);
this.logger.sectionEnd();
break;
default:
const tapParserNode = await this.runInNode(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParserNode);
break;
}
}
}