File size: 3,875 Bytes
38be44d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | /**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { spawn } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
// Script to deflake tests
// Ex. npm run deflake -- --command="npm run test:e2e -- --test-name-pattern 'extension'" --runs=3
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(__dirname, '..');
const dockerIgnorePath = path.join(projectRoot, '.dockerignore');
const DOCKERIGNORE_CONTENT = `.integration-tests`.trim();
/**
* Runs a command and streams its output to the console.
* @param {string} command The command string to execute (e.g., 'npm run test:e2e -- --watch').
* @returns {Promise<number>} A Promise that resolves with the exit code of the process.
*/
function runCommand(cmd, args = []) {
if (!cmd) {
return Promise.resolve(1);
}
return new Promise((resolve, reject) => {
const child = spawn(cmd, args, {
shell: true,
stdio: 'inherit',
env: { ...process.env },
});
child.on('close', (code) => {
resolve(code ?? 1); // code can be null if the process was killed
});
child.on('error', (err) => {
// An error occurred in spawning the process (e.g., command not found).
console.error(`Failed to start command: ${err.message}`);
reject(err);
});
});
}
// -------------------------------------------------------------------
async function main() {
const argv = await yargs(hideBin(process.argv))
.option('command', {
type: 'string',
demandOption: true,
description: 'The command to run',
})
.option('runs', {
type: 'number',
default: 5,
description: 'The number of runs to perform',
}).argv;
const NUM_RUNS = argv.runs;
const COMMAND = argv.command;
const ARGS = argv._;
let failures = 0;
const backupDockerIgnorePath = dockerIgnorePath + '.bak';
let originalDockerIgnoreRenamed = false;
console.log(`--- Starting Deflake Run (${NUM_RUNS} iterations) ---`);
try {
try {
// Try to rename to back up an existing .dockerignore
await fs.rename(dockerIgnorePath, backupDockerIgnorePath);
originalDockerIgnoreRenamed = true;
} catch (err) {
// If the file doesn't exist, that's fine. Otherwise, rethrow.
if (err.code !== 'ENOENT') throw err;
}
// Create the temporary .dockerignore for this run.
await fs.writeFile(dockerIgnorePath, DOCKERIGNORE_CONTENT);
for (let i = 1; i <= NUM_RUNS; i++) {
console.log(`\n[RUN ${i}/${NUM_RUNS}]`);
try {
const exitCode = await runCommand(COMMAND, ARGS);
if (exitCode === 0) {
console.log('✅ Run PASS');
} else {
console.log(`❌ Run FAIL (Exit Code: ${exitCode})`);
failures++;
}
} catch (error) {
console.error('❌ Run FAIL (Execution Error)', error);
failures++;
}
}
} finally {
try {
// Clean up the temporary .dockerignore
await fs.unlink(dockerIgnorePath);
} catch (err) {
console.error('Failed to remove temporary .dockerignore:', err);
}
if (originalDockerIgnoreRenamed) {
try {
// Restore the original .dockerignore if it was backed up.
await fs.rename(backupDockerIgnorePath, dockerIgnorePath);
} catch (err) {
console.error('Failed to restore original .dockerignore:', err);
}
}
}
console.log('\n--- FINAL DEFLAKE SUMMARY ---');
console.log(`Total Runs: ${NUM_RUNS}`);
console.log(`Total Failures: ${failures}`);
process.exit(failures > 0 ? 1 : 0);
}
main().catch((error) => {
console.error('Error in deflake:', error);
process.exit(1);
});
|