From abd2ba53e38bc6d46843878041ed03a0ac7574c7 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 17 Nov 2025 09:23:50 +0000 Subject: [PATCH 1/3] test: delete e2e directories after tests finish Deletes the e2e directories after tests complete to ensure a clean state for subsequent test runs and to free up disk space. --- tests/legacy-cli/e2e/utils/assets.ts | 12 +++++++----- tests/legacy-cli/e2e/utils/project.ts | 5 +++++ tests/legacy-cli/e2e_runner.ts | 15 ++++++++++++--- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/tests/legacy-cli/e2e/utils/assets.ts b/tests/legacy-cli/e2e/utils/assets.ts index d421086c1b9e..153fdaccc6de 100644 --- a/tests/legacy-cli/e2e/utils/assets.ts +++ b/tests/legacy-cli/e2e/utils/assets.ts @@ -5,7 +5,7 @@ import { getGlobalVariable } from './env'; import { resolve } from 'node:path'; import { copyFile } from './fs'; import { installWorkspacePackages, setRegistry } from './packages'; -import { useBuiltPackagesVersions } from './project'; +import { getTestProjectDir, useBuiltPackagesVersions } from './project'; export function assetDir(assetName: string) { return join(__dirname, '../e2e/assets', assetName); @@ -21,7 +21,7 @@ export function copyProjectAsset(assetName: string, to?: string) { export function copyAssets(assetName: string, to?: string) { const seed = +Date.now(); - const tempRoot = join(getGlobalVariable('projects-root'), 'assets', assetName + '-' + seed); + const tempRoot = join(getTestAssetsDir(), assetName + '-' + seed); const root = assetDir(assetName); return Promise.resolve() @@ -30,9 +30,7 @@ export function copyAssets(assetName: string, to?: string) { return allFiles.reduce((promise, filePath) => { const toPath = - to !== undefined - ? resolve(getGlobalVariable('projects-root'), 'test-project', to, filePath) - : join(tempRoot, filePath); + to !== undefined ? resolve(getTestProjectDir(), to, filePath) : join(tempRoot, filePath); return promise .then(() => copyFile(join(root, filePath), toPath)) @@ -65,3 +63,7 @@ export async function createProjectFromAsset( return () => setRegistry(true /** useTestRegistry */); } + +export function getTestAssetsDir(): string { + return join(getGlobalVariable('projects-root'), 'assets'); +} diff --git a/tests/legacy-cli/e2e/utils/project.ts b/tests/legacy-cli/e2e/utils/project.ts index c84b7fce8e1d..fe9cfd8a0e04 100644 --- a/tests/legacy-cli/e2e/utils/project.ts +++ b/tests/legacy-cli/e2e/utils/project.ts @@ -7,6 +7,7 @@ import { gitCommit } from './git'; import { findFreePort } from './network'; import { installWorkspacePackages, PkgInfo } from './packages'; import { execAndWaitForOutputToMatch, git, ng } from './process'; +import { join } from 'node:path'; export function updateJsonFile(filePath: string, fn: (json: any) => any | void) { return readFile(filePath).then((tsConfigJson) => { @@ -270,3 +271,7 @@ export function updateServerFileForEsbuild(filepath: string): Promise { `, ); } + +export function getTestProjectDir(): string { + return join(getGlobalVariable('projects-root'), 'test-project'); +} diff --git a/tests/legacy-cli/e2e_runner.ts b/tests/legacy-cli/e2e_runner.ts index 5d7031f20489..86a036c5518a 100644 --- a/tests/legacy-cli/e2e_runner.ts +++ b/tests/legacy-cli/e2e_runner.ts @@ -4,6 +4,7 @@ import colors from 'ansi-colors'; import glob from 'fast-glob'; import * as path from 'node:path'; import * as fs from 'node:fs'; +import { rm } from 'node:fs/promises'; import { getGlobalVariable, setGlobalVariable } from './e2e/utils/env'; import { gitClean } from './e2e/utils/git'; import { createNpmRegistry } from './e2e/utils/registry'; @@ -13,7 +14,7 @@ import { findFreePort } from './e2e/utils/network'; import { extractFile } from './e2e/utils/tar'; import { realpathSync } from 'node:fs'; import { PkgInfo } from './e2e/utils/packages'; -import { rm } from 'node:fs/promises'; +import { getTestProjectDir } from './e2e/utils/project'; Error.stackTraceLimit = Infinity; @@ -271,6 +272,14 @@ Promise.all([findFreePort(), findFreePort(), findPackageTars()]) } finally { registryProcess.kill(); secureRegistryProcess.kill(); + + await rm(getGlobalVariable('projects-root'), { + recursive: true, + force: true, + maxRetries: 3, + }).catch(() => { + // If this fails it is not fatal. + }); } }) .catch((err) => { @@ -334,7 +343,7 @@ function runInitializer(absoluteName: string): Promise { * Run a file from the main 'test-project' directory in a subprocess via launchTestProcess(). */ async function runTest(absoluteName: string): Promise { - process.chdir(join(getGlobalVariable('projects-root'), 'test-project')); + process.chdir(getTestProjectDir()); await launchTestProcess(absoluteName); await cleanTestProject(); @@ -343,7 +352,7 @@ async function runTest(absoluteName: string): Promise { async function cleanTestProject() { await gitClean(); - const testProject = join(getGlobalVariable('projects-root'), 'test-project'); + const testProject = getTestProjectDir(); // Note: Dist directory is not cleared between tests, as `git clean` // doesn't delete it. From 07884ebf13e64bcb2c570a304502796500f0e1aa Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 17 Nov 2025 09:25:27 +0000 Subject: [PATCH 2/3] Revert "ci: bump specs for e2e-package-managers" This reverts commit 7217478809e80c57e8705eaeea38d2fe32a9f9bd. --- .github/workflows/ci.yml | 4 +--- .github/workflows/pr.yml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60e3cd99ac57..e257c8735b65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,9 +160,7 @@ jobs: strategy: fail-fast: false matrix: - # These tests can generate a significant amount of temp files, especially when - # flaky targets are retried. The larger machine type comes with 2x more SSD space. - os: [ubuntu-latest-4core] + os: [ubuntu-latest] node: [22] subset: [yarn, pnpm] shard: [0, 1, 2] diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index aab59a7bc029..6d4ab92e7243 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -178,9 +178,7 @@ jobs: strategy: fail-fast: false matrix: - # These tests can generate a significant amount of temp files, especially when - # flaky targets are retried. The larger machine type comes with 2x more SSD space. - os: [ubuntu-latest-4core] + os: [ubuntu-latest] node: [22] subset: [yarn, pnpm] shard: [0, 1, 2] From 23a378350f24b7f4bdede17f697086c0c0583462 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:56:27 +0000 Subject: [PATCH 3/3] build: clean up verdaccio storage and share storage Ensures that the Verdaccio registry storage is properly cleaned up at the end of E2E tests by placing it within the main temporary directory which is now explicitly deleted on process exit or SIGINT. This change also centralizes the Verdaccio storage location, allowing both HTTP and HTTPS Verdaccio instances to share the same storage, simplifying test setup and cleanup. Removes redundant temporary directory creation logic for npm sandbox and temporary project roots, as these are now managed by a single `tmp-root`. --- .../e2e/setup/001-create-tmp-dir.ts | 19 --- ...{002-npm-sandbox.ts => 001-npm-sandbox.ts} | 0 tests/legacy-cli/e2e/utils/registry.ts | 21 +-- tests/legacy-cli/e2e_runner.ts | 126 ++++++++++-------- 4 files changed, 80 insertions(+), 86 deletions(-) delete mode 100644 tests/legacy-cli/e2e/setup/001-create-tmp-dir.ts rename tests/legacy-cli/e2e/setup/{002-npm-sandbox.ts => 001-npm-sandbox.ts} (100%) diff --git a/tests/legacy-cli/e2e/setup/001-create-tmp-dir.ts b/tests/legacy-cli/e2e/setup/001-create-tmp-dir.ts deleted file mode 100644 index 4914921eca18..000000000000 --- a/tests/legacy-cli/e2e/setup/001-create-tmp-dir.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { dirname } from 'node:path'; -import { getGlobalVariable, setGlobalVariable } from '../utils/env'; -import { mktempd } from '../utils/utils'; - -export default async function () { - const argv = getGlobalVariable('argv'); - - // Get to a temporary directory. - let tempRoot: string; - if (argv.reuse) { - tempRoot = dirname(argv.reuse); - } else if (argv.tmpdir) { - tempRoot = argv.tmpdir; - } else { - tempRoot = await mktempd('angular-cli-e2e-', process.env.E2E_TEMP); - } - console.log(` Using "${tempRoot}" as temporary directory for a new project.`); - setGlobalVariable('tmp-root', tempRoot); -} diff --git a/tests/legacy-cli/e2e/setup/002-npm-sandbox.ts b/tests/legacy-cli/e2e/setup/001-npm-sandbox.ts similarity index 100% rename from tests/legacy-cli/e2e/setup/002-npm-sandbox.ts rename to tests/legacy-cli/e2e/setup/001-npm-sandbox.ts diff --git a/tests/legacy-cli/e2e/utils/registry.ts b/tests/legacy-cli/e2e/utils/registry.ts index 1bd3084d4f48..b4c1b08afcbc 100644 --- a/tests/legacy-cli/e2e/utils/registry.ts +++ b/tests/legacy-cli/e2e/utils/registry.ts @@ -1,9 +1,10 @@ import { ChildProcess, fork } from 'node:child_process'; import { on } from 'node:events'; +import { mkdir } from 'node:fs/promises'; import { join } from 'node:path'; import { getGlobalVariable } from './env'; import { writeFile, readFile } from './fs'; -import { mktempd } from './utils'; +import { existsSync } from 'node:fs'; export async function createNpmRegistry( port: number, @@ -11,14 +12,18 @@ export async function createNpmRegistry( withAuthentication = false, ): Promise { // Setup local package registry - const registryPath = await mktempd('angular-cli-e2e-registry-'); + const registryPath = join(getGlobalVariable('tmp-root'), 'registry'); + if (!existsSync(registryPath)) { + await mkdir(registryPath); + } + + const configFileName = withAuthentication ? 'verdaccio_auth.yaml' : 'verdaccio.yaml'; + let configContent = await readFile(join(__dirname, '../', configFileName)); + configContent = configContent + .replace(/\$\{HTTP_PORT\}/g, String(port)) + .replace(/\$\{HTTPS_PORT\}/g, String(httpsPort)); + const configPath = join(registryPath, configFileName); - let configContent = await readFile( - join(__dirname, '../', withAuthentication ? 'verdaccio_auth.yaml' : 'verdaccio.yaml'), - ); - configContent = configContent.replace(/\$\{HTTP_PORT\}/g, String(port)); - configContent = configContent.replace(/\$\{HTTPS_PORT\}/g, String(httpsPort)); - const configPath = join(registryPath, 'verdaccio.yaml'); await writeFile(configPath, configContent); const verdaccioServer = fork(require.resolve('verdaccio/bin/verdaccio'), ['-c', configPath]); diff --git a/tests/legacy-cli/e2e_runner.ts b/tests/legacy-cli/e2e_runner.ts index 86a036c5518a..0db1dfb01025 100644 --- a/tests/legacy-cli/e2e_runner.ts +++ b/tests/legacy-cli/e2e_runner.ts @@ -15,6 +15,7 @@ import { extractFile } from './e2e/utils/tar'; import { realpathSync } from 'node:fs'; import { PkgInfo } from './e2e/utils/packages'; import { getTestProjectDir } from './e2e/utils/project'; +import { mktempd } from './e2e/utils/utils'; Error.stackTraceLimit = Infinity; @@ -31,13 +32,9 @@ Error.stackTraceLimit = Infinity; * --ng-snapshots Install angular snapshot builds in the test project. * --glob Run tests matching this glob pattern (relative to tests/e2e/). * --ignore Ignore tests matching this glob pattern. - * --reuse=/path Use a path instead of create a new project. That project should have been - * created, and npm installed. Ideally you want a project created by a previous - * run of e2e. * --nb-shards Total number of shards that this is part of. Default is 2 if --shard is * passed in. * --shard Index of this processes' shard. - * --tmpdir=path Override temporary directory to use for new projects. * --package-manager Package manager to use. * --package=path An npm package to be published before running tests * @@ -58,8 +55,6 @@ const parsed = parseArgs({ 'nosilent': { type: 'boolean' }, 'package': { type: 'string', multiple: true, default: ['./dist/_*.tgz'] }, 'package-manager': { type: 'string', default: 'npm' }, - 'reuse': { type: 'string' }, - 'tmpdir': { type: 'string' }, 'verbose': { type: 'boolean' }, 'nb-shards': { type: 'string' }, @@ -227,65 +222,68 @@ process.env.CHROME_BIN = path.resolve(process.env.CHROME_BIN!); process.env.CHROME_PATH = path.resolve(process.env.CHROME_PATH!); process.env.CHROMEDRIVER_BIN = path.resolve(process.env.CHROMEDRIVER_BIN!); -Promise.all([findFreePort(), findFreePort(), findPackageTars()]) - .then(async ([httpPort, httpsPort, packageTars]) => { - setGlobalVariable('package-registry', 'http://localhost:' + httpPort); - setGlobalVariable('package-secure-registry', 'http://localhost:' + httpsPort); - setGlobalVariable('package-tars', packageTars); - - // NPM registries for the lifetime of the test execution - const registryProcess = await createNpmRegistry(httpPort, httpPort); - const secureRegistryProcess = await createNpmRegistry(httpPort, httpsPort, true); - - try { - await runSteps(runSetup, allSetups, 'setup'); - await runSteps(runInitializer, allInitializers, 'initializer'); - await runSteps(runTest, testsToRun, 'test'); - - if (shardId !== null) { - console.log(colors.green(`Done shard ${shardId} of ${nbShards}.`)); - } else { - console.log(colors.green('Done.')); - } +(async () => { + const tempRoot = await mktempd('angular-cli-e2e-', process.env.E2E_TEMP); + setGlobalVariable('tmp-root', tempRoot); + + process.on('SIGINT', deleteTemporaryRoot); + process.on('exit', deleteTemporaryRoot); + + const [httpPort, httpsPort, packageTars] = await Promise.all([ + findFreePort(), + findFreePort(), + findPackageTars(), + ]); + setGlobalVariable('package-registry', 'http://localhost:' + httpPort); + setGlobalVariable('package-secure-registry', 'http://localhost:' + httpsPort); + setGlobalVariable('package-tars', packageTars); + + // NPM registries for the lifetime of the test execution + const registryProcess = await createNpmRegistry(httpPort, httpPort); + const secureRegistryProcess = await createNpmRegistry(httpPort, httpsPort, true); + + try { + console.log(` Using "${tempRoot}" as temporary directory for a new project.`); + + await runSteps(runSetup, allSetups, 'setup'); + await runSteps(runInitializer, allInitializers, 'initializer'); + await runSteps(runTest, testsToRun, 'test'); + + if (shardId !== null) { + console.log(colors.green(`Done shard ${shardId} of ${nbShards}.`)); + } else { + console.log(colors.green('Done.')); + } - process.exitCode = 0; - } catch (err) { - if (err instanceof Error) { - console.log('\n'); - console.error(colors.red(err.message)); - if (err.stack) { - console.error(colors.red(err.stack)); - } - } else { - console.error(colors.red(String(err))); + process.exitCode = 0; + } catch (err) { + if (err instanceof Error) { + console.log('\n'); + console.error(colors.red(err.message)); + if (err.stack) { + console.error(colors.red(err.stack)); } + } else { + console.error(colors.red(String(err))); + } - if (argv.debug) { - console.log(`Current Directory: ${process.cwd()}`); - console.log('Will loop forever while you debug... CTRL-C to quit.'); - - // Wait forever until user explicitly cancels. - await new Promise(() => {}); - } + if (argv.debug) { + console.log(`Current Directory: ${process.cwd()}`); + console.log('Will loop forever while you debug... CTRL-C to quit.'); - process.exitCode = 1; - } finally { - registryProcess.kill(); - secureRegistryProcess.kill(); - - await rm(getGlobalVariable('projects-root'), { - recursive: true, - force: true, - maxRetries: 3, - }).catch(() => { - // If this fails it is not fatal. - }); + // Wait forever until user explicitly cancels. + await new Promise(() => {}); } - }) - .catch((err) => { - console.error(colors.red(`Unkown Error: ${err}`)); + process.exitCode = 1; - }); + } finally { + registryProcess.kill(); + secureRegistryProcess.kill(); + } +})().catch((err) => { + console.error(colors.red(`Unkown Error: ${err}`)); + process.exitCode = 1; +}); async function runSteps( run: (name: string) => Promise | void, @@ -415,3 +413,13 @@ async function findPackageTars(): Promise<{ [pkg: string]: PkgInfo }> { {} as { [pkg: string]: PkgInfo }, ); } + +function deleteTemporaryRoot(): void { + try { + fs.rmSync(getGlobalVariable('tmp-root'), { + recursive: true, + force: true, + maxRetries: 3, + }); + } catch {} +}