chore: best-effort cleanup for output folders that are mounted (#12300)
Fixes #12106
This commit is contained in:
parent
4399623f9f
commit
e5c9d1e39f
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -7525,7 +7525,6 @@
|
||||||
"open": "8.4.0",
|
"open": "8.4.0",
|
||||||
"pirates": "4.0.4",
|
"pirates": "4.0.4",
|
||||||
"playwright-core": "1.20.0-next",
|
"playwright-core": "1.20.0-next",
|
||||||
"rimraf": "3.0.2",
|
|
||||||
"source-map-support": "0.4.18",
|
"source-map-support": "0.4.18",
|
||||||
"stack-utils": "2.0.5",
|
"stack-utils": "2.0.5",
|
||||||
"yazl": "2.5.1"
|
"yazl": "2.5.1"
|
||||||
|
|
@ -8341,7 +8340,6 @@
|
||||||
"open": "8.4.0",
|
"open": "8.4.0",
|
||||||
"pirates": "4.0.4",
|
"pirates": "4.0.4",
|
||||||
"playwright-core": "1.20.0-next",
|
"playwright-core": "1.20.0-next",
|
||||||
"rimraf": "3.0.2",
|
|
||||||
"source-map-support": "0.4.18",
|
"source-map-support": "0.4.18",
|
||||||
"stack-utils": "2.0.5",
|
"stack-utils": "2.0.5",
|
||||||
"yazl": "2.5.1"
|
"yazl": "2.5.1"
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import removeFolder from 'rimraf';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
|
import { promisify } from 'util';
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import { spawn, SpawnOptions, execSync } from 'child_process';
|
import { spawn, SpawnOptions, execSync } from 'child_process';
|
||||||
import { getProxyForUrl } from 'proxy-from-env';
|
import { getProxyForUrl } from 'proxy-from-env';
|
||||||
|
|
@ -29,6 +30,8 @@ import { getUbuntuVersionSync, parseOSReleaseText } from './ubuntuVersion';
|
||||||
import { NameValue } from '../protocol/channels';
|
import { NameValue } from '../protocol/channels';
|
||||||
import ProgressBar from 'progress';
|
import ProgressBar from 'progress';
|
||||||
|
|
||||||
|
const removeFolderAsync = promisify(removeFolder);
|
||||||
|
const readDirAsync = promisify(fs.readdir);
|
||||||
// `https-proxy-agent` v5 is written in TypeScript and exposes generated types.
|
// `https-proxy-agent` v5 is written in TypeScript and exposes generated types.
|
||||||
// However, as of June 2020, its types are generated with tsconfig that enables
|
// However, as of June 2020, its types are generated with tsconfig that enables
|
||||||
// `esModuleInterop` option.
|
// `esModuleInterop` option.
|
||||||
|
|
@ -415,11 +418,32 @@ export function createGuid(): string {
|
||||||
return crypto.randomBytes(16).toString('hex');
|
return crypto.randomBytes(16).toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function removeFoldersOrDie(dirs: string[]): Promise<void> {
|
||||||
|
const errors = await removeFolders(dirs);
|
||||||
|
for (const error of errors) {
|
||||||
|
if (error)
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function removeFolders(dirs: string[]): Promise<Array<Error|null|undefined>> {
|
export async function removeFolders(dirs: string[]): Promise<Array<Error|null|undefined>> {
|
||||||
return await Promise.all(dirs.map((dir: string) => {
|
return await Promise.all(dirs.map((dir: string) => {
|
||||||
return new Promise<Error|null|undefined>(fulfill => {
|
return new Promise<Error|null|undefined>(fulfill => {
|
||||||
removeFolder(dir, { maxBusyTries: 10 }, error => {
|
removeFolder(dir, { maxBusyTries: 10 }, async error => {
|
||||||
fulfill(error ?? undefined);
|
if ((error as any)?.code === 'EBUSY') {
|
||||||
|
// We failed to remove folder, might be due to the whole folder being mounted inside a container:
|
||||||
|
// https://github.com/microsoft/playwright/issues/12106
|
||||||
|
// Do a best-effort to remove all files inside of it instead.
|
||||||
|
try {
|
||||||
|
const entries = await readDirAsync(dir).catch(e => []);
|
||||||
|
await Promise.all(entries.map(entry => removeFolderAsync(path.join(dir, entry))));
|
||||||
|
fulfill(undefined);
|
||||||
|
} catch (e) {
|
||||||
|
fulfill(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fulfill(error ?? undefined);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@
|
||||||
"open": "8.4.0",
|
"open": "8.4.0",
|
||||||
"pirates": "4.0.4",
|
"pirates": "4.0.4",
|
||||||
"playwright-core": "1.20.0-next",
|
"playwright-core": "1.20.0-next",
|
||||||
"rimraf": "3.0.2",
|
|
||||||
"source-map-support": "0.4.18",
|
"source-map-support": "0.4.18",
|
||||||
"stack-utils": "2.0.5",
|
"stack-utils": "2.0.5",
|
||||||
"yazl": "2.5.1"
|
"yazl": "2.5.1"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import rimraf from 'rimraf';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
|
@ -38,9 +37,8 @@ import { Minimatch } from 'minimatch';
|
||||||
import { Config, FullConfig } from './types';
|
import { Config, FullConfig } from './types';
|
||||||
import { WebServer } from './webServer';
|
import { WebServer } from './webServer';
|
||||||
import { raceAgainstTimeout } from 'playwright-core/lib/utils/async';
|
import { raceAgainstTimeout } from 'playwright-core/lib/utils/async';
|
||||||
import { SigIntWatcher } from 'playwright-core/lib/utils/utils';
|
import { removeFoldersOrDie, SigIntWatcher } from 'playwright-core/lib/utils/utils';
|
||||||
|
|
||||||
const removeFolderAsync = promisify(rimraf);
|
|
||||||
const readDirAsync = promisify(fs.readdir);
|
const readDirAsync = promisify(fs.readdir);
|
||||||
const readFileAsync = promisify(fs.readFile);
|
const readFileAsync = promisify(fs.readFile);
|
||||||
export const kDefaultConfigFiles = ['playwright.config.ts', 'playwright.config.js', 'playwright.config.mjs'];
|
export const kDefaultConfigFiles = ['playwright.config.ts', 'playwright.config.js', 'playwright.config.mjs'];
|
||||||
|
|
@ -352,7 +350,7 @@ export class Runner {
|
||||||
|
|
||||||
// 12. Remove output directores.
|
// 12. Remove output directores.
|
||||||
try {
|
try {
|
||||||
await Promise.all(Array.from(outputDirs).map(outputDir => removeFolderAsync(outputDir)));
|
await removeFoldersOrDie(Array.from(outputDirs));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._reporter.onError?.(serializeError(e));
|
this._reporter.onError?.(serializeError(e));
|
||||||
return { status: 'failed' };
|
return { status: 'failed' };
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import rimraf from 'rimraf';
|
|
||||||
import util from 'util';
|
|
||||||
import colors from 'colors/safe';
|
import colors from 'colors/safe';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { serializeError, formatLocation } from './util';
|
import { serializeError, formatLocation } from './util';
|
||||||
|
|
@ -27,10 +25,9 @@ import { Annotations, TestError, TestInfo, TestStepInternal, WorkerInfo } from '
|
||||||
import { ProjectImpl } from './project';
|
import { ProjectImpl } from './project';
|
||||||
import { FixtureRunner } from './fixtures';
|
import { FixtureRunner } from './fixtures';
|
||||||
import { raceAgainstTimeout } from 'playwright-core/lib/utils/async';
|
import { raceAgainstTimeout } from 'playwright-core/lib/utils/async';
|
||||||
|
import { removeFolders } from 'playwright-core/lib/utils/utils';
|
||||||
import { TestInfoImpl } from './testInfo';
|
import { TestInfoImpl } from './testInfo';
|
||||||
|
|
||||||
const removeFolderAsync = util.promisify(rimraf);
|
|
||||||
|
|
||||||
export class WorkerRunner extends EventEmitter {
|
export class WorkerRunner extends EventEmitter {
|
||||||
private _params: WorkerInitParams;
|
private _params: WorkerInitParams;
|
||||||
private _loader!: Loader;
|
private _loader!: Loader;
|
||||||
|
|
@ -314,7 +311,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
const preserveOutput = this._loader.fullConfig().preserveOutput === 'always' ||
|
const preserveOutput = this._loader.fullConfig().preserveOutput === 'always' ||
|
||||||
(this._loader.fullConfig().preserveOutput === 'failures-only' && isFailure);
|
(this._loader.fullConfig().preserveOutput === 'failures-only' && isFailure);
|
||||||
if (!preserveOutput)
|
if (!preserveOutput)
|
||||||
await removeFolderAsync(testInfo.outputDir).catch(e => {});
|
await removeFolders([testInfo.outputDir]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runTestWithBeforeHooks(test: TestCase, testInfo: TestInfoImpl) {
|
private async _runTestWithBeforeHooks(test: TestCase, testInfo: TestInfoImpl) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue