chore: run format
This commit is contained in:
@@ -7,205 +7,219 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export class ParallelTestRunner extends EventEmitter {
|
||||
constructor(logger) {
|
||||
super();
|
||||
this.logger = logger;
|
||||
this.workers = [];
|
||||
this.results = {};
|
||||
}
|
||||
constructor(logger) {
|
||||
super();
|
||||
this.logger = logger;
|
||||
this.workers = [];
|
||||
this.results = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Run test groups in parallel
|
||||
* @param {Object} testGroups - Groups of tests to run
|
||||
* @param {Object} sharedContext - Shared context for all tests
|
||||
* @returns {Promise<Object>} Combined results from all test groups
|
||||
*/
|
||||
async runTestGroups(testGroups, sharedContext) {
|
||||
const groupNames = Object.keys(testGroups);
|
||||
const workerPromises = [];
|
||||
/**
|
||||
* Run test groups in parallel
|
||||
* @param {Object} testGroups - Groups of tests to run
|
||||
* @param {Object} sharedContext - Shared context for all tests
|
||||
* @returns {Promise<Object>} Combined results from all test groups
|
||||
*/
|
||||
async runTestGroups(testGroups, sharedContext) {
|
||||
const groupNames = Object.keys(testGroups);
|
||||
const workerPromises = [];
|
||||
|
||||
this.logger.info(`Starting parallel execution of ${groupNames.length} test groups`);
|
||||
this.logger.info(
|
||||
`Starting parallel execution of ${groupNames.length} test groups`
|
||||
);
|
||||
|
||||
for (const groupName of groupNames) {
|
||||
const workerPromise = this.runTestGroup(groupName, testGroups[groupName], sharedContext);
|
||||
workerPromises.push(workerPromise);
|
||||
}
|
||||
for (const groupName of groupNames) {
|
||||
const workerPromise = this.runTestGroup(
|
||||
groupName,
|
||||
testGroups[groupName],
|
||||
sharedContext
|
||||
);
|
||||
workerPromises.push(workerPromise);
|
||||
}
|
||||
|
||||
// Wait for all workers to complete
|
||||
const results = await Promise.allSettled(workerPromises);
|
||||
// Wait for all workers to complete
|
||||
const results = await Promise.allSettled(workerPromises);
|
||||
|
||||
// Process results
|
||||
const combinedResults = {
|
||||
overall: 'passed',
|
||||
groups: {},
|
||||
summary: {
|
||||
totalGroups: groupNames.length,
|
||||
passedGroups: 0,
|
||||
failedGroups: 0,
|
||||
errors: []
|
||||
}
|
||||
};
|
||||
// Process results
|
||||
const combinedResults = {
|
||||
overall: 'passed',
|
||||
groups: {},
|
||||
summary: {
|
||||
totalGroups: groupNames.length,
|
||||
passedGroups: 0,
|
||||
failedGroups: 0,
|
||||
errors: []
|
||||
}
|
||||
};
|
||||
|
||||
results.forEach((result, index) => {
|
||||
const groupName = groupNames[index];
|
||||
|
||||
if (result.status === 'fulfilled') {
|
||||
combinedResults.groups[groupName] = result.value;
|
||||
if (result.value.status === 'passed') {
|
||||
combinedResults.summary.passedGroups++;
|
||||
} else {
|
||||
combinedResults.summary.failedGroups++;
|
||||
combinedResults.overall = 'failed';
|
||||
}
|
||||
} else {
|
||||
combinedResults.groups[groupName] = {
|
||||
status: 'failed',
|
||||
error: result.reason.message || 'Unknown error'
|
||||
};
|
||||
combinedResults.summary.failedGroups++;
|
||||
combinedResults.summary.errors.push({
|
||||
group: groupName,
|
||||
error: result.reason.message
|
||||
});
|
||||
combinedResults.overall = 'failed';
|
||||
}
|
||||
});
|
||||
results.forEach((result, index) => {
|
||||
const groupName = groupNames[index];
|
||||
|
||||
return combinedResults;
|
||||
}
|
||||
if (result.status === 'fulfilled') {
|
||||
combinedResults.groups[groupName] = result.value;
|
||||
if (result.value.status === 'passed') {
|
||||
combinedResults.summary.passedGroups++;
|
||||
} else {
|
||||
combinedResults.summary.failedGroups++;
|
||||
combinedResults.overall = 'failed';
|
||||
}
|
||||
} else {
|
||||
combinedResults.groups[groupName] = {
|
||||
status: 'failed',
|
||||
error: result.reason.message || 'Unknown error'
|
||||
};
|
||||
combinedResults.summary.failedGroups++;
|
||||
combinedResults.summary.errors.push({
|
||||
group: groupName,
|
||||
error: result.reason.message
|
||||
});
|
||||
combinedResults.overall = 'failed';
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Run a single test group in a worker thread
|
||||
*/
|
||||
async runTestGroup(groupName, testModules, sharedContext) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const workerPath = join(__dirname, 'test-worker.js');
|
||||
|
||||
const worker = new Worker(workerPath, {
|
||||
workerData: {
|
||||
groupName,
|
||||
testModules,
|
||||
sharedContext,
|
||||
logDir: this.logger.logDir,
|
||||
testRunId: this.logger.testRunId
|
||||
}
|
||||
});
|
||||
return combinedResults;
|
||||
}
|
||||
|
||||
this.workers.push(worker);
|
||||
/**
|
||||
* Run a single test group in a worker thread
|
||||
*/
|
||||
async runTestGroup(groupName, testModules, sharedContext) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const workerPath = join(__dirname, 'test-worker.js');
|
||||
|
||||
// Handle messages from worker
|
||||
worker.on('message', (message) => {
|
||||
if (message.type === 'log') {
|
||||
const level = message.level.toLowerCase();
|
||||
if (typeof this.logger[level] === 'function') {
|
||||
this.logger[level](message.message);
|
||||
} else {
|
||||
// Fallback to info if the level doesn't exist
|
||||
this.logger.info(message.message);
|
||||
}
|
||||
} else if (message.type === 'step') {
|
||||
this.logger.step(message.message);
|
||||
} else if (message.type === 'cost') {
|
||||
this.logger.addCost(message.cost);
|
||||
} else if (message.type === 'results') {
|
||||
this.results[groupName] = message.results;
|
||||
}
|
||||
});
|
||||
const worker = new Worker(workerPath, {
|
||||
workerData: {
|
||||
groupName,
|
||||
testModules,
|
||||
sharedContext,
|
||||
logDir: this.logger.logDir,
|
||||
testRunId: this.logger.testRunId
|
||||
}
|
||||
});
|
||||
|
||||
// Handle worker completion
|
||||
worker.on('exit', (code) => {
|
||||
this.workers = this.workers.filter(w => w !== worker);
|
||||
|
||||
if (code === 0) {
|
||||
resolve(this.results[groupName] || { status: 'passed', group: groupName });
|
||||
} else {
|
||||
reject(new Error(`Worker for group ${groupName} exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
this.workers.push(worker);
|
||||
|
||||
// Handle worker errors
|
||||
worker.on('error', (error) => {
|
||||
this.workers = this.workers.filter(w => w !== worker);
|
||||
reject(error);
|
||||
});
|
||||
// Handle messages from worker
|
||||
worker.on('message', (message) => {
|
||||
if (message.type === 'log') {
|
||||
const level = message.level.toLowerCase();
|
||||
if (typeof this.logger[level] === 'function') {
|
||||
this.logger[level](message.message);
|
||||
} else {
|
||||
// Fallback to info if the level doesn't exist
|
||||
this.logger.info(message.message);
|
||||
}
|
||||
} else if (message.type === 'step') {
|
||||
this.logger.step(message.message);
|
||||
} else if (message.type === 'cost') {
|
||||
this.logger.addCost(message.cost);
|
||||
} else if (message.type === 'results') {
|
||||
this.results[groupName] = message.results;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
// Handle worker completion
|
||||
worker.on('exit', (code) => {
|
||||
this.workers = this.workers.filter((w) => w !== worker);
|
||||
|
||||
/**
|
||||
* Terminate all running workers
|
||||
*/
|
||||
async terminate() {
|
||||
const terminationPromises = this.workers.map(worker =>
|
||||
worker.terminate().catch(err =>
|
||||
this.logger.warning(`Failed to terminate worker: ${err.message}`)
|
||||
)
|
||||
);
|
||||
if (code === 0) {
|
||||
resolve(
|
||||
this.results[groupName] || { status: 'passed', group: groupName }
|
||||
);
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Worker for group ${groupName} exited with code ${code}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(terminationPromises);
|
||||
this.workers = [];
|
||||
}
|
||||
// Handle worker errors
|
||||
worker.on('error', (error) => {
|
||||
this.workers = this.workers.filter((w) => w !== worker);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate all running workers
|
||||
*/
|
||||
async terminate() {
|
||||
const terminationPromises = this.workers.map((worker) =>
|
||||
worker
|
||||
.terminate()
|
||||
.catch((err) =>
|
||||
this.logger.warning(`Failed to terminate worker: ${err.message}`)
|
||||
)
|
||||
);
|
||||
|
||||
await Promise.all(terminationPromises);
|
||||
this.workers = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequential test runner for comparison or fallback
|
||||
*/
|
||||
export class SequentialTestRunner {
|
||||
constructor(logger, helpers) {
|
||||
this.logger = logger;
|
||||
this.helpers = helpers;
|
||||
}
|
||||
constructor(logger, helpers) {
|
||||
this.logger = logger;
|
||||
this.helpers = helpers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tests sequentially
|
||||
*/
|
||||
async runTests(testModules, context) {
|
||||
const results = {
|
||||
overall: 'passed',
|
||||
tests: {},
|
||||
summary: {
|
||||
totalTests: testModules.length,
|
||||
passedTests: 0,
|
||||
failedTests: 0,
|
||||
errors: []
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Run tests sequentially
|
||||
*/
|
||||
async runTests(testModules, context) {
|
||||
const results = {
|
||||
overall: 'passed',
|
||||
tests: {},
|
||||
summary: {
|
||||
totalTests: testModules.length,
|
||||
passedTests: 0,
|
||||
failedTests: 0,
|
||||
errors: []
|
||||
}
|
||||
};
|
||||
|
||||
for (const testModule of testModules) {
|
||||
try {
|
||||
this.logger.step(`Running ${testModule} tests`);
|
||||
|
||||
// Dynamic import of test module
|
||||
const testPath = join(dirname(__dirname), 'tests', `${testModule}.test.js`);
|
||||
const { default: testFn } = await import(testPath);
|
||||
|
||||
// Run the test
|
||||
const testResults = await testFn(this.logger, this.helpers, context);
|
||||
|
||||
results.tests[testModule] = testResults;
|
||||
|
||||
if (testResults.status === 'passed') {
|
||||
results.summary.passedTests++;
|
||||
} else {
|
||||
results.summary.failedTests++;
|
||||
results.overall = 'failed';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to run ${testModule}: ${error.message}`);
|
||||
results.tests[testModule] = {
|
||||
status: 'failed',
|
||||
error: error.message
|
||||
};
|
||||
results.summary.failedTests++;
|
||||
results.summary.errors.push({
|
||||
test: testModule,
|
||||
error: error.message
|
||||
});
|
||||
results.overall = 'failed';
|
||||
}
|
||||
}
|
||||
for (const testModule of testModules) {
|
||||
try {
|
||||
this.logger.step(`Running ${testModule} tests`);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
// Dynamic import of test module
|
||||
const testPath = join(
|
||||
dirname(__dirname),
|
||||
'tests',
|
||||
`${testModule}.test.js`
|
||||
);
|
||||
const { default: testFn } = await import(testPath);
|
||||
|
||||
// Run the test
|
||||
const testResults = await testFn(this.logger, this.helpers, context);
|
||||
|
||||
results.tests[testModule] = testResults;
|
||||
|
||||
if (testResults.status === 'passed') {
|
||||
results.summary.passedTests++;
|
||||
} else {
|
||||
results.summary.failedTests++;
|
||||
results.overall = 'failed';
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to run ${testModule}: ${error.message}`);
|
||||
results.tests[testModule] = {
|
||||
status: 'failed',
|
||||
error: error.message
|
||||
};
|
||||
results.summary.failedTests++;
|
||||
results.summary.errors.push({
|
||||
test: testModule,
|
||||
error: error.message
|
||||
});
|
||||
results.overall = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,124 +9,127 @@ const __dirname = dirname(__filename);
|
||||
|
||||
// Worker logger that sends messages to parent
|
||||
class WorkerLogger extends TestLogger {
|
||||
constructor(logDir, testRunId, groupName) {
|
||||
super(logDir, `${testRunId}_${groupName}`);
|
||||
this.groupName = groupName;
|
||||
}
|
||||
constructor(logDir, testRunId, groupName) {
|
||||
super(logDir, `${testRunId}_${groupName}`);
|
||||
this.groupName = groupName;
|
||||
}
|
||||
|
||||
log(level, message, options = {}) {
|
||||
super.log(level, message, options);
|
||||
|
||||
// Send log to parent
|
||||
parentPort.postMessage({
|
||||
type: 'log',
|
||||
level: level.toLowerCase(),
|
||||
message: `[${this.groupName}] ${message}`
|
||||
});
|
||||
}
|
||||
log(level, message, options = {}) {
|
||||
super.log(level, message, options);
|
||||
|
||||
step(message) {
|
||||
super.step(message);
|
||||
|
||||
parentPort.postMessage({
|
||||
type: 'step',
|
||||
message: `[${this.groupName}] ${message}`
|
||||
});
|
||||
}
|
||||
// Send log to parent
|
||||
parentPort.postMessage({
|
||||
type: 'log',
|
||||
level: level.toLowerCase(),
|
||||
message: `[${this.groupName}] ${message}`
|
||||
});
|
||||
}
|
||||
|
||||
addCost(cost) {
|
||||
super.addCost(cost);
|
||||
|
||||
parentPort.postMessage({
|
||||
type: 'cost',
|
||||
cost
|
||||
});
|
||||
}
|
||||
step(message) {
|
||||
super.step(message);
|
||||
|
||||
parentPort.postMessage({
|
||||
type: 'step',
|
||||
message: `[${this.groupName}] ${message}`
|
||||
});
|
||||
}
|
||||
|
||||
addCost(cost) {
|
||||
super.addCost(cost);
|
||||
|
||||
parentPort.postMessage({
|
||||
type: 'cost',
|
||||
cost
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Main worker execution
|
||||
async function runTestGroup() {
|
||||
const { groupName, testModules, sharedContext, logDir, testRunId } = workerData;
|
||||
|
||||
const logger = new WorkerLogger(logDir, testRunId, groupName);
|
||||
const helpers = new TestHelpers(logger);
|
||||
|
||||
logger.info(`Worker started for test group: ${groupName}`);
|
||||
|
||||
const results = {
|
||||
group: groupName,
|
||||
status: 'passed',
|
||||
tests: {},
|
||||
errors: [],
|
||||
startTime: Date.now()
|
||||
};
|
||||
const { groupName, testModules, sharedContext, logDir, testRunId } =
|
||||
workerData;
|
||||
|
||||
try {
|
||||
// Run each test module in the group
|
||||
for (const testModule of testModules) {
|
||||
try {
|
||||
logger.info(`Running test: ${testModule}`);
|
||||
|
||||
// Dynamic import of test module
|
||||
const testPath = join(dirname(__dirname), 'tests', `${testModule}.test.js`);
|
||||
const { default: testFn } = await import(testPath);
|
||||
|
||||
// Run the test with shared context
|
||||
const testResults = await testFn(logger, helpers, sharedContext);
|
||||
|
||||
results.tests[testModule] = testResults;
|
||||
|
||||
if (testResults.status !== 'passed') {
|
||||
results.status = 'failed';
|
||||
if (testResults.errors) {
|
||||
results.errors.push(...testResults.errors);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Test ${testModule} failed: ${error.message}`);
|
||||
results.tests[testModule] = {
|
||||
status: 'failed',
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
};
|
||||
results.status = 'failed';
|
||||
results.errors.push({
|
||||
test: testModule,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Worker error: ${error.message}`);
|
||||
results.status = 'failed';
|
||||
results.errors.push({
|
||||
group: groupName,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
const logger = new WorkerLogger(logDir, testRunId, groupName);
|
||||
const helpers = new TestHelpers(logger);
|
||||
|
||||
results.endTime = Date.now();
|
||||
results.duration = results.endTime - results.startTime;
|
||||
|
||||
// Flush logs and get summary
|
||||
logger.flush();
|
||||
const summary = logger.getSummary();
|
||||
results.summary = summary;
|
||||
logger.info(`Worker started for test group: ${groupName}`);
|
||||
|
||||
// Send results to parent
|
||||
parentPort.postMessage({
|
||||
type: 'results',
|
||||
results
|
||||
});
|
||||
const results = {
|
||||
group: groupName,
|
||||
status: 'passed',
|
||||
tests: {},
|
||||
errors: [],
|
||||
startTime: Date.now()
|
||||
};
|
||||
|
||||
logger.info(`Worker completed for test group: ${groupName}`);
|
||||
try {
|
||||
// Run each test module in the group
|
||||
for (const testModule of testModules) {
|
||||
try {
|
||||
logger.info(`Running test: ${testModule}`);
|
||||
|
||||
// Dynamic import of test module
|
||||
const testPath = join(
|
||||
dirname(__dirname),
|
||||
'tests',
|
||||
`${testModule}.test.js`
|
||||
);
|
||||
const { default: testFn } = await import(testPath);
|
||||
|
||||
// Run the test with shared context
|
||||
const testResults = await testFn(logger, helpers, sharedContext);
|
||||
|
||||
results.tests[testModule] = testResults;
|
||||
|
||||
if (testResults.status !== 'passed') {
|
||||
results.status = 'failed';
|
||||
if (testResults.errors) {
|
||||
results.errors.push(...testResults.errors);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Test ${testModule} failed: ${error.message}`);
|
||||
results.tests[testModule] = {
|
||||
status: 'failed',
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
};
|
||||
results.status = 'failed';
|
||||
results.errors.push({
|
||||
test: testModule,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Worker error: ${error.message}`);
|
||||
results.status = 'failed';
|
||||
results.errors.push({
|
||||
group: groupName,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
|
||||
results.endTime = Date.now();
|
||||
results.duration = results.endTime - results.startTime;
|
||||
|
||||
// Flush logs and get summary
|
||||
logger.flush();
|
||||
const summary = logger.getSummary();
|
||||
results.summary = summary;
|
||||
|
||||
// Send results to parent
|
||||
parentPort.postMessage({
|
||||
type: 'results',
|
||||
results
|
||||
});
|
||||
|
||||
logger.info(`Worker completed for test group: ${groupName}`);
|
||||
}
|
||||
|
||||
// Run the test group
|
||||
runTestGroup().catch(error => {
|
||||
console.error('Worker fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
runTestGroup().catch((error) => {
|
||||
console.error('Worker fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user