Skip to content

Commit f21bbb5

Browse files
committed
test: run e2e tests on provided packages
The NPM packages to test must be specified via --package instead of invoking the build from within the e2e tests
1 parent 812c0e6 commit f21bbb5

File tree

10 files changed

+173
-42
lines changed

10 files changed

+173
-42
lines changed

.circleci/dynamic_config.yml

+11-5
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,23 @@ jobs:
237237
- run:
238238
name: Execute CLI E2E Tests with NPM
239239
command: |
240-
node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<</ parameters.snapshots >> --tmpdir=/mnt/ramdisk/e2e
240+
node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<</ parameters.snapshots >> --tmpdir=/mnt/ramdisk/e2e --package ./dist/_*.tgz
241241
- when:
242242
condition:
243243
equal: ['esbuild', << parameters.subset >>]
244244
steps:
245245
- run:
246246
name: Execute CLI E2E Tests Subset with Esbuild
247247
command: |
248-
node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<</ parameters.snapshots >> --esbuild --tmpdir=/mnt/ramdisk/e2e --glob="{tests/basic/**,tests/build/prod-build.ts,tests/build/relative-sourcemap.ts,tests/build/styles/scss.ts,tests/build/styles/include-paths.ts,tests/commands/add/add-pwa.ts}" --ignore="tests/basic/{environment,rebuild,serve,scripts-array}.ts"
248+
node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<</ parameters.snapshots >> --esbuild --tmpdir=/mnt/ramdisk/e2e --package ./dist/_*.tgz --glob="{tests/basic/**,tests/build/prod-build.ts,tests/build/relative-sourcemap.ts,tests/build/styles/scss.ts,tests/build/styles/include-paths.ts,tests/commands/add/add-pwa.ts}" --ignore="tests/basic/{environment,rebuild,serve,scripts-array}.ts"
249249
- when:
250250
condition:
251251
equal: ['yarn', << parameters.subset >>]
252252
steps:
253253
- run:
254254
name: Execute CLI E2E Tests Subset with Yarn
255255
command: |
256-
node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<</ parameters.snapshots >> --yarn --tmpdir=/mnt/ramdisk/e2e --glob="{tests/basic/**,tests/update/**,tests/commands/add/**}"
256+
node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<</ parameters.snapshots >> --yarn --tmpdir=/mnt/ramdisk/e2e --package ./dist/_*.tgz --glob="{tests/basic/**,tests/update/**,tests/commands/add/**}"
257257
- fail_fast
258258

259259
test-browsers:
@@ -275,7 +275,7 @@ jobs:
275275
# Waits for the Saucelabs tunnel to be ready. This ensures that we don't run tests
276276
# too early without Saucelabs not being ready.
277277
- run: ./scripts/saucelabs/wait-for-tunnel.sh
278-
- run: node ./tests/legacy-cli/run_e2e ./tests/legacy-cli/e2e/tests/misc/browsers.ts
278+
- run: node ./tests/legacy-cli/run_e2e --package ./dist/_*.tgz --glob="tests/misc/browsers.ts"
279279
- run: ./scripts/saucelabs/stop-tunnel.sh
280280
- fail_fast
281281

@@ -284,6 +284,10 @@ jobs:
284284
steps:
285285
- custom_attach_workspace
286286
- run: yarn build
287+
- persist_to_workspace:
288+
root: *workspace_location
289+
paths:
290+
- dist/_*.tgz
287291

288292
build-bazel-e2e:
289293
executor: action-executor
@@ -350,12 +354,14 @@ jobs:
350354
# Path where Arsenal Image Mounter files are downloaded.
351355
# Must match path in .circleci/win-ram-disk.ps1
352356
- ./aim
357+
# Build the npm packages for the e2e tests
358+
- run: yarn build
353359
# Run partial e2e suite on PRs only. Release branches will run the full e2e suite.
354360
- run:
355361
name: Execute E2E Tests
356362
command: |
357363
mkdir X:/ramdisk/e2e-main
358-
node tests\legacy-cli\run_e2e.js --nb-shards=$env:CIRCLE_NODE_TOTAL --shard=$env:CIRCLE_NODE_INDEX --tmpdir=X:/ramdisk/e2e-main
364+
node tests\legacy-cli\run_e2e.js --nb-shards=$env:CIRCLE_NODE_TOTAL --shard=$env:CIRCLE_NODE_INDEX --tmpdir=X:/ramdisk/e2e-main --package ./dist/_*.tgz
359365
- fail_fast
360366

