feat(tstest): Enhance tstest with fluent API, suite grouping, tag filtering, fixture & snapshot testing, and parallel execution improvements
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import * as plugins from './tapbundle.plugins.js';
|
||||
import { tapCreator } from './tapbundle.tapcreator.js';
|
||||
import { TapTools } from './tapbundle.classes.taptools.js';
|
||||
import { TapTools, SkipError } from './tapbundle.classes.taptools.js';
|
||||
|
||||
// imported interfaces
|
||||
import { Deferred } from '@push.rocks/smartpromise';
|
||||
import { HrtMeasurement } from '@push.rocks/smarttime';
|
||||
|
||||
// interfaces
|
||||
export type TTestStatus = 'success' | 'error' | 'pending' | 'errorAfterSuccess' | 'timeout';
|
||||
export type TTestStatus = 'success' | 'error' | 'pending' | 'errorAfterSuccess' | 'timeout' | 'skipped';
|
||||
|
||||
export interface ITestFunction<T> {
|
||||
(tapTools?: TapTools): Promise<T>;
|
||||
@@ -22,6 +22,12 @@ export class TapTest<T = unknown> {
|
||||
public tapTools: TapTools;
|
||||
public testFunction: ITestFunction<T>;
|
||||
public testKey: number; // the testKey the position in the test qeue. Set upon calling .run()
|
||||
public timeoutMs?: number;
|
||||
public isTodo: boolean = false;
|
||||
public todoReason?: string;
|
||||
public tags: string[] = [];
|
||||
public priority: 'high' | 'medium' | 'low' = 'medium';
|
||||
public fileName?: string;
|
||||
private testDeferred: Deferred<TapTest<T>> = plugins.smartpromise.defer();
|
||||
public testPromise: Promise<TapTest<T>> = this.testDeferred.promise;
|
||||
private testResultDeferred: Deferred<T> = plugins.smartpromise.defer();
|
||||
@@ -46,42 +52,102 @@ export class TapTest<T = unknown> {
|
||||
* run the test
|
||||
*/
|
||||
public async run(testKeyArg: number) {
|
||||
this.hrtMeasurement.start();
|
||||
this.testKey = testKeyArg;
|
||||
const testNumber = testKeyArg + 1;
|
||||
try {
|
||||
const testReturnValue = await this.testFunction(this.tapTools);
|
||||
if (this.status === 'timeout') {
|
||||
throw new Error('Test succeeded, but timed out...');
|
||||
}
|
||||
this.hrtMeasurement.stop();
|
||||
console.log(
|
||||
`ok ${testNumber} - ${this.description} # time=${this.hrtMeasurement.milliSeconds}ms`,
|
||||
);
|
||||
|
||||
// Handle todo tests
|
||||
if (this.isTodo) {
|
||||
const todoText = this.todoReason ? `# TODO ${this.todoReason}` : '# TODO';
|
||||
console.log(`ok ${testNumber} - ${this.description} ${todoText}`);
|
||||
this.status = 'success';
|
||||
this.testDeferred.resolve(this);
|
||||
this.testResultDeferred.resolve(testReturnValue);
|
||||
} catch (err: any) {
|
||||
this.hrtMeasurement.stop();
|
||||
console.log(
|
||||
`not ok ${testNumber} - ${this.description} # time=${this.hrtMeasurement.milliSeconds}ms`,
|
||||
);
|
||||
this.testDeferred.resolve(this);
|
||||
this.testResultDeferred.resolve(err);
|
||||
|
||||
// if the test has already succeeded before
|
||||
if (this.status === 'success') {
|
||||
this.status = 'errorAfterSuccess';
|
||||
console.log('!!! ALERT !!!: weird behaviour, since test has been already successfull');
|
||||
} else {
|
||||
this.status = 'error';
|
||||
return;
|
||||
}
|
||||
|
||||
// Run test with retries
|
||||
let lastError: any;
|
||||
const maxRetries = this.tapTools.maxRetries;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
this.hrtMeasurement.start();
|
||||
|
||||
try {
|
||||
// Set up timeout if specified
|
||||
let timeoutHandle: any;
|
||||
let timeoutPromise: Promise<never> | null = null;
|
||||
|
||||
if (this.timeoutMs) {
|
||||
timeoutPromise = new Promise<never>((_, reject) => {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
this.status = 'timeout';
|
||||
reject(new Error(`Test timed out after ${this.timeoutMs}ms`));
|
||||
}, this.timeoutMs);
|
||||
});
|
||||
}
|
||||
|
||||
// Run the test function with potential timeout
|
||||
const testPromise = this.testFunction(this.tapTools);
|
||||
const testReturnValue = timeoutPromise
|
||||
? await Promise.race([testPromise, timeoutPromise])
|
||||
: await testPromise;
|
||||
|
||||
// Clear timeout if test completed
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
|
||||
this.hrtMeasurement.stop();
|
||||
console.log(
|
||||
`ok ${testNumber} - ${this.description} # time=${this.hrtMeasurement.milliSeconds}ms`,
|
||||
);
|
||||
this.status = 'success';
|
||||
this.testDeferred.resolve(this);
|
||||
this.testResultDeferred.resolve(testReturnValue);
|
||||
return; // Success, exit retry loop
|
||||
|
||||
} catch (err: any) {
|
||||
this.hrtMeasurement.stop();
|
||||
|
||||
// Handle skip
|
||||
if (err instanceof SkipError || err.name === 'SkipError') {
|
||||
console.log(`ok ${testNumber} - ${this.description} # SKIP ${err.message.replace('Skipped: ', '')}`);
|
||||
this.status = 'skipped';
|
||||
this.testDeferred.resolve(this);
|
||||
return;
|
||||
}
|
||||
|
||||
lastError = err;
|
||||
|
||||
// If we have retries left, try again
|
||||
if (attempt < maxRetries) {
|
||||
console.log(
|
||||
`# Retry ${attempt + 1}/${maxRetries} for test: ${this.description}`,
|
||||
);
|
||||
this.tapTools._incrementRetryCount();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Final failure
|
||||
console.log(
|
||||
`not ok ${testNumber} - ${this.description} # time=${this.hrtMeasurement.milliSeconds}ms`,
|
||||
);
|
||||
this.testDeferred.resolve(this);
|
||||
this.testResultDeferred.resolve(err);
|
||||
|
||||
// if the test has already succeeded before
|
||||
if (this.status === 'success') {
|
||||
this.status = 'errorAfterSuccess';
|
||||
console.log('!!! ALERT !!!: weird behaviour, since test has been already successfull');
|
||||
} else {
|
||||
this.status = 'error';
|
||||
}
|
||||
|
||||
// if the test is allowed to fail
|
||||
if (this.failureAllowed) {
|
||||
console.log(`please note: failure allowed!`);
|
||||
}
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
// if the test is allowed to fail
|
||||
if (this.failureAllowed) {
|
||||
console.log(`please note: failure allowed!`);
|
||||
}
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user