mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-19 17:58:48 +00:00
Run test-release in a temporary folder
This commit is contained in:
@@ -4,9 +4,14 @@
|
||||
import asar from '@electron/asar';
|
||||
import assert from 'node:assert';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { mkdtemp, cp } from 'node:fs/promises';
|
||||
import { constants as fsConstants } from 'node:fs';
|
||||
import { _electron as electron } from 'playwright';
|
||||
|
||||
import { productName, name } from '../util/packageJson.node.js';
|
||||
import { gracefulRmRecursive } from '../util/gracefulFs.node.js';
|
||||
import { consoleLogger } from '../util/consoleLogger.std.js';
|
||||
|
||||
const ENVIRONMENT = 'production';
|
||||
const RELEASE_DIR = join(__dirname, '..', '..', 'release');
|
||||
@@ -56,25 +61,38 @@ for (const fileName of files) {
|
||||
|
||||
// A simple test to verify a visible window is opened with a title
|
||||
const main = async () => {
|
||||
const executablePath = join(RELEASE_DIR, exe);
|
||||
console.log('Starting path', executablePath);
|
||||
const app = await electron.launch({
|
||||
executablePath,
|
||||
locale: 'en',
|
||||
});
|
||||
const tmpFolder = await mkdtemp(join(tmpdir(), 'test-release'));
|
||||
const tmpApp = join(tmpFolder, 'Signal');
|
||||
|
||||
console.log('Waiting for a first window');
|
||||
const window = await app.firstWindow();
|
||||
try {
|
||||
await cp(RELEASE_DIR, tmpApp, {
|
||||
recursive: true,
|
||||
mode: fsConstants.COPYFILE_FICLONE,
|
||||
});
|
||||
|
||||
console.log('Waiting for app to fully load');
|
||||
await window.waitForSelector(
|
||||
'.App, .app-loading-screen:has-text("Optimizing")'
|
||||
);
|
||||
const executablePath = join(tmpApp, exe);
|
||||
console.log('Starting path', executablePath);
|
||||
const app = await electron.launch({
|
||||
executablePath,
|
||||
locale: 'en',
|
||||
cwd: tmpApp,
|
||||
});
|
||||
|
||||
console.log('Checking window title');
|
||||
assert.strictEqual(await window.title(), productName);
|
||||
console.log('Waiting for a first window');
|
||||
const window = await app.firstWindow();
|
||||
|
||||
await app.close();
|
||||
console.log('Waiting for app to fully load');
|
||||
await window.waitForSelector(
|
||||
'.App, .app-loading-screen:has-text("Optimizing")'
|
||||
);
|
||||
|
||||
console.log('Checking window title');
|
||||
assert.strictEqual(await window.title(), productName);
|
||||
|
||||
await app.close();
|
||||
} finally {
|
||||
await gracefulRmRecursive(consoleLogger, tmpFolder);
|
||||
}
|
||||
};
|
||||
|
||||
main().catch(error => {
|
||||
|
||||
@@ -53,12 +53,11 @@ import {
|
||||
prepareDownload as prepareDifferentialDownload,
|
||||
} from './differential.node.js';
|
||||
import { getGotOptions } from './got.node.js';
|
||||
import { checkIntegrity, isTimeToUpdate } from './util.node.js';
|
||||
import {
|
||||
checkIntegrity,
|
||||
gracefulRename,
|
||||
gracefulRmRecursive,
|
||||
isTimeToUpdate,
|
||||
} from './util.node.js';
|
||||
} from '../util/gracefulFs.node.js';
|
||||
|
||||
import type { LoggerType } from '../types/Logging.std.js';
|
||||
import type { PrepareDownloadResultType as DifferentialDownloadDataType } from './differential.node.js';
|
||||
|
||||
@@ -3,14 +3,11 @@
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
import { createReadStream } from 'node:fs';
|
||||
import { rename, rm } from 'node:fs/promises';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
|
||||
import type { LoggerType } from '../types/Logging.std.js';
|
||||
import * as Errors from '../types/errors.std.js';
|
||||
import { SECOND, MINUTE, HOUR } from '../util/durations/index.std.js';
|
||||
import { sleep } from '../util/sleep.std.js';
|
||||
import { isOlderThan } from '../util/timestamp.std.js';
|
||||
import { MINUTE, HOUR } from '../util/durations/index.std.js';
|
||||
|
||||
export type CheckIntegrityResultType = Readonly<
|
||||
| {
|
||||
@@ -48,97 +45,6 @@ export async function checkIntegrity(
|
||||
}
|
||||
}
|
||||
|
||||
async function doGracefulFSOperation<Args extends ReadonlyArray<unknown>>({
|
||||
name,
|
||||
operation,
|
||||
args,
|
||||
logger,
|
||||
startedAt,
|
||||
retryCount,
|
||||
retryAfter = 5 * SECOND,
|
||||
timeout = 5 * MINUTE,
|
||||
}: {
|
||||
name: string;
|
||||
operation: (...args: Args) => Promise<void>;
|
||||
args: Args;
|
||||
logger: LoggerType;
|
||||
startedAt: number;
|
||||
retryCount: number;
|
||||
retryAfter?: number;
|
||||
timeout?: number;
|
||||
}): Promise<void> {
|
||||
const logId = `gracefulFS(${name})`;
|
||||
try {
|
||||
await operation(...args);
|
||||
|
||||
if (retryCount !== 0) {
|
||||
logger.info(
|
||||
`${logId}: succeeded after ${retryCount} retries, ${args.join(', ')}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 'EACCES' && error.code !== 'EPERM') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (isOlderThan(startedAt, timeout)) {
|
||||
logger.warn(`${logId}: timed out, ${args.join(', ')}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`${logId}: got ${error.code} when running on ${args.join(', ')}; ` +
|
||||
`retrying in one second. (retryCount=${retryCount})`
|
||||
);
|
||||
|
||||
await sleep(retryAfter);
|
||||
|
||||
return doGracefulFSOperation({
|
||||
name,
|
||||
operation,
|
||||
args,
|
||||
logger,
|
||||
startedAt,
|
||||
retryCount: retryCount + 1,
|
||||
retryAfter,
|
||||
timeout,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function gracefulRename(
|
||||
logger: LoggerType,
|
||||
fromPath: string,
|
||||
toPath: string
|
||||
): Promise<void> {
|
||||
return doGracefulFSOperation({
|
||||
name: 'rename',
|
||||
operation: rename,
|
||||
args: [fromPath, toPath],
|
||||
logger,
|
||||
startedAt: Date.now(),
|
||||
retryCount: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function rmRecursive(path: string): Promise<void> {
|
||||
return rm(path, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
export async function gracefulRmRecursive(
|
||||
logger: LoggerType,
|
||||
path: string
|
||||
): Promise<void> {
|
||||
return doGracefulFSOperation({
|
||||
name: 'rmRecursive',
|
||||
operation: rmRecursive,
|
||||
args: [path],
|
||||
logger,
|
||||
startedAt: Date.now(),
|
||||
retryCount: 0,
|
||||
});
|
||||
}
|
||||
|
||||
const MAX_UPDATE_DELAY = 6 * HOUR;
|
||||
|
||||
export function isTimeToUpdate({
|
||||
|
||||
100
ts/util/gracefulFs.node.ts
Normal file
100
ts/util/gracefulFs.node.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { rename, rm } from 'node:fs/promises';
|
||||
|
||||
import type { LoggerType } from '../types/Logging.std.js';
|
||||
import { SECOND, MINUTE } from './durations/index.std.js';
|
||||
import { isOlderThan } from './timestamp.std.js';
|
||||
import { sleep } from './sleep.std.js';
|
||||
|
||||
async function doGracefulFSOperation<Args extends ReadonlyArray<unknown>>({
|
||||
name,
|
||||
operation,
|
||||
args,
|
||||
logger,
|
||||
startedAt,
|
||||
retryCount,
|
||||
retryAfter = 5 * SECOND,
|
||||
timeout = 5 * MINUTE,
|
||||
}: {
|
||||
name: string;
|
||||
operation: (...args: Args) => Promise<void>;
|
||||
args: Args;
|
||||
logger: LoggerType;
|
||||
startedAt: number;
|
||||
retryCount: number;
|
||||
retryAfter?: number;
|
||||
timeout?: number;
|
||||
}): Promise<void> {
|
||||
const logId = `gracefulFS(${name})`;
|
||||
try {
|
||||
await operation(...args);
|
||||
|
||||
if (retryCount !== 0) {
|
||||
logger.info(
|
||||
`${logId}: succeeded after ${retryCount} retries, ${args.join(', ')}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 'EACCES' && error.code !== 'EPERM') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (isOlderThan(startedAt, timeout)) {
|
||||
logger.warn(`${logId}: timed out, ${args.join(', ')}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`${logId}: got ${error.code} when running on ${args.join(', ')}; ` +
|
||||
`retrying in one second. (retryCount=${retryCount})`
|
||||
);
|
||||
|
||||
await sleep(retryAfter);
|
||||
|
||||
return doGracefulFSOperation({
|
||||
name,
|
||||
operation,
|
||||
args,
|
||||
logger,
|
||||
startedAt,
|
||||
retryCount: retryCount + 1,
|
||||
retryAfter,
|
||||
timeout,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function gracefulRename(
|
||||
logger: LoggerType,
|
||||
fromPath: string,
|
||||
toPath: string
|
||||
): Promise<void> {
|
||||
return doGracefulFSOperation({
|
||||
name: 'rename',
|
||||
operation: rename,
|
||||
args: [fromPath, toPath],
|
||||
logger,
|
||||
startedAt: Date.now(),
|
||||
retryCount: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function rmRecursive(path: string): Promise<void> {
|
||||
return rm(path, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
export async function gracefulRmRecursive(
|
||||
logger: LoggerType,
|
||||
path: string
|
||||
): Promise<void> {
|
||||
return doGracefulFSOperation({
|
||||
name: 'rmRecursive',
|
||||
operation: rmRecursive,
|
||||
args: [path],
|
||||
logger,
|
||||
startedAt: Date.now(),
|
||||
retryCount: 0,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user