feat(docker): add Docker test file support and runtime adapter
This commit is contained in:
9
test/test.example.latest.docker.sh
Executable file
9
test/test.example.latest.docker.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Sample Docker test file
|
||||||
|
# This file demonstrates the naming pattern: test.{baseName}.{variant}.docker.sh
|
||||||
|
# The variant "latest" maps to the Dockerfile in the project root
|
||||||
|
|
||||||
|
echo "TAP version 13"
|
||||||
|
echo "1..2"
|
||||||
|
echo "ok 1 - Sample Docker test passes"
|
||||||
|
echo "ok 2 - Docker environment is working"
|
||||||
251
ts/tstest.classes.runtime.docker.ts
Normal file
251
ts/tstest.classes.runtime.docker.ts
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
import * as plugins from './tstest.plugins.js';
|
||||||
|
import { coloredString as cs } from '@push.rocks/consolecolor';
|
||||||
|
import {
|
||||||
|
RuntimeAdapter,
|
||||||
|
type RuntimeOptions,
|
||||||
|
type RuntimeCommand,
|
||||||
|
type RuntimeAvailability,
|
||||||
|
} from './tstest.classes.runtime.adapter.js';
|
||||||
|
import { TapParser } from './tstest.classes.tap.parser.js';
|
||||||
|
import { TsTestLogger } from './tstest.logging.js';
|
||||||
|
import type { Runtime } from './tstest.classes.runtime.parser.js';
|
||||||
|
import {
|
||||||
|
parseDockerTestFilename,
|
||||||
|
mapVariantToDockerfile,
|
||||||
|
isDockerTestFile
|
||||||
|
} from './tstest.classes.runtime.parser.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker runtime adapter
|
||||||
|
* Executes shell test files inside Docker containers
|
||||||
|
* Pattern: test.{variant}.docker.sh
|
||||||
|
* Variants map to Dockerfiles: latest -> Dockerfile, others -> Dockerfile_{variant}
|
||||||
|
*/
|
||||||
|
export class DockerRuntimeAdapter extends RuntimeAdapter {
|
||||||
|
readonly id: Runtime = 'node'; // Using 'node' temporarily as Runtime type doesn't include 'docker'
|
||||||
|
readonly displayName: string = 'Docker';
|
||||||
|
|
||||||
|
private builtImages: Set<string> = new Set(); // Track built images to avoid rebuilding
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private logger: TsTestLogger,
|
||||||
|
private smartshellInstance: any, // SmartShell instance from @push.rocks/smartshell
|
||||||
|
private timeoutSeconds: number | null,
|
||||||
|
private cwd: string
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Docker CLI is available
|
||||||
|
*/
|
||||||
|
async checkAvailable(): Promise<RuntimeAvailability> {
|
||||||
|
try {
|
||||||
|
const result = await this.smartshellInstance.exec('docker --version');
|
||||||
|
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
return {
|
||||||
|
available: false,
|
||||||
|
error: 'Docker command failed',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract version from output like "Docker version 24.0.5, build ced0996"
|
||||||
|
const versionMatch = result.stdout.match(/Docker version ([^,]+)/);
|
||||||
|
const version = versionMatch ? versionMatch[1] : 'unknown';
|
||||||
|
|
||||||
|
return {
|
||||||
|
available: true,
|
||||||
|
version,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
available: false,
|
||||||
|
error: `Docker not found: ${error.message}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create command configuration for Docker test execution
|
||||||
|
* This is used for informational purposes
|
||||||
|
*/
|
||||||
|
createCommand(testFile: string, options?: RuntimeOptions): RuntimeCommand {
|
||||||
|
const parsed = parseDockerTestFilename(testFile);
|
||||||
|
const dockerfilePath = mapVariantToDockerfile(parsed.variant, this.cwd);
|
||||||
|
const imageName = `tstest-${parsed.variant}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
command: 'docker',
|
||||||
|
args: [
|
||||||
|
'run',
|
||||||
|
'--rm',
|
||||||
|
'-v',
|
||||||
|
`${this.cwd}/test:/test`,
|
||||||
|
imageName,
|
||||||
|
'taprun',
|
||||||
|
`/test/${plugins.path.basename(testFile)}`
|
||||||
|
],
|
||||||
|
env: {},
|
||||||
|
cwd: this.cwd,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a Docker image from the specified Dockerfile
|
||||||
|
*/
|
||||||
|
private async buildDockerImage(dockerfilePath: string, imageName: string): Promise<void> {
|
||||||
|
// Check if image is already built
|
||||||
|
if (this.builtImages.has(imageName)) {
|
||||||
|
this.logger.tapOutput(`Using cached Docker image: ${imageName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Dockerfile exists
|
||||||
|
if (!await plugins.smartfile.fs.fileExists(dockerfilePath)) {
|
||||||
|
throw new Error(
|
||||||
|
`Dockerfile not found: ${dockerfilePath}\n` +
|
||||||
|
`Expected Dockerfile for Docker test variant.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.tapOutput(`Building Docker image: ${imageName} from ${dockerfilePath}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buildResult = await this.smartshellInstance.exec(
|
||||||
|
`docker build -f ${dockerfilePath} -t ${imageName} ${this.cwd}`,
|
||||||
|
{
|
||||||
|
cwd: this.cwd,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (buildResult.exitCode !== 0) {
|
||||||
|
throw new Error(`Docker build failed:\n${buildResult.stderr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.builtImages.add(imageName);
|
||||||
|
this.logger.tapOutput(`✅ Docker image built successfully: ${imageName}`);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to build Docker image: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a Docker test file
|
||||||
|
*/
|
||||||
|
async run(
|
||||||
|
testFile: string,
|
||||||
|
index: number,
|
||||||
|
total: number,
|
||||||
|
options?: RuntimeOptions
|
||||||
|
): Promise<TapParser> {
|
||||||
|
this.logger.testFileStart(testFile, this.displayName, index, total);
|
||||||
|
|
||||||
|
// Parse the Docker test filename
|
||||||
|
const parsed = parseDockerTestFilename(testFile);
|
||||||
|
const dockerfilePath = mapVariantToDockerfile(parsed.variant, this.cwd);
|
||||||
|
const imageName = `tstest-${parsed.variant}`;
|
||||||
|
|
||||||
|
// Build the Docker image
|
||||||
|
await this.buildDockerImage(dockerfilePath, imageName);
|
||||||
|
|
||||||
|
// Prepare the test file path relative to the mounted directory
|
||||||
|
// We need to get the path relative to cwd
|
||||||
|
const absoluteTestPath = plugins.path.isAbsolute(testFile)
|
||||||
|
? testFile
|
||||||
|
: plugins.path.join(this.cwd, testFile);
|
||||||
|
|
||||||
|
const relativeTestPath = plugins.path.relative(this.cwd, absoluteTestPath);
|
||||||
|
|
||||||
|
// Create TAP parser
|
||||||
|
const tapParser = new TapParser(testFile + ':docker', this.logger);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Build docker run command
|
||||||
|
const dockerArgs = [
|
||||||
|
'run',
|
||||||
|
'--rm',
|
||||||
|
'-v',
|
||||||
|
`${this.cwd}/test:/test`,
|
||||||
|
imageName,
|
||||||
|
'taprun',
|
||||||
|
`/test/${plugins.path.basename(testFile)}`
|
||||||
|
];
|
||||||
|
|
||||||
|
this.logger.tapOutput(`Executing: docker ${dockerArgs.join(' ')}`);
|
||||||
|
|
||||||
|
// Execute the Docker container
|
||||||
|
const execPromise = this.smartshellInstance.execStreaming(
|
||||||
|
`docker ${dockerArgs.join(' ')}`,
|
||||||
|
{
|
||||||
|
cwd: this.cwd,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up timeout if configured
|
||||||
|
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||||
|
if (this.timeoutSeconds) {
|
||||||
|
timeoutHandle = setTimeout(() => {
|
||||||
|
this.logger.tapOutput(`⏱️ Test timeout (${this.timeoutSeconds}s) - killing container`);
|
||||||
|
// Try to kill any running containers with this image
|
||||||
|
this.smartshellInstance.exec(`docker ps -q --filter ancestor=${imageName} | xargs -r docker kill`);
|
||||||
|
}, this.timeoutSeconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream output to TAP parser line by line
|
||||||
|
execPromise.childProcess.stdout.on('data', (data: Buffer) => {
|
||||||
|
const output = data.toString();
|
||||||
|
const lines = output.split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim()) {
|
||||||
|
tapParser.handleTapLog(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
execPromise.childProcess.stderr.on('data', (data: Buffer) => {
|
||||||
|
const output = data.toString();
|
||||||
|
this.logger.tapOutput(cs(`[stderr] ${output}`, 'orange'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for completion
|
||||||
|
const result = await execPromise;
|
||||||
|
|
||||||
|
// Clear timeout
|
||||||
|
if (timeoutHandle) {
|
||||||
|
clearTimeout(timeoutHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
this.logger.tapOutput(cs(`❌ Docker test failed with exit code ${result.exitCode}`, 'red'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate final result
|
||||||
|
await tapParser.evaluateFinalResult();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.tapOutput(cs(`❌ Error running Docker test: ${error.message}`, 'red'));
|
||||||
|
// Add a failing test result to the parser
|
||||||
|
tapParser.handleTapLog('not ok 1 - Docker test execution failed');
|
||||||
|
await tapParser.evaluateFinalResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tapParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up built Docker images (optional, can be called at end of test suite)
|
||||||
|
*/
|
||||||
|
async cleanup(): Promise<void> {
|
||||||
|
for (const imageName of this.builtImages) {
|
||||||
|
try {
|
||||||
|
this.logger.tapOutput(`Removing Docker image: ${imageName}`);
|
||||||
|
await this.smartshellInstance.exec(`docker rmi ${imageName}`);
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
this.logger.tapOutput(cs(`Warning: Failed to remove image ${imageName}: ${error.message}`, 'orange'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.builtImages.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ export interface ParserConfig {
|
|||||||
|
|
||||||
const KNOWN_RUNTIMES: Set<string> = new Set(['node', 'chromium', 'deno', 'bun']);
|
const KNOWN_RUNTIMES: Set<string> = new Set(['node', 'chromium', 'deno', 'bun']);
|
||||||
const KNOWN_MODIFIERS: Set<string> = new Set(['nonci']);
|
const KNOWN_MODIFIERS: Set<string> = new Set(['nonci']);
|
||||||
const VALID_EXTENSIONS: Set<string> = new Set(['ts', 'tsx', 'mts', 'cts']);
|
const VALID_EXTENSIONS: Set<string> = new Set(['ts', 'tsx', 'mts', 'cts', 'sh']);
|
||||||
const ALL_RUNTIMES: Runtime[] = ['node', 'chromium', 'deno', 'bun'];
|
const ALL_RUNTIMES: Runtime[] = ['node', 'chromium', 'deno', 'bun'];
|
||||||
|
|
||||||
// Legacy mappings for backwards compatibility
|
// Legacy mappings for backwards compatibility
|
||||||
@@ -228,3 +228,81 @@ export function getLegacyMigrationTarget(fileName: string): string | null {
|
|||||||
|
|
||||||
return parts.join('.');
|
return parts.join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker test file information
|
||||||
|
*/
|
||||||
|
export interface DockerTestFileInfo {
|
||||||
|
baseName: string;
|
||||||
|
variant: string;
|
||||||
|
isDockerTest: true;
|
||||||
|
original: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a filename matches the Docker test pattern: *.{variant}.docker.sh
|
||||||
|
* Examples: test.latest.docker.sh, test.integration.npmci.docker.sh
|
||||||
|
*/
|
||||||
|
export function isDockerTestFile(fileName: string): boolean {
|
||||||
|
// Must end with .docker.sh
|
||||||
|
if (!fileName.endsWith('.docker.sh')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract filename from path if needed
|
||||||
|
const name = fileName.split('/').pop() || fileName;
|
||||||
|
|
||||||
|
// Must have at least 3 parts: [baseName, variant, docker, sh]
|
||||||
|
const parts = name.split('.');
|
||||||
|
return parts.length >= 4 && parts[parts.length - 2] === 'docker' && parts[parts.length - 1] === 'sh';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a Docker test filename to extract variant and base name
|
||||||
|
* Pattern: test.{baseName}.{variant}.docker.sh
|
||||||
|
* Examples:
|
||||||
|
* - test.latest.docker.sh -> { baseName: 'test', variant: 'latest' }
|
||||||
|
* - test.integration.npmci.docker.sh -> { baseName: 'test.integration', variant: 'npmci' }
|
||||||
|
*/
|
||||||
|
export function parseDockerTestFilename(filePath: string): DockerTestFileInfo {
|
||||||
|
// Extract just the filename from the path
|
||||||
|
const fileName = filePath.split('/').pop() || filePath;
|
||||||
|
const original = fileName;
|
||||||
|
|
||||||
|
if (!isDockerTestFile(fileName)) {
|
||||||
|
throw new Error(`Not a valid Docker test file: "${fileName}". Expected pattern: *.{variant}.docker.sh`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove .docker.sh suffix
|
||||||
|
const withoutSuffix = fileName.slice(0, -10); // Remove '.docker.sh'
|
||||||
|
const tokens = withoutSuffix.split('.');
|
||||||
|
|
||||||
|
if (tokens.length === 0) {
|
||||||
|
throw new Error(`Invalid Docker test file: empty basename in "${fileName}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last token before .docker.sh is the variant
|
||||||
|
const variant = tokens[tokens.length - 1];
|
||||||
|
|
||||||
|
// Everything else is the base name
|
||||||
|
const baseName = tokens.slice(0, -1).join('.');
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseName: baseName || 'test',
|
||||||
|
variant,
|
||||||
|
isDockerTest: true,
|
||||||
|
original,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a Docker variant to its corresponding Dockerfile path
|
||||||
|
* "latest" -> "Dockerfile"
|
||||||
|
* Other variants -> "Dockerfile_{variant}"
|
||||||
|
*/
|
||||||
|
export function mapVariantToDockerfile(variant: string, baseDir: string): string {
|
||||||
|
if (variant === 'latest') {
|
||||||
|
return `${baseDir}/Dockerfile`;
|
||||||
|
}
|
||||||
|
return `${baseDir}/Dockerfile_${variant}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,12 +74,20 @@ export class TestDirectory {
|
|||||||
case TestExecutionMode.DIRECTORY:
|
case TestExecutionMode.DIRECTORY:
|
||||||
// Directory mode - now recursive with ** pattern
|
// Directory mode - now recursive with ** pattern
|
||||||
const dirPath = plugins.path.join(this.cwd, this.testPath);
|
const dirPath = plugins.path.join(this.cwd, this.testPath);
|
||||||
const testPattern = '**/test*.ts';
|
|
||||||
|
// Search for both TypeScript test files and Docker shell test files
|
||||||
const testFiles = await plugins.smartfile.fs.listFileTree(dirPath, testPattern);
|
const tsPattern = '**/test*.ts';
|
||||||
|
const dockerPattern = '**/*.docker.sh';
|
||||||
|
|
||||||
|
const [tsFiles, dockerFiles] = await Promise.all([
|
||||||
|
plugins.smartfile.fs.listFileTree(dirPath, tsPattern),
|
||||||
|
plugins.smartfile.fs.listFileTree(dirPath, dockerPattern),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const allTestFiles = [...tsFiles, ...dockerFiles];
|
||||||
|
|
||||||
this.testfileArray = await Promise.all(
|
this.testfileArray = await Promise.all(
|
||||||
testFiles.map(async (filePath) => {
|
allTestFiles.map(async (filePath) => {
|
||||||
const absolutePath = plugins.path.isAbsolute(filePath)
|
const absolutePath = plugins.path.isAbsolute(filePath)
|
||||||
? filePath
|
? filePath
|
||||||
: plugins.path.join(dirPath, filePath);
|
: plugins.path.join(dirPath, filePath);
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ import { TsTestLogger } from './tstest.logging.js';
|
|||||||
import type { LogOptions } from './tstest.logging.js';
|
import type { LogOptions } from './tstest.logging.js';
|
||||||
|
|
||||||
// Runtime adapters
|
// Runtime adapters
|
||||||
import { parseTestFilename } from './tstest.classes.runtime.parser.js';
|
import { parseTestFilename, isDockerTestFile, parseDockerTestFilename } from './tstest.classes.runtime.parser.js';
|
||||||
import { RuntimeAdapterRegistry } from './tstest.classes.runtime.adapter.js';
|
import { RuntimeAdapterRegistry } from './tstest.classes.runtime.adapter.js';
|
||||||
import { NodeRuntimeAdapter } from './tstest.classes.runtime.node.js';
|
import { NodeRuntimeAdapter } from './tstest.classes.runtime.node.js';
|
||||||
import { ChromiumRuntimeAdapter } from './tstest.classes.runtime.chromium.js';
|
import { ChromiumRuntimeAdapter } from './tstest.classes.runtime.chromium.js';
|
||||||
import { DenoRuntimeAdapter } from './tstest.classes.runtime.deno.js';
|
import { DenoRuntimeAdapter } from './tstest.classes.runtime.deno.js';
|
||||||
import { BunRuntimeAdapter } from './tstest.classes.runtime.bun.js';
|
import { BunRuntimeAdapter } from './tstest.classes.runtime.bun.js';
|
||||||
|
import { DockerRuntimeAdapter } from './tstest.classes.runtime.docker.js';
|
||||||
|
|
||||||
export class TsTest {
|
export class TsTest {
|
||||||
public testDir: TestDirectory;
|
public testDir: TestDirectory;
|
||||||
@@ -37,6 +38,7 @@ export class TsTest {
|
|||||||
public tsbundleInstance = new plugins.tsbundle.TsBundle();
|
public tsbundleInstance = new plugins.tsbundle.TsBundle();
|
||||||
|
|
||||||
public runtimeRegistry = new RuntimeAdapterRegistry();
|
public runtimeRegistry = new RuntimeAdapterRegistry();
|
||||||
|
public dockerAdapter: DockerRuntimeAdapter | null = null;
|
||||||
|
|
||||||
constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = [], startFromFile: number | null = null, stopAtFile: number | null = null, timeoutSeconds: number | null = null) {
|
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.executionMode = executionModeArg;
|
||||||
@@ -60,6 +62,14 @@ export class TsTest {
|
|||||||
this.runtimeRegistry.register(
|
this.runtimeRegistry.register(
|
||||||
new BunRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags)
|
new BunRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Initialize Docker adapter
|
||||||
|
this.dockerAdapter = new DockerRuntimeAdapter(
|
||||||
|
this.logger,
|
||||||
|
this.smartshellInstance,
|
||||||
|
this.timeoutSeconds,
|
||||||
|
cwdArg
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -211,8 +221,14 @@ export class TsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) {
|
private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) {
|
||||||
// Parse the filename to determine runtimes and modifiers
|
|
||||||
const fileName = plugins.path.basename(fileNameArg);
|
const fileName = plugins.path.basename(fileNameArg);
|
||||||
|
|
||||||
|
// Check if this is a Docker test file
|
||||||
|
if (isDockerTestFile(fileName)) {
|
||||||
|
return await this.runDockerTest(fileNameArg, fileIndex, totalFiles, tapCombinator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the filename to determine runtimes and modifiers (for TypeScript tests)
|
||||||
const parsed = parseTestFilename(fileName, { strictUnknownRuntime: false });
|
const parsed = parseTestFilename(fileName, { strictUnknownRuntime: false });
|
||||||
|
|
||||||
// Check for nonci modifier in CI environment
|
// Check for nonci modifier in CI environment
|
||||||
@@ -258,6 +274,28 @@ export class TsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a Docker test file
|
||||||
|
*/
|
||||||
|
private async runDockerTest(
|
||||||
|
fileNameArg: string,
|
||||||
|
fileIndex: number,
|
||||||
|
totalFiles: number,
|
||||||
|
tapCombinator: TapCombinator
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.dockerAdapter) {
|
||||||
|
this.logger.tapOutput(cs('❌ Docker adapter not initialized', 'red'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tapParser = await this.dockerAdapter.run(fileNameArg, fileIndex, totalFiles);
|
||||||
|
tapCombinator.addTapParser(tapParser);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.tapOutput(cs(`❌ Docker test failed: ${error.message}`, 'red'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async runInNode(fileNameArg: string, index: number, total: number): Promise<TapParser> {
|
public async runInNode(fileNameArg: string, index: number, total: number): Promise<TapParser> {
|
||||||
this.logger.testFileStart(fileNameArg, 'node.js', index, total);
|
this.logger.testFileStart(fileNameArg, 'node.js', index, total);
|
||||||
const tapParser = new TapParser(fileNameArg + ':node', this.logger);
|
const tapParser = new TapParser(fileNameArg + ':node', this.logger);
|
||||||
|
|||||||
Reference in New Issue
Block a user