magnitude-core
Version:
Magnitude e2e testing agent
133 lines (132 loc) • 5.03 kB
JavaScript
import logger from "@/logger";
export async function retryOnErrorIsSuccess(fnToRetry, retryOptions) {
try {
await retryOnError(fnToRetry, retryOptions);
return true;
}
catch (error) {
return false;
}
}
export async function retryOnError(fnToRetry, retryOptions) {
let lastError;
const options = {
...retryOptions,
retryLimit: retryOptions.retryLimit ?? 3,
delayMs: retryOptions.delayMs ?? 200,
showWarnOnRetry: retryOptions.showWarnOnRetry ?? false,
//throw: retryOptions.throw ?? true
};
if (options.retryLimit < 0) {
options.retryLimit = 0;
}
for (let attempt = 0; attempt <= options.retryLimit; attempt++) {
try {
return await fnToRetry();
}
catch (error) {
lastError = error;
const errorMessage = String(error?.message ?? error);
if (options.mode === 'retry_all') {
if (options.showWarnOnRetry) {
logger.warn(`Retrying on: ${errorMessage}`);
}
}
else if (options.mode === 'retry_on_partial_message') {
const includesSubstring = options.errorSubstrings.some((substring) => errorMessage.includes(substring));
if (includesSubstring) {
if (options.showWarnOnRetry) {
logger.warn(`Retrying on: ${errorMessage}`);
}
}
else {
// Error message does NOT contain the target substring. This error is not retryable.
throw lastError; // Throw this current error immediately.
}
}
}
await new Promise((resolve) => setTimeout(resolve, options.delayMs));
}
throw lastError;
//if (options.throw) throw lastError;
}
/**
* Performs a deep comparison between two values to determine if they are equivalent.
*
* @param a The first value to compare.
* @param b The second value to compare.
* @param cache A WeakMap to handle circular references. Should not be provided by the caller.
* @returns `true` if the values are deeply equal, `false` otherwise.
*/
export function deepEquals(a, b, cache = new WeakMap()) {
// 1. Strict equality check (handles primitives, same object references, null, undefined)
if (a === b) {
return true;
}
// 2. If a or b is null or not an object, and they weren't ===, they are not equal.
// (This covers cases like comparing a primitive to an object, or one null and one object)
if (a == null || typeof a !== 'object' || b == null || typeof b !== 'object') {
return false;
}
// 3. Handle circular references:
// If we've already started comparing 'a' and 'b' in the current path,
// assume they are equal to break the cycle.
if (cache.has(a) && cache.get(a).has(b)) {
return true;
}
// Add to cache before recursive calls
if (!cache.has(a)) {
cache.set(a, new WeakSet());
}
cache.get(a).add(b);
// Symmetrically for b -> a to catch cycles like a.c = b; b.c = a;
if (!cache.has(b)) {
cache.set(b, new WeakSet());
}
cache.get(b).add(a);
// 4. Handle Dates
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
// 5. Handle Regular Expressions
if (a instanceof RegExp && b instanceof RegExp) {
return a.source === b.source && a.flags === b.flags;
}
// 6. Handle Arrays
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!deepEquals(a[i], b[i], cache)) {
return false;
}
}
return true;
}
// 7. Handle plain Objects
// At this point, 'a' and 'b' are objects but not arrays, dates, or regexps.
// If one is an array and the other an object, they are not equal.
if (Array.isArray(a) || Array.isArray(b)) {
return false;
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) {
return false;
}
for (const key of keysA) {
// Check if key exists in B. `Object.keys` only returns own enumerable properties.
// `hasOwnProperty` is a more robust check if b might have non-enumerable properties from its prototype with the same name.
// However, since we compare keysA.length and keysB.length, if key is in keysA but not in keysB,
// lengths would mismatch or the loop over keysB (if we did that) would find a key in B not in A.
// The most crucial part is `Object.prototype.hasOwnProperty.call(b, key)`
if (!Object.prototype.hasOwnProperty.call(b, key)) {
return false;
}
if (!deepEquals(a[key], b[key], cache)) {
return false;
}
}
return true;
}