361367
workflows:

lib/BUILD.bazel

-12
This file was deleted.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
"@types/progress": "^2.0.3",
120120
"@types/resolve": "^1.17.1",
121121
"@types/semver": "^7.3.12",
122+
"@types/tar": "^6.1.2",
122123
"@types/text-table": "^0.2.1",
123124
"@types/uuid": "^8.0.0",
124125
"@types/yargs": "^17.0.8",

tarify.mjs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import fs from 'fs';
10+
import { Parse } from 'tar';
11+
12+
['uncaughtExceptionMonitor', 'unhandledRejection'].forEach(type => {
13+
process.on(type, (...args) => console.error(`${type}: `, ...args));
14+
});
15+
16+
// Inspired by https://github.com/npm/node-tar/issues/181#issuecomment-402088964
17+
async function extractTarFile(tarball, filePath) {
18+
console.log('DETAR: ', filePath, tarball);
19+
20+
return new Promise((resolve, reject) => {
21+
let chunks = null;
22+
23+
fs.createReadStream(tarball).pipe(
24+
new Parse({
25+
strict: true,
26+
end: true,
27+
filter: (p) => p === filePath,
28+
onentry: (entry) => {
29+
chunks = [];
30+
31+
entry.on('data', (chunk) => chunks.push(chunk));
32+
entry.on('error', reject);
33+
entry.on('finish', () => resolve(Buffer.concat(chunks)));
34+
},
35+
}),
36+
);
37+
});
38+
}
39+
40+
try {
41+
const r = await extractTarFile(
42+
'./dist/bin/packages/angular_devkit/core/npm_package.tgz',
43+
'package/package.json'
44+
);
45+
46+
console.log('DONE: ', JSON.parse(r.toString('utf8')).name);
47+
} catch (e) {
48+
console.log("FAIL", e);
49+
}
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
1+
import { join } from 'path';
12
import { getGlobalVariable } from '../utils/env';
23
import { globalNpm, extractNpmEnv } from '../utils/process';
34
import { isPrereleaseCli } from '../utils/project';
45

