UNPKG

testify-universal-cli

Version:

Universal interactive CLI tool for scanning and executing tests across multiple programming languages

229 lines (203 loc) 5.98 kB
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 []; } }