Skip to content
This repository was archived by the owner on Apr 4, 2025. It is now read-only.

Commit 0ac5621

Browse files
filipesilvahansl
authored andcommitted
feat(@angular-devkit/architect): use temp folder in TestProjectHost
Using Git had a nasty tendency to clear changes while testing.
1 parent 22e88a8 commit 0ac5621

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+266
-266
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Outputs
22
bazel-*
3+
test-project-host-*
34
dist/
45

56
# IDEs

packages/angular_devkit/architect/testing/run-target-spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@ import { TestProjectHost } from './test-project-host';
1414

1515

1616
export function runTargetSpec(
17-
workspaceRoot: Path,
1817
host: TestProjectHost,
1918
targetSpec: TargetSpecifier,
2019
overrides = {},
2120
logger: logging.Logger = new logging.NullLogger(),
2221
): Observable<BuildEvent> {
2322
targetSpec = { ...targetSpec, overrides };
2423
const workspaceFile = normalize('angular.json');
25-
const workspace = new experimental.workspace.Workspace(workspaceRoot, host);
24+
const workspace = new experimental.workspace.Workspace(host.root(), host);
2625

2726
return workspace.loadWorkspaceFromHost(workspaceFile).pipe(
2827
concatMap(ws => new Architect(ws).loadArchitect()),

packages/angular_devkit/architect/testing/test-project-host.ts

+78-63
Original file line numberDiff line numberDiff line change
@@ -8,92 +8,96 @@
88

99
import {
1010
Path,
11-
getSystemPath,
11+
basename,
12+
dirname,
13+
join,
1214
normalize,
15+
relative,
1316
virtualFs,
1417
} from '@angular-devkit/core';
1518
import { NodeJsSyncHost } from '@angular-devkit/core/node';
16-
import { SpawnOptions, spawn } from 'child_process';
1719
import { Stats } from 'fs';
18-
import { EMPTY, Observable } from 'rxjs';
19-
import { concatMap, map } from 'rxjs/operators';
20+
import { EMPTY, Observable, from, of } from 'rxjs';
21+
import { concatMap, delay, map, mergeMap, retry, tap } from 'rxjs/operators';
2022

2123

22-
interface ProcessOutput {
23-
stdout: string;
24-
stderr: string;
25-
}
26-
2724
export class TestProjectHost extends NodeJsSyncHost {
28-
private _scopedSyncHost: virtualFs.SyncDelegateHost<Stats>;
25+
private _currentRoot: Path | null = null;
26+
private _scopedSyncHost: virtualFs.SyncDelegateHost<Stats> | null = null;
2927

30-
constructor(protected _root: Path) {
28+
constructor(protected _templateRoot: Path) {
3129
super();
32-
this._scopedSyncHost = new virtualFs.SyncDelegateHost(new virtualFs.ScopedHost(this, _root));
3330
}
3431

35-
scopedSync() {
36-
return this._scopedSyncHost;
37-
}
32+
root(): Path {
33+
if (this._currentRoot === null) {
34+
throw new Error('TestProjectHost must be initialized before being used.');
35+
}
3836

39-
initialize(): Observable<void> {
40-
return this.exists(normalize('.git')).pipe(
41-
concatMap(exists => !exists ? this._gitInit() : EMPTY),
42-
);
37+
// tslint:disable-next-line:non-null-operator
38+
return this._currentRoot!;
4339
}
4440

45-
restore(): Observable<void> {
46-
return this._gitClean();
41+
scopedSync(): virtualFs.SyncDelegateHost<Stats> {
42+
if (this._currentRoot === null || this._scopedSyncHost === null) {
43+
throw new Error('TestProjectHost must be initialized before being used.');
44+
}
45+
46+
// tslint:disable-next-line:non-null-operator
47+
return this._scopedSyncHost!;
4748
}
4849

49-
private _gitClean(): Observable<void> {
50-
return this._exec('git', ['clean', '-fd']).pipe(
51-
concatMap(() => this._exec('git', ['checkout', '.'])),
52-
map(() => { }),
50+
initialize(): Observable<void> {
51+
const recursiveList = (path: Path): Observable<Path> => this.list(path).pipe(
52+
// Emit each fragment individually.
53+
concatMap(fragments => from(fragments)),
54+
// Join the path with fragment.
55+
map(fragment => join(path, fragment)),
56+
// Emit directory content paths instead of the directory path.
57+
mergeMap(path => this.isDirectory(path).pipe(
58+
concatMap(isDir => isDir ? recursiveList(path) : of(path)),
59+
)),
5360
);
54-
}
5561

56-
private _gitInit(): Observable<void> {
57-
return this._exec('git', ['init']).pipe(
58-
concatMap(() => this._exec('git', ['config', 'user.email', 'angular-core+e2e@google.com'])),
59-
concatMap(() => this._exec('git', ['config', 'user.name', 'Angular DevKit Tests'])),
60-
concatMap(() => this._exec('git', ['add', '--all'])),
61-
concatMap(() => this._exec('git', ['commit', '-am', '"Initial commit"'])),
62+
// Find a unique folder that we can write to to use as current root.
63+
return this.findUniqueFolderPath().pipe(
64+
// Save the path and create a scoped host for it.
65+
tap(newFolderPath => {
66+
this._currentRoot = newFolderPath;
67+
this._scopedSyncHost = new virtualFs.SyncDelegateHost(
68+
new virtualFs.ScopedHost(this, this.root()));
69+
}),
70+
// List all files in root.
71+
concatMap(() => recursiveList(this._templateRoot)),
72+
// Copy them over to the current root.
73+
concatMap(from => {
74+
const to = join(this.root(), relative(this._templateRoot, from));
75+
76+
return this.read(from).pipe(
77+
concatMap(buffer => this.write(to, buffer)),
78+
);
79+
}),
6280
map(() => { }),
6381
);
6482
}
6583

66-
private _exec(cmd: string, args: string[]): Observable<ProcessOutput> {
67-
return new Observable(obs => {
68-
args = args.filter(x => x !== undefined);
69-
let stdout = '';
70-
let stderr = '';
71-
72-
const spawnOptions: SpawnOptions = { cwd: getSystemPath(this._root) };
73-
74-
if (process.platform.startsWith('win')) {
75-
args.unshift('/c', cmd);
76-
cmd = 'cmd.exe';
77-
spawnOptions['stdio'] = 'pipe';
78-
}
79-
80-
const childProcess = spawn(cmd, args, spawnOptions);
81-
childProcess.stdout.on('data', (data: Buffer) => stdout += data.toString('utf-8'));
82-
childProcess.stderr.on('data', (data: Buffer) => stderr += data.toString('utf-8'));
83-
84-
// Create the error here so the stack shows who called this function.
85-
const err = new Error(`Running "${cmd} ${args.join(' ')}" returned error code `);
86-
87-
childProcess.on('exit', (code) => {
88-
if (!code) {
89-
obs.next({ stdout, stderr });
90-
} else {
91-
err.message += `${code}.\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}\n`;
92-
obs.error(err);
93-
}
94-
obs.complete();
95-
});
96-
});
84+
restore(): Observable<void> {
85+
if (this._currentRoot === null) {
86+
return EMPTY;
87+
}
88+
89+
// Delete the current root and clear the variables.
90+
// Wait 50ms and retry up to 10 times, to give time for file locks to clear.
91+
return this.exists(this.root()).pipe(
92+
delay(50),
93+
concatMap(exists => exists ? this.delete(this.root()) : of(null)),
94+
retry(10),
95+
tap(() => {
96+
this._currentRoot = null;
97+
this._scopedSyncHost = null;
98+
}),
99+
map(() => { }),
100+
);
97101
}
98102

99103
writeMultipleFiles(files: { [path: string]: string | ArrayBufferLike | Buffer }): void {
@@ -137,4 +141,15 @@ export class TestProjectHost extends NodeJsSyncHost {
137141
const content = this.scopedSync().read(normalize(from));
138142
this.scopedSync().write(normalize(to), content);
139143
}
144+
145+
private findUniqueFolderPath(): Observable<Path> {
146+
// 11 character alphanumeric string.
147+
const randomString = Math.random().toString(36).slice(2);
148+
const newFolderName = `test-project-host-${basename(this._templateRoot)}-${randomString}`;
149+
const newFolderPath = join(dirname(this._templateRoot), newFolderName);
150+
151+
return this.exists(newFolderPath).pipe(
152+
concatMap(exists => exists ? this.findUniqueFolderPath() : of(newFolderPath)),
153+
);
154+
}
140155
}

packages/angular_devkit/build_angular/test/app-shell/app-shell_spec_large.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { runTargetSpec } from '@angular-devkit/architect/testing';
99
import { normalize, virtualFs } from '@angular-devkit/core';
1010
import { tap } from 'rxjs/operators';
11-
import { Timeout, host, workspaceRoot } from '../utils';
11+
import { Timeout, host } from '../utils';
1212

1313

1414
describe('AppShell Builder', () => {
@@ -38,7 +38,7 @@ describe('AppShell Builder', () => {
3838
`,
3939
});
4040

41-
runTargetSpec(workspaceRoot, host, { project: 'app', target: 'app-shell' }).pipe(
41+
runTargetSpec(host, { project: 'app', target: 'app-shell' }).pipe(
4242
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
4343
tap(() => {
4444
const fileName = 'dist/index.html';

packages/angular_devkit/build_angular/test/browser/allow-js_spec_large.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { runTargetSpec } from '@angular-devkit/architect/testing';
1010
import { tap } from 'rxjs/operators';
11-
import { Timeout, browserTargetSpec, host, workspaceRoot } from '../utils';
11+
import { Timeout, browserTargetSpec, host } from '../utils';
1212

1313

1414
describe('Browser Builder allow js', () => {
@@ -24,7 +24,7 @@ describe('Browser Builder allow js', () => {
2424
// TODO: this test originally edited tsconfig to have `"allowJs": true` but works without it.
2525
// Investigate.
2626

27-
runTargetSpec(workspaceRoot, host, browserTargetSpec).pipe(
27+
runTargetSpec(host, browserTargetSpec).pipe(
2828
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
2929
).toPromise().then(done, done.fail);
3030
}, Timeout.Basic);
@@ -37,7 +37,7 @@ describe('Browser Builder allow js', () => {
3737

3838
const overrides = { aot: true };
3939

40-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides).pipe(
40+
runTargetSpec(host, browserTargetSpec, overrides).pipe(
4141
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
4242
).toPromise().then(done, done.fail);
4343
}, Timeout.Basic);

packages/angular_devkit/build_angular/test/browser/aot_spec_large.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { runTargetSpec } from '@angular-devkit/architect/testing';
1010
import { join, normalize, virtualFs } from '@angular-devkit/core';
1111
import { tap } from 'rxjs/operators';
12-
import { Timeout, browserTargetSpec, host, workspaceRoot } from '../utils';
12+
import { Timeout, browserTargetSpec, host } from '../utils';
1313

1414

1515
describe('Browser Builder AOT', () => {
@@ -21,7 +21,7 @@ describe('Browser Builder AOT', () => {
2121
it('works', (done) => {
2222
const overrides = { aot: true };
2323

24-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides).pipe(
24+
runTargetSpec(host, browserTargetSpec, overrides).pipe(
2525
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
2626
tap(() => {
2727
const fileName = join(outputPath, 'main.js');

packages/angular_devkit/build_angular/test/browser/assets_spec_large.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { runTargetSpec } from '@angular-devkit/architect/testing';
1010
import { normalize, virtualFs } from '@angular-devkit/core';
1111
import { tap, toArray } from 'rxjs/operators';
12-
import { Timeout, browserTargetSpec, host, workspaceRoot } from '../utils';
12+
import { Timeout, browserTargetSpec, host } from '../utils';
1313

1414

1515
describe('Browser Builder assets', () => {
@@ -45,7 +45,7 @@ describe('Browser Builder assets', () => {
4545
],
4646
};
4747

48-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides).pipe(
48+
runTargetSpec(host, browserTargetSpec, overrides).pipe(
4949
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
5050
tap(() => {
5151
// Assets we expect should be there.
@@ -70,7 +70,7 @@ describe('Browser Builder assets', () => {
7070
}],
7171
};
7272

73-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides)
73+
runTargetSpec(host, browserTargetSpec, overrides)
7474
.subscribe(undefined, () => done(), done.fail);
7575

7676
// The node_modules folder must be deleted, otherwise code that tries to find the
@@ -87,7 +87,7 @@ describe('Browser Builder assets', () => {
8787
assets: ['not-source-root/file.txt'],
8888
};
8989

90-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides)
90+
runTargetSpec(host, browserTargetSpec, overrides)
9191
.subscribe(undefined, () => done(), done.fail);
9292

9393
// The node_modules folder must be deleted, otherwise code that tries to find the
@@ -100,7 +100,7 @@ describe('Browser Builder assets', () => {
100100
assets: [],
101101
};
102102

103-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides).pipe(
103+
runTargetSpec(host, browserTargetSpec, overrides).pipe(
104104
toArray(),
105105
tap((buildEvents) => expect(buildEvents.length).toBe(1)),
106106
).toPromise().then(done, done.fail);

packages/angular_devkit/build_angular/test/browser/base-href_spec_large.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { runTargetSpec } from '@angular-devkit/architect/testing';
1010
import { join, normalize, virtualFs } from '@angular-devkit/core';
1111
import { tap } from 'rxjs/operators';
12-
import { Timeout, browserTargetSpec, host, workspaceRoot } from '../utils';
12+
import { Timeout, browserTargetSpec, host } from '../utils';
1313

1414

1515
describe('Browser Builder base href', () => {
@@ -26,7 +26,7 @@ describe('Browser Builder base href', () => {
2626

2727
const overrides = { baseHref: '/myUrl' };
2828

29-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides).pipe(
29+
runTargetSpec(host, browserTargetSpec, overrides).pipe(
3030
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
3131
tap(() => {
3232
const fileName = join(outputPath, 'index.html');

packages/angular_devkit/build_angular/test/browser/build-optimizer_spec_large.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { runTargetSpec } from '@angular-devkit/architect/testing';
1010
import { join, normalize, virtualFs } from '@angular-devkit/core';
1111
import { tap } from 'rxjs/operators';
12-
import { Timeout, browserTargetSpec, host, workspaceRoot } from '../utils';
12+
import { Timeout, browserTargetSpec, host } from '../utils';
1313

1414

1515
describe('Browser Builder build optimizer', () => {
@@ -20,7 +20,7 @@ describe('Browser Builder build optimizer', () => {
2020

2121
it('works', (done) => {
2222
const overrides = { aot: true, buildOptimizer: true };
23-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides).pipe(
23+
runTargetSpec(host, browserTargetSpec, overrides).pipe(
2424
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
2525
tap(() => {
2626
const fileName = join(outputPath, 'main.js');

packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { TestLogger, runTargetSpec } from '@angular-devkit/architect/testing';
1010
import { tap } from 'rxjs/operators';
11-
import { Timeout, browserTargetSpec, host, workspaceRoot } from '../utils';
11+
import { Timeout, browserTargetSpec, host } from '../utils';
1212

1313

1414
describe('Browser Builder bundle budgets', () => {
@@ -24,7 +24,7 @@ describe('Browser Builder bundle budgets', () => {
2424

2525
const logger = new TestLogger('rebuild-type-errors');
2626

27-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides, logger).pipe(
27+
runTargetSpec(host, browserTargetSpec, overrides, logger).pipe(
2828
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
2929
tap(() => expect(logger.includes('WARNING')).toBe(false)),
3030
).toPromise().then(done, done.fail);
@@ -36,7 +36,7 @@ describe('Browser Builder bundle budgets', () => {
3636
budgets: [{ type: 'all', maximumError: '100b' }],
3737
};
3838

39-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides).pipe(
39+
runTargetSpec(host, browserTargetSpec, overrides).pipe(
4040
tap((buildEvent) => expect(buildEvent.success).toBe(false)),
4141
).toPromise().then(done, done.fail);
4242
}, Timeout.Complex);
@@ -49,7 +49,7 @@ describe('Browser Builder bundle budgets', () => {
4949

5050
const logger = new TestLogger('rebuild-type-errors');
5151

52-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides, logger).pipe(
52+
runTargetSpec(host, browserTargetSpec, overrides, logger).pipe(
5353
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
5454
tap(() => expect(logger.includes('WARNING')).toBe(true)),
5555
).toPromise().then(done, done.fail);

packages/angular_devkit/build_angular/test/browser/circular-dependency_spec_large.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { TestLogger, runTargetSpec } from '@angular-devkit/architect/testing';
1010
import { tap } from 'rxjs/operators';
11-
import { Timeout, browserTargetSpec, host, workspaceRoot } from '../utils';
11+
import { Timeout, browserTargetSpec, host } from '../utils';
1212

1313

1414
describe('Browser Builder circular dependency detection', () => {
@@ -22,7 +22,7 @@ describe('Browser Builder circular dependency detection', () => {
2222
const overrides = { baseHref: '/myUrl' };
2323
const logger = new TestLogger('circular-dependencies');
2424

25-
runTargetSpec(workspaceRoot, host, browserTargetSpec, overrides, logger).pipe(
25+
runTargetSpec(host, browserTargetSpec, overrides, logger).pipe(
2626
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
2727
tap(() => expect(logger.includes('Circular dependency detected')).toBe(true)),
2828
).toPromise().then(done, done.fail);

0 commit comments

Comments
 (0)