Repository navigation
Expand file tree
/
Copy pathpersistentIpc.test.ts
More file actions
143 lines (135 loc) · 6.05 KB
/
Copy pathpersistentIpc.test.ts
File metadata and controls
143 lines (135 loc) · 6.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as fs from 'node:fs';
import * as path from 'node:path';
import { setTimeout as delayAsync } from 'node:timers/promises';
import { createPersistentIpcTestFixture, type IPersistentIpcTestFixture } from './PersistentIpcTestFixture';
jest.setTimeout(45_000);
describe('explicit persistent Node IPC through the public client', () => {
let activeFixture: IPersistentIpcTestFixture | undefined;
afterEach(async () => {
const closing = activeFixture;
activeFixture = undefined;
await closing?.closeAsync();
}, 35_000);
function withFixtureAsync(
work: (current: IPersistentIpcTestFixture) => Promise<void>,
options: Parameters<typeof createPersistentIpcTestFixture>[0] = {}
): Promise<void> {
const current = createPersistentIpcTestFixture(options);
activeFixture = current;
return current.runAsync(() => work(current));
}
it('runs the real Node launcher, preserves raw args, reuses PID and skips unchanged work', () =>
withFixtureAsync(async (fixture) => {
const result = await fixture.invokeAsync([
'build',
'--only',
'a',
'--label',
'custom raw value',
'--ignored',
'excluded',
'--verbose'
]);
if (result.code !== 0) throw new Error(`IPC invocation failed: ${result.stderr}\n${result.stdout}`);
expect(result.code).toBe(0);
expect(result.stderr).not.toMatch(/using in-process/i);
expect(result.stdout).toContain('ipc-a-one-original');
const ready = fixture.events().filter((event) => event.kind === 'ready');
expect(ready).toHaveLength(1);
expect(ready[0].args).toEqual([
'literal space',
'"quoted"',
'%NOT_EXPANDED%',
'--label',
'custom raw value'
]);
const status = await fixture.statusAsync();
expect(status.workspace?.warmSet?.measuredRunnerMemoryBytes).toBeGreaterThan(0);
expect(status.workspace?.warmSet?.unmeasuredRunnerCount).toBe(0);
const argv = [
'build',
'--only',
'a',
'--label',
'custom raw value',
'--ignored',
'excluded',
'--verbose'
];
expect((await fixture.invokeAsync(argv)).code).toBe(0);
expect(fixture.events().filter((event) => event.kind === 'complete')).toHaveLength(1);
fixture.input('a', { value: 'two' });
await delayAsync(100);
expect(fixture.events().filter((event) => event.kind === 'complete')).toHaveLength(1);
expect((await fixture.invokeAsync(argv)).code).toBe(0);
const events = fixture.events();
expect(events.filter((event) => event.kind === 'ready')).toHaveLength(1);
expect(events.filter((event) => event.kind === 'complete').map((event) => event.pid)).toEqual([
ready[0].pid,
ready[0].pid
]);
expect((await fixture.statusAsync()).workspace?.generationToken).toBe(
status.workspace?.generationToken
);
expect(fs.readFileSync(path.join(fixture.folder, 'a/lib/result.txt'), 'utf8')).toBe('two:original');
}));
it('retires entrypoint and local tool implementation changes without restarting on ordinary input', () =>
withFixtureAsync(async (fixture) => {
await fixture.buildAsync('--only', 'a');
const first = await fixture.statusAsync();
const oldPid = fixture.events().find((event) => event.kind === 'ready')!.pid;
fixture.write('a/tools/ipc/implementation.cjs', "exports.version = 'changed';\n");
await fixture.buildAsync('--only', 'a');
const second = await fixture.statusAsync();
expect(second.pid).toBe(first.pid);
expect(second.workspace?.generationToken).not.toBe(first.workspace?.generationToken);
expect(fixture.events().some((event) => event.kind === 'closed' && event.pid === oldPid)).toBe(true);
expect(
fixture
.events()
.filter((event) => event.kind === 'ready')
.at(-1)?.pid
).not.toBe(oldPid);
expect(fs.readFileSync(path.join(fixture.folder, 'a/lib/result.txt'), 'utf8')).toBe('one:changed');
const nextPid = fixture
.events()
.filter((event) => event.kind === 'ready')
.at(-1)!.pid;
const entry = fs.readFileSync(path.join(fixture.folder, 'a/tools/ipc/entry.cjs'), 'utf8');
fixture.write('a/tools/ipc/entry.cjs', `${entry}\n// Updated entrypoint implementation.\n`);
await fixture.buildAsync('--only', 'a');
expect(fixture.events().some((event) => event.kind === 'closed' && event.pid === nextPid)).toBe(true);
expect(
fixture
.events()
.filter((event) => event.kind === 'ready')
.at(-1)?.pid
).not.toBe(nextPid);
}));
it('leaves non-opted-in builds and rebuilds on the native shell path', () =>
withFixtureAsync(
async (fixture) => {
await fixture.buildAsync('--only', 'a');
expect(fixture.events()).toEqual([]);
expect(fs.readFileSync(path.join(fixture.folder, 'runs.txt'), 'utf8')).toContain('a:one');
const config = JSON.parse(fs.readFileSync(path.join(fixture.folder, 'rush.json'), 'utf8'));
config.daemon.usePersistentIpcRunners = true;
fixture.write('rush.json', JSON.stringify(config));
await fixture.buildAsync('--only', 'a');
const oldPid = fixture.events().find((event) => event.kind === 'ready')!.pid;
for (let index = 0; index < 2; index++) {
const result = await fixture.invokeAsync(['rebuild', '--only', 'a', '--verbose']);
expect(result.code).toBe(0);
expect(result.stderr).not.toMatch(/using in-process/i);
}
expect(fixture.events().filter((event) => event.kind === 'ready')).toHaveLength(1);
expect(fixture.events().some((event) => event.kind === 'closed' && event.pid === oldPid)).toBe(true);
expect(
fs.readFileSync(path.join(fixture.folder, 'runs.txt'), 'utf8').trim().split('\n')
).toHaveLength(3);
},
{ enabled: false }
));
});