-
-
Notifications
You must be signed in to change notification settings - Fork 431
/
Copy pathcore-client-provider.slow-test.ts
333 lines (303 loc) · 12.4 KB
/
core-client-provider.slow-test.ts
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import type { MaybePromise } from '@theia/core/lib/common/types';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { Container } from '@theia/core/shared/inversify';
import { expect } from 'chai';
import { promises as fs } from 'node:fs';
import { join } from 'node:path';
import { sync as deleteSync } from 'rimraf';
import {
BoardsService,
CoreService,
LibraryService,
} from '../../common/protocol';
import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
import { CLI_CONFIG, DefaultCliConfig } from '../../node/cli-config';
import { BoardListRequest } from '../../node/cli-protocol/cc/arduino/cli/commands/v1/board_pb';
import { CoreClientProvider } from '../../node/core-client-provider';
import { spawnCommand } from '../../node/exec-util';
import { ConfigDirUriProvider } from '../../node/theia/env-variables/env-variables-server';
import { ErrnoException } from '../../node/utils/errors';
import {
createBaseContainer,
createCliConfig,
newTempConfigDirPath,
startDaemon,
} from './test-bindings';
const timeout = 5 * 60 * 1_000; // five minutes
describe('core-client-provider', () => {
let toDispose: DisposableCollection;
beforeEach(() => (toDispose = new DisposableCollection()));
afterEach(() => toDispose.dispose());
it("should update no indexes when the 'directories.data' exists", async function () {
this.timeout(timeout);
const configDirPath = await prepareTestConfigDir();
const container = await startCli(configDirPath, toDispose);
await assertFunctionalCli(container, ({ coreClientProvider }) => {
const { indexUpdateSummaryBeforeInit } = coreClientProvider;
expect(indexUpdateSummaryBeforeInit).to.be.not.undefined;
expect(indexUpdateSummaryBeforeInit).to.be.empty;
});
});
// The better translation the CLI has, the more likely IDE2 won't be able to detect primary package and library index errors.
// Instead of running the test against all supported locales, IDE2 runs the tests with locales that result in a bug.
['it', 'de'].map(([locale]) =>
it(`should recover when the 'directories.data' folder is missing independently from the CLI's locale ('${locale}')`, async function () {
this.timeout(timeout);
const configDirPath = await prepareTestConfigDir({ locale });
const container = await startCli(configDirPath, toDispose);
await assertFunctionalCli(container, ({ coreClientProvider }) => {
const { indexUpdateSummaryBeforeInit } = coreClientProvider;
expect(indexUpdateSummaryBeforeInit).to.be.not.undefined;
expect(indexUpdateSummaryBeforeInit).to.be.empty;
});
})
);
it("should recover when the 'directories.data' folder is missing", async function () {
this.timeout(timeout);
const configDirPath = await prepareTestConfigDir();
deleteSync(join(configDirPath, 'data'));
const now = new Date().toISOString();
const container = await startCli(configDirPath, toDispose);
await assertFunctionalCli(container, ({ coreClientProvider }) => {
const { indexUpdateSummaryBeforeInit } = coreClientProvider;
const libUpdateTimestamp = indexUpdateSummaryBeforeInit['library'];
expect(libUpdateTimestamp).to.be.not.empty;
expect(libUpdateTimestamp.localeCompare(now)).to.be.greaterThan(0);
const platformUpdateTimestamp = indexUpdateSummaryBeforeInit['platform'];
expect(platformUpdateTimestamp).to.be.not.empty;
expect(platformUpdateTimestamp.localeCompare(now)).to.be.greaterThan(0);
});
});
it("should recover when the primary package index file ('package_index.json') is missing", async function () {
this.timeout(timeout);
const configDirPath = await prepareTestConfigDir();
const primaryPackageIndexPath = join(
configDirPath,
'data',
'Arduino15',
'package_index.json'
);
deleteSync(primaryPackageIndexPath);
const now = new Date().toISOString();
const container = await startCli(configDirPath, toDispose);
await assertFunctionalCli(container, ({ coreClientProvider }) => {
const { indexUpdateSummaryBeforeInit } = coreClientProvider;
expect(indexUpdateSummaryBeforeInit['library']).to.be.undefined;
const platformUpdateTimestamp = indexUpdateSummaryBeforeInit['platform'];
expect(platformUpdateTimestamp).to.be.not.empty;
expect(platformUpdateTimestamp.localeCompare(now)).to.be.greaterThan(0);
});
const rawJson = await fs.readFile(primaryPackageIndexPath, {
encoding: 'utf8',
});
expect(rawJson).to.be.not.empty;
const object = JSON.parse(rawJson);
expect(object).to.be.not.empty;
});
['serial-discovery', 'mdns-discovery'].map((tool) =>
it(`should recover when the '${join(
'packages',
'builtin',
'tools',
tool
)}' folder is missing`, async function () {
this.timeout(timeout);
const configDirPath = await prepareTestConfigDir();
const builtinToolsPath = join(
configDirPath,
'data',
'Arduino15',
'packages',
'builtin',
'tools',
tool
);
deleteSync(builtinToolsPath);
const container = await startCli(configDirPath, toDispose);
await assertFunctionalCli(container, ({ coreClientProvider }) => {
const { indexUpdateSummaryBeforeInit } = coreClientProvider;
expect(indexUpdateSummaryBeforeInit).to.be.not.undefined;
expect(indexUpdateSummaryBeforeInit).to.be.empty;
});
const toolVersions = await fs.readdir(builtinToolsPath);
expect(toolVersions.length).to.be.greaterThanOrEqual(1);
})
);
it("should recover when the library index file ('library_index.json') is missing", async function () {
this.timeout(timeout);
const configDirPath = await prepareTestConfigDir();
const libraryPackageIndexPath = join(
configDirPath,
'data',
'Arduino15',
'library_index.json'
);
deleteSync(libraryPackageIndexPath);
const now = new Date().toISOString();
const container = await startCli(configDirPath, toDispose);
await assertFunctionalCli(container, ({ coreClientProvider }) => {
const { indexUpdateSummaryBeforeInit } = coreClientProvider;
const libUpdateTimestamp = indexUpdateSummaryBeforeInit['library'];
expect(libUpdateTimestamp).to.be.not.empty;
expect(libUpdateTimestamp.localeCompare(now)).to.be.greaterThan(0);
expect(indexUpdateSummaryBeforeInit['platform']).to.be.undefined;
});
const rawJson = await fs.readFile(libraryPackageIndexPath, {
encoding: 'utf8',
});
expect(rawJson).to.be.not.empty;
const object = JSON.parse(rawJson);
expect(object).to.be.not.empty;
});
it('should recover when a 3rd party package index file is missing but the platform is not installed', async function () {
this.timeout(timeout);
const additionalUrls = [
'https://www.pjrc.com/teensy/package_teensy_index.json',
];
const assertTeensyAvailable = async (boardsService: BoardsService) => {
const boardsPackages = await boardsService.search({});
expect(
boardsPackages.filter(({ id }) => id === 'teensy:avr').length
).to.be.equal(1);
};
const configDirPath = await prepareTestConfigDir({
board_manager: { additional_urls: additionalUrls },
});
const thirdPartyPackageIndexPath = join(
configDirPath,
'data',
'Arduino15',
'package_teensy_index.json'
);
deleteSync(thirdPartyPackageIndexPath);
const container = await startCli(configDirPath, toDispose);
await assertFunctionalCli(
container,
async ({ coreClientProvider, boardsService, coreService }) => {
const { indexUpdateSummaryBeforeInit } = coreClientProvider;
expect(indexUpdateSummaryBeforeInit).to.be.not.undefined;
expect(indexUpdateSummaryBeforeInit).to.be.empty;
// IDE2 cannot recover from a 3rd party package index issue.
// Only when the primary package or library index is corrupt.
// https://github.com/arduino/arduino-ide/issues/2021
await coreService.updateIndex({ types: ['platform'] });
await assertTeensyAvailable(boardsService);
}
);
});
});
interface Services {
coreClientProvider: CoreClientProvider;
coreService: CoreService;
libraryService: LibraryService;
boardsService: BoardsService;
}
async function assertFunctionalCli(
container: Container,
otherAsserts?: (services: Services) => MaybePromise<void>
): Promise<void> {
const coreClientProvider =
container.get<CoreClientProvider>(CoreClientProvider);
const coreService = container.get<CoreService>(CoreService);
const libraryService = container.get<LibraryService>(LibraryService);
const boardsService = container.get<BoardsService>(BoardsService);
expect(coreClientProvider).to.be.not.undefined;
expect(coreService).to.be.not.undefined;
expect(libraryService).to.be.not.undefined;
expect(boardsService).to.be.not.undefined;
const coreClient = coreClientProvider.tryGetClient;
expect(coreClient).to.be.not.undefined;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { client, instance } = coreClient!;
const installedBoards = await boardsService.getInstalledBoards();
expect(installedBoards.length).to.be.equal(0);
const libraries = await libraryService.search({
query: 'cmaglie',
type: 'Contributed',
});
expect(libraries.length).to.be.greaterThanOrEqual(1);
expect(
libraries.filter(({ name }) => name === 'KonnektingFlashStorage').length
).to.be.greaterThanOrEqual(1);
// IDE2 runs `board list -w` equivalent, but running a single `board list`
// is sufficient for the tests to check if the serial discover tool is OK.
await new Promise<void>((resolve, reject) =>
client.boardList(new BoardListRequest().setInstance(instance), (err) => {
if (err) {
reject(err);
}
resolve(); // The response does not matter. Tests must be relaxed. Maybe there are environments without a serial port?
})
);
return otherAsserts?.({
coreClientProvider,
coreService,
libraryService,
boardsService,
});
}
/**
* Initializes the CLI by creating a temporary config folder including the correctly initialized
* `directories.data` folder so that tests can corrupt it and test it the CLI initialization can recover.
* The resolved path is pointing the temporary config folder. By the time the promise resolves, the CLI
* daemon is stopped. This function should be used to initialize a correct `directories.data` folder and
* the config folder.
*/
async function prepareTestConfigDir(
configOverrides: Partial<DefaultCliConfig> = {}
): Promise<string> {
const params = { configDirPath: newTempConfigDirPath(), configOverrides };
const container = await createContainer(params);
const daemon = container.get<ArduinoDaemonImpl>(ArduinoDaemonImpl);
const cliPath = await daemon.getExecPath();
const configDirUriProvider =
container.get<ConfigDirUriProvider>(ConfigDirUriProvider);
const configDirPath = FileUri.fsPath(configDirUriProvider.configDirUri());
await coreUpdateIndex(cliPath, configDirPath);
return configDirPath;
}
async function coreUpdateIndex(
cliPath: string,
configDirPath: string
): Promise<void> {
const cliConfigPath = join(configDirPath, 'arduino-cli.yaml');
await fs.access(cliConfigPath);
const stdout = await spawnCommand(
cliPath,
['core', 'update-index', '--config-file', cliConfigPath],
(error) => console.error(error)
);
console.log(stdout);
}
async function startCli(
configDirPath: string,
toDispose: DisposableCollection
): Promise<Container> {
const cliConfigPath = join(configDirPath, CLI_CONFIG);
try {
await fs.readFile(cliConfigPath);
} catch (err) {
if (ErrnoException.isENOENT(err)) {
throw new Error(
`The CLI configuration was not found at ${cliConfigPath} when starting the tests.`
);
}
throw err;
}
const container = await createContainer(configDirPath);
await startDaemon(container, toDispose);
return container;
}
async function createContainer(
params:
| { configDirPath: string; configOverrides: Partial<DefaultCliConfig> }
| string = newTempConfigDirPath()
): Promise<Container> {
if (typeof params === 'string') {
return createBaseContainer({ configDirPath: params });
}
const { configDirPath, configOverrides } = params;
const cliConfig = await createCliConfig(configDirPath, configOverrides);
return createBaseContainer({ configDirPath, cliConfig });
}