testify-universal-cli
Version:
Universal interactive CLI tool for scanning and executing tests across multiple programming languages
229 lines (203 loc) • 5.98 kB
JavaScript
import { execa } from 'execa';
import { findUp } from 'find-up';
import fs from 'node:fs/promises';
/**
* Get a Ruby test runner
*
* @param {string} customRunner - Custom test runner
* @returns {object} - Ruby test runner
*/
export function getRubyRunner(customRunner) {
return {
/**
* Run all tests
*
* @param {Array<{filePath: string, relativePath: string, markers: string[]}>} testFiles - Test files
* @param {string} cwd - Current working directory
* @param {string[]} markers - Test markers
* @returns {Promise<{stdout: string, failed: boolean}>} - Test results
*/
async runAll(testFiles, cwd, markers = []) {
const runner = await determineTestRunner(cwd, customRunner);
try {
// Build command based on the test runner
const { command, args } = buildRunAllCommand(runner, testFiles, markers);
const { stdout, stderr, exitCode } = await execa(command, args, {
cwd,
all: true,
reject: false,
});
return {
stdout: stdout || stderr,
failed: exitCode !== 0,
};
} catch (error) {
return {
stdout: error.message,
failed: true,
};
}
},
/**
* Run a single test file
*
* @param {object} testFile - Test file
* @param {string} testFile.filePath - Path to test file
* @param {string[]} testFile.markers - Test markers
* @param {string} cwd - Current working directory
* @returns {Promise<{stdout: string, failed: boolean}>} - Test results
*/
async runFile(testFile, cwd) {
const runner = await determineTestRunner(cwd, customRunner);
try {
// Build command based on the test runner
const { command, args } = buildRunFileCommand(runner, testFile);
const { stdout, stderr, exitCode } = await execa(command, args, {
cwd,
all: true,
reject: false,
});
return {
stdout: stdout || stderr,
failed: exitCode !== 0,
};
} catch (error) {
return {
stdout: error.message,
failed: true,
};
}
},
};
}
/**
* Determine the test runner to use
*
* @param {string} cwd - Current working directory
* @param {string} customRunner - Custom test runner
* @returns {Promise<string>} - Test runner to use
*/
async function determineTestRunner(cwd, customRunner) {
if (customRunner) {
return customRunner;
}
try {
// Check for Ruby test runner configuration files
const hasRSpec = await findUp(['.rspec', 'spec/spec_helper.rb'], { cwd });
if (hasRSpec) {
return 'rspec';
}
const hasRake = await findUp(['Rakefile'], { cwd });
if (hasRake) {
try {
const { stdout } = await execa('rake', ['-T'], { cwd });
if (stdout.includes('test') || stdout.includes('spec')) {
return 'rake';
}
} catch {
// Failed to run rake -T, try another approach
}
}
// Default to RSpec if nothing else is found
return 'rspec';
} catch (error) {
// Default to RSpec on error
return 'rspec';
}
}
/**
* Build command for running all tests
*
* @param {string} runner - Test runner
* @param {Array<{filePath: string, relativePath: string, markers: string[]}>} testFiles - Test files
* @param {string[]} markers - Test markers
* @returns {object} - Command and args
*/
function buildRunAllCommand(runner, testFiles, markers = []) {
switch (runner) {
case 'rspec':
const args = ['--format', 'documentation'];
// Add tag filtering if markers are provided
if (markers.length > 0) {
args.push('--tag', markers.join(','));
}
// Add test files to args if markers are provided and files are filtered
if (markers.length > 0 && testFiles.length > 0) {
const testPatterns = testFiles.map(file => file.filePath);
args.push(...testPatterns);
}
return {
command: 'bundle',
args: ['exec', 'rspec', ...args],
};
case 'rake':
return {
command: 'bundle',
args: ['exec', 'rake', 'test'],
};
default:
return {
command: 'bundle',
args: ['exec', runner],
};
}
}
/**
* Build command for running a single test file
*
* @param {string} runner - Test runner
* @param {object} testFile - Test file
* @returns {object} - Command and args
*/
function buildRunFileCommand(runner, testFile) {
switch (runner) {
case 'rspec':
return {
command: 'bundle',
args: ['exec', 'rspec', testFile.filePath, '--format', 'documentation'],
};
case 'rake':
// For Rake, we need to set the TEST environment variable
return {
command: 'bundle',
args: ['exec', 'rake', 'test', `TEST=${testFile.filePath}`],
};
default:
return {
command: 'bundle',
args: ['exec', runner, testFile.filePath],
};
}
}
/**
* Extract test tags from RSpec files
*
* @param {string} filePath - Path to test file
* @returns {Promise<string[]>} - Extracted tags
*/
export async function extractRSpecTags(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
const tags = new Set();
// RSpec tag patterns
const tagPattern = /it\s*(?:,\s*(?:[:']([a-zA-Z0-9_]+)['"]|:([a-zA-Z0-9_]+)))/g;
let match;
while ((match = tagPattern.exec(content)) !== null) {
const tag = match[1] || match[2];
if (tag) {
tags.add(tag);
}
}
// Look for metadata tags
const metadataPattern = /(?:describe|context|it)\s*.*\s*,\s*(?:[:']([a-zA-Z0-9_]+)['"]|:([a-zA-Z0-9_]+))\s*=>/g;
while ((match = metadataPattern.exec(content)) !== null) {
const tag = match[1] || match[2];
if (tag) {
tags.add(tag);
}
}
return [...tags];
} catch (error) {
return [];
}
}