56
export default async function () {
67
const testRegistry: string = getGlobalVariable('package-registry');
7-
await globalNpm(
8-
[
9-
'run',
10-
'admin',
11-
'--',
12-
'publish',
13-
'--no-versionCheck',
14-
'--no-branchCheck',
15-
`--registry=${testRegistry}`,
16-
'--tag',
17-
isPrereleaseCli() ? 'next' : 'latest',
18-
],
19-
{
20-
...extractNpmEnv(),
21-
// Also set an auth token value for the local test registry which is required by npm 7+
22-
// even though it is never actually used.
23-
'NPM_CONFIG__AUTH': 'e2e-testing',
24-
},
8+
const packageTars: string[] = Object.values(getGlobalVariable('package-tars'));
9+
10+
// Publish packages specified with --package
11+
await Promise.all(
12+
packageTars.map((p: string) =>
13+
globalNpm(
14+
[
15+
'publish',
16+
`--registry=${testRegistry}`,
17+
'--tag',
18+
isPrereleaseCli() ? 'next' : 'latest',
19+
join(process.cwd(), p),
20+
],
21+
{
22+
...extractNpmEnv(),
23+
// Also set an auth token value for the local test registry which is required by npm 7+
24+
// even though it is never actually used.
25+
'NPM_CONFIG__AUTH': 'e2e-testing',
26+
},
27+
),
28+
),
2529
);
2630
}

tests/legacy-cli/e2e/utils/BUILD.bazel

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ ts_library(
66
srcs = glob(["**/*.ts"]),
77
visibility = ["//visibility:public"],
88
deps = [
9-
"//lib",
109
"//tests/legacy-cli/e2e/ng-snapshot",
1110
"@npm//@types/glob",
1211
"@npm//@types/node-fetch",
1312
"@npm//@types/semver",
13+
"@npm//@types/tar",
1414
"@npm//@types/yargs-parser",
1515
"@npm//ansi-colors",
1616
"@npm//glob",
@@ -19,6 +19,7 @@ ts_library(
1919
"@npm//puppeteer",
2020
"@npm//rxjs",
2121
"@npm//semver",
22+
"@npm//tar",
2223
"@npm//tree-kill",
2324
"@npm//verdaccio",
2425
"@npm//verdaccio-auth-memory",

tests/legacy-cli/e2e/utils/project.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as fs from 'fs';
22
import * as path from 'path';
33
import { prerelease, SemVer } from 'semver';
44
import yargsParser from 'yargs-parser';
5-
import { packages } from '../../../../lib/packages';
65
import { getGlobalVariable } from './env';
76
import { prependToFile, readFile, replaceInFile, writeFile } from './fs';
87
import { gitCommit } from './git';
@@ -95,15 +94,17 @@ export async function prepareProjectForE2e(name: string) {
9594
}
9695

9796
export function useBuiltPackages(): Promise<void> {
97+
const packages = getGlobalVariable('package-tars');
98+
9899
return updateJsonFile('package.json', (json) => {
99100
json['dependencies'] ??= {};
100101
json['devDependencies'] ??= {};
101102

102103
for (const packageName of Object.keys(packages)) {
103104
if (packageName in json['dependencies']) {
104-
json['dependencies'][packageName] = packages[packageName].tar;
105+
json['dependencies'][packageName] = packages[packageName];
105106
} else if (packageName in json['devDependencies']) {
106-
json['devDependencies'][packageName] = packages[packageName].tar;
107+
json['devDependencies'][packageName] = packages[packageName];
107108
}
108109
}
109110
});
@@ -220,7 +221,7 @@ export async function useCIChrome(projectDir: string = ''): Promise<void> {
220221
}
221222
}
222223

223-
export const NgCLIVersion = new SemVer(packages['@angular/cli'].version);
224+
export const NgCLIVersion = new SemVer(require('../../../../package.json').version);
224225

225226
export function isPrereleaseCli(): boolean {
226227
return (prerelease(NgCLIVersion)?.length ?? 0) > 0;

tests/legacy-cli/e2e/utils/tar.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import fs from 'fs';
10+
import { Parse } from 'tar';
11+
12+
/**
13+
* Extract and return the contents of a single file out of a tar file.
14+
*
15+
* @param tarball the tar file to extract from
16+
* @param filePath the path of the file to extract
17+
* @returns the Buffer of file or an error on fs/tar error or file not found
18+
*/
19+
export async function extractFile(tarball: string, filePath: string): Promise<Buffer> {
20+
return new Promise((resolve, reject) => {
21+
let chunks: Buffer[] | null = null;
22+
23+
fs.createReadStream(tarball)
24+
.pipe(
25+
new Parse({
26+
strict: true,
27+
filter: (p) => p === filePath,
28+
// TODO: @types/tar 'entry' does not have ReadEntry.on
29+
onentry: (entry: any) => {
30+
chunks = [];
31+
32+
entry.on('data', (chunk: any) => chunks!.push(chunk));
33+
entry.on('error', reject);
34+
entry.on('finish', () => resolve(Buffer.concat(chunks!)));
35+
},
36+
}),
37+
)
38+
.on('close', reject);
39+
});
40+
}

tests/legacy-cli/e2e_runner.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { createNpmRegistry } from './e2e/utils/registry';
1010
import { launchTestProcess } from './e2e/utils/process';
1111
import { join } from 'path';
1212
import { findFreePort } from './e2e/utils/network';
13+
import { extractFile } from './e2e/utils/tar';
14+
import { realpathSync } from 'fs';
1315

1416
Error.stackTraceLimit = Infinity;
1517

@@ -34,6 +36,8 @@ Error.stackTraceLimit = Infinity;
3436
* --shard Index of this processes' shard.
3537
* --tmpdir=path Override temporary directory to use for new projects.
3638
* --yarn Use yarn as package manager.
39+
* --package=path An npm package to be published before running tests
40+
*
3741
* If unnamed flags are passed in, the list of tests will be filtered to include only those passed.
3842
*/
3943
const argv = yargsParser(process.argv.slice(2), {
@@ -49,6 +53,7 @@ const argv = yargsParser(process.argv.slice(2), {
4953
],
5054
string: ['devkit', 'glob', 'ignore', 'reuse', 'ng-tag', 'tmpdir', 'ng-version'],
5155
number: ['nb-shards', 'shard'],
56+
array: ['package'],
5257
configuration: {
5358
'dot-notation': false,
5459
'camel-case-expansion': false,
@@ -163,10 +168,11 @@ setGlobalVariable('argv', argv);
163168
setGlobalVariable('ci', process.env['CI']?.toLowerCase() === 'true' || process.env['CI'] === '1');
164169
setGlobalVariable('package-manager', argv.yarn ? 'yarn' : 'npm');
165170

166-
Promise.all([findFreePort(), findFreePort()])
167-
.then(async ([httpPort, httpsPort]) => {
171+
Promise.all([findFreePort(), findFreePort(), findPackageTars()])
172+
.then(async ([httpPort, httpsPort, packageTars]) => {
168173
setGlobalVariable('package-registry', 'http://localhost:' + httpPort);
169174
setGlobalVariable('package-secure-registry', 'http://localhost:' + httpsPort);
175+
setGlobalVariable('package-tars', packageTars);
170176

171177
// NPM registries for the lifetime of the test execution
172178
const registryProcess = await createNpmRegistry(httpPort, httpPort);
@@ -315,3 +321,23 @@ function printFooter(testName: string, type: 'setup' | 'initializer' | 'test', s
315321
);
316322
console.log('');
317323
}
324+
325+
// Collect the packages passed as arguments and return as {package-name => pkg-path}
326+
async function findPackageTars(): Promise<{ [pkg: string]: string }> {
327+
const pkgs: string[] = (getGlobalVariable('argv').package as string[]).flatMap((p) =>
328+
glob.sync(p),
329+
);
330+
331+
const pkgJsons = await Promise.all(pkgs.map((pkg) => extractFile(pkg, 'package/package.json')));
332+
333+
return pkgs.reduce((all, pkg, i) => {
334+
const json = pkgJsons[i].toString('utf8');
335+
const name = JSON.parse(json).name as string;
336+
if (!name) {
337+
throw new Error(`Package ${pkg} - package.json name not found`);
338+
}
339+
340+
all[name] = realpathSync(pkg);
341+
return all;
342+
}, {} as { [pkg: string]: string });
343+
}

yarn.lock

+15
Original file line numberDiff line numberDiff line change
@@ -2418,6 +2418,14 @@
24182418
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
24192419
integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==
24202420

2421+
"@types/tar@^6.1.2":
2422+
version "6.1.2"
2423+
resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.2.tgz#e60108a7d1b08cc91bf2faf1286cc08fdad48bbe"
2424+
integrity sha512-bnX3RRm70/n1WMwmevdOAeDU4YP7f5JSubgnuU+yrO+xQQjwDboJj3u2NTJI5ngCQhXihqVVAH5h5J8YpdpEvg==
2425+
dependencies:
2426+
"@types/node" "*"
2427+
minipass "^3.3.5"
2428+
24212429
"@types/text-table@^0.2.1":
24222430
version "0.2.2"
24232431
resolved "https://registry.yarnpkg.com/@types/text-table/-/text-table-0.2.2.tgz#774c90cfcfbc8b4b0ebb00fecbe861dc8b1e8e26"
@@ -7713,6 +7721,13 @@ minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6:
77137721
dependencies:
77147722
yallist "^4.0.0"
77157723

7724+
minipass@^3.3.5:
7725+
version "3.3.5"
7726+
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.5.tgz#6da7e53a48db8a856eeb9153d85b230a2119e819"
7727+
integrity sha512-rQ/p+KfKBkeNwo04U15i+hOwoVBVmekmm/HcfTkTN2t9pbQKCMm4eN5gFeqgrrSp/kH/7BYYhTIHOxGqzbBPaA==
7728+
dependencies:
7729+
yallist "^4.0.0"
7730+
77167731
minizlib@^2.1.1, minizlib@^2.1.2:
77177732
version "2.1.2"
77187733
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"

0 commit comments

Comments
 (0)