Skip to content

Commit 95233de

Browse files
committed
more e2e test improvements
1 parent 4e5f58e commit 95233de

2 files changed

Lines changed: 43 additions & 8 deletions

File tree

packages/b2c-cli/test/functional/e2e/job-execution.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,29 @@ describe('Job Execution E2E Tests', function () {
286286
expect(exists, `Export file should exist at ${exportFilePath}`).to.be.true;
287287

288288
const stats = await fs.stat(exportFilePath);
289-
expect(stats.size, 'Export file should not be empty').to.be.greaterThan(0);
289+
290+
// Default export extracts the zip, so localPath is a directory. On
291+
// Windows, fs.stat() of a directory returns size 0 (NTFS), unlike Linux
292+
// where directory inodes report nonzero. Walk into the directory to
293+
// verify it contains real data.
294+
if (stats.isDirectory()) {
295+
const entries = await fs.readdir(exportFilePath, {recursive: true, withFileTypes: true});
296+
const files = entries.filter((e) => e.isFile());
297+
expect(files.length, `Export directory should contain files at ${exportFilePath}`).to.be.greaterThan(0);
298+
299+
let totalBytes = 0;
300+
for (const file of files) {
301+
const filePath = path.join(file.parentPath ?? exportFilePath, file.name);
302+
// eslint-disable-next-line no-await-in-loop
303+
const fileStat = await fs.stat(filePath);
304+
totalBytes += fileStat.size;
305+
}
306+
expect(totalBytes, `Export directory contents should not be empty (${files.length} files)`).to.be.greaterThan(
307+
0,
308+
);
309+
} else {
310+
expect(stats.size, `Export file should not be empty at ${exportFilePath}`).to.be.greaterThan(0);
311+
}
290312
});
291313
});
292314

packages/b2c-cli/test/functional/e2e/test-utils.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ export async function runCLIWithRetry(args: string[], options: RetryOptions = {}
210210
let lastResult: ExecaReturnValue | null = null;
211211

212212
for (let attempt = 0; attempt <= maxRetries; attempt++) {
213+
if (verbose) {
214+
console.log(` → CLI: b2c ${args.join(' ')} (attempt ${attempt + 1}/${maxRetries + 1})`);
215+
}
216+
213217
// eslint-disable-next-line no-await-in-loop
214218
const result = await runCLI(args, {timeout, env, cwd});
215219

@@ -223,14 +227,22 @@ export async function runCLIWithRetry(args: string[], options: RetryOptions = {}
223227

224228
lastResult = result;
225229

226-
// Check if error is retryable
230+
// Check if error is retryable. Treat execa timeouts as retryable since
231+
// they kill the child process and produce empty stderr (no signal in text).
227232
const errorMsg = toString(result.stderr) || toString(result.stdout);
228-
const isRetryable = retryableErrors.some((pattern) => pattern.test(errorMsg));
233+
const isTimeout = Boolean(result.timedOut) || result.signal === 'SIGTERM';
234+
const isRetryable = isTimeout || retryableErrors.some((pattern) => pattern.test(errorMsg));
229235

230-
// If not retryable or last attempt, return result
236+
// If not retryable or last attempt, return result. Always surface what
237+
// happened on CI so failures aren't silent (empty stderr on timeout).
231238
if (!isRetryable || attempt === maxRetries) {
232-
if (verbose && !isRetryable) {
233-
console.log(` ✗ This looks non-transient; not retrying`);
239+
if (verbose) {
240+
if (isRetryable) {
241+
console.log(` ✗ Exhausted ${maxRetries + 1} attempts; giving up`);
242+
} else {
243+
console.log(` ✗ Non-retryable failure; giving up`);
244+
}
245+
console.log(` ${getErrorDetails(result).split('\n').join('\n ')}`);
234246
}
235247
return result;
236248
}
@@ -239,8 +251,9 @@ export async function runCLIWithRetry(args: string[], options: RetryOptions = {}
239251
const delay = Math.min(initialDelay * 2 ** attempt, maxDelay);
240252

241253
if (verbose) {
242-
console.log(` ⚠ Temporary issue (attempt ${attempt + 1}/${maxRetries + 1}); trying again in ${delay}ms...`);
243-
console.log(` Details: ${errorMsg.slice(0, 200)}${errorMsg.length > 200 ? '...' : ''}`);
254+
const reason = isTimeout ? 'execa timeout' : 'transient error';
255+
console.log(` ⚠ ${reason} (attempt ${attempt + 1}/${maxRetries + 1}); retrying in ${delay}ms...`);
256+
console.log(` ${getErrorDetails(result).split('\n').join('\n ')}`);
244257
}
245258

246259
// eslint-disable-next-line no-await-in-loop

0 commit comments

Comments
 (0)