mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-14 06:46:36 +00:00
test: gRPC core client init integration test
- Copied the env-variable server from Theia and made it possible to customize it for the tests. Each test has its own `data` folder. - Relaxed the primary package and library index error detection. This should make the init error detection locale independent. - Kill the daemon process subtree when stopping the daemon. Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
097c92d904
commit
51f69f6a59
@ -68,7 +68,7 @@
|
|||||||
"cpy": "^8.1.2",
|
"cpy": "^8.1.2",
|
||||||
"cross-fetch": "^3.1.5",
|
"cross-fetch": "^3.1.5",
|
||||||
"dateformat": "^3.0.3",
|
"dateformat": "^3.0.3",
|
||||||
"deepmerge": "2.0.1",
|
"deepmerge": "^4.2.2",
|
||||||
"electron-updater": "^4.6.5",
|
"electron-updater": "^4.6.5",
|
||||||
"fast-json-stable-stringify": "^2.1.0",
|
"fast-json-stable-stringify": "^2.1.0",
|
||||||
"fast-safe-stringify": "^2.1.1",
|
"fast-safe-stringify": "^2.1.1",
|
||||||
|
@ -16,6 +16,7 @@ import { ArduinoDaemon, NotificationServiceServer } from '../common/protocol';
|
|||||||
import { CLI_CONFIG } from './cli-config';
|
import { CLI_CONFIG } from './cli-config';
|
||||||
import { getExecPath } from './exec-util';
|
import { getExecPath } from './exec-util';
|
||||||
import { SettingsReader } from './settings-reader';
|
import { SettingsReader } from './settings-reader';
|
||||||
|
import { ProcessUtils } from '@theia/core/lib/node/process-utils';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ArduinoDaemonImpl
|
export class ArduinoDaemonImpl
|
||||||
@ -34,6 +35,9 @@ export class ArduinoDaemonImpl
|
|||||||
@inject(SettingsReader)
|
@inject(SettingsReader)
|
||||||
private readonly settingsReader: SettingsReader;
|
private readonly settingsReader: SettingsReader;
|
||||||
|
|
||||||
|
@inject(ProcessUtils)
|
||||||
|
private readonly processUtils: ProcessUtils;
|
||||||
|
|
||||||
private readonly toDispose = new DisposableCollection();
|
private readonly toDispose = new DisposableCollection();
|
||||||
private readonly onDaemonStartedEmitter = new Emitter<string>();
|
private readonly onDaemonStartedEmitter = new Emitter<string>();
|
||||||
private readonly onDaemonStoppedEmitter = new Emitter<void>();
|
private readonly onDaemonStoppedEmitter = new Emitter<void>();
|
||||||
@ -84,8 +88,16 @@ export class ArduinoDaemonImpl
|
|||||||
).unref();
|
).unref();
|
||||||
|
|
||||||
this.toDispose.pushAll([
|
this.toDispose.pushAll([
|
||||||
Disposable.create(() => daemon.kill()),
|
Disposable.create(() => {
|
||||||
Disposable.create(() => this.fireDaemonStopped()),
|
if (daemon.pid) {
|
||||||
|
this.processUtils.terminateProcessTree(daemon.pid);
|
||||||
|
this.fireDaemonStopped();
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'The CLI Daemon process does not have a PID. IDE2 could not stop the CLI daemon.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
this.fireDaemonStarted(port);
|
this.fireDaemonStarted(port);
|
||||||
this.onData('Daemon is running.');
|
this.onData('Daemon is running.');
|
||||||
|
@ -41,7 +41,10 @@ import {
|
|||||||
} from '../common/protocol/arduino-daemon';
|
} from '../common/protocol/arduino-daemon';
|
||||||
import { ConfigServiceImpl } from './config-service-impl';
|
import { ConfigServiceImpl } from './config-service-impl';
|
||||||
import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||||
import { EnvVariablesServer } from './theia/env-variables/env-variables-server';
|
import {
|
||||||
|
ConfigDirUriProvider,
|
||||||
|
EnvVariablesServer,
|
||||||
|
} from './theia/env-variables/env-variables-server';
|
||||||
import { NodeFileSystemExt } from './node-filesystem-ext';
|
import { NodeFileSystemExt } from './node-filesystem-ext';
|
||||||
import {
|
import {
|
||||||
FileSystemExt,
|
FileSystemExt,
|
||||||
@ -236,6 +239,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
bind(DefaultWorkspaceServer).toSelf().inSingletonScope();
|
bind(DefaultWorkspaceServer).toSelf().inSingletonScope();
|
||||||
rebind(TheiaWorkspaceServer).toService(DefaultWorkspaceServer);
|
rebind(TheiaWorkspaceServer).toService(DefaultWorkspaceServer);
|
||||||
|
|
||||||
|
bind(ConfigDirUriProvider).toSelf().inSingletonScope();
|
||||||
bind(EnvVariablesServer).toSelf().inSingletonScope();
|
bind(EnvVariablesServer).toSelf().inSingletonScope();
|
||||||
rebind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
|
rebind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
|
||||||
|
|
||||||
|
@ -530,7 +530,6 @@ function isPrimaryPackageIndexMissingStatus(
|
|||||||
{ directories: { data } }: DefaultCliConfig
|
{ directories: { data } }: DefaultCliConfig
|
||||||
): boolean {
|
): boolean {
|
||||||
const predicate = ({ message }: RpcStatus.AsObject) =>
|
const predicate = ({ message }: RpcStatus.AsObject) =>
|
||||||
message.includes('loading json index file') &&
|
|
||||||
message.includes(join(data, 'package_index.json'));
|
message.includes(join(data, 'package_index.json'));
|
||||||
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
|
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
|
||||||
return evaluate(status, predicate);
|
return evaluate(status, predicate);
|
||||||
@ -551,8 +550,6 @@ function isLibraryIndexMissingStatus(
|
|||||||
{ directories: { data } }: DefaultCliConfig
|
{ directories: { data } }: DefaultCliConfig
|
||||||
): boolean {
|
): boolean {
|
||||||
const predicate = ({ message }: RpcStatus.AsObject) =>
|
const predicate = ({ message }: RpcStatus.AsObject) =>
|
||||||
message.includes('index file') &&
|
|
||||||
message.includes('reading') &&
|
|
||||||
message.includes(join(data, 'library_index.json'));
|
message.includes(join(data, 'library_index.json'));
|
||||||
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
|
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
|
||||||
return evaluate(status, predicate);
|
return evaluate(status, predicate);
|
||||||
|
@ -1,15 +1,112 @@
|
|||||||
import { join } from 'path';
|
import {
|
||||||
import { homedir } from 'os';
|
EnvVariable,
|
||||||
import { injectable } from '@theia/core/shared/inversify';
|
EnvVariablesServer as TheiaEnvVariablesServer,
|
||||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
} from '@theia/core/lib/common/env-variables/env-variables-protocol';
|
||||||
|
import { isWindows } from '@theia/core/lib/common/os';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
|
import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
|
||||||
import { EnvVariablesServerImpl as TheiaEnvVariablesServerImpl } from '@theia/core/lib/node/env-variables/env-variables-server';
|
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||||
|
import {
|
||||||
|
inject,
|
||||||
|
injectable,
|
||||||
|
postConstruct,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
|
import { list as listDrives } from 'drivelist';
|
||||||
|
import { homedir } from 'os';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class EnvVariablesServer extends TheiaEnvVariablesServerImpl {
|
export class ConfigDirUriProvider {
|
||||||
protected override readonly configDirUri = Promise.resolve(
|
private uri: URI | undefined;
|
||||||
FileUri.create(
|
|
||||||
|
configDirUri(): URI {
|
||||||
|
if (!this.uri) {
|
||||||
|
this.uri = FileUri.create(
|
||||||
join(homedir(), BackendApplicationConfigProvider.get().configDirName)
|
join(homedir(), BackendApplicationConfigProvider.get().configDirName)
|
||||||
).toString()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return this.uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy-pasted from https://github.com/eclipse-theia/theia/blob/v1.31.1/packages/core/src/node/env-variables/env-variables-server.ts
|
||||||
|
// to simplify the binding of the config directory location for tests.
|
||||||
|
@injectable()
|
||||||
|
export class EnvVariablesServer implements TheiaEnvVariablesServer {
|
||||||
|
@inject(ConfigDirUriProvider)
|
||||||
|
private readonly configDirUriProvider: ConfigDirUriProvider;
|
||||||
|
|
||||||
|
private readonly envs: { [key: string]: EnvVariable } = {};
|
||||||
|
private readonly homeDirUri = FileUri.create(homedir()).toString();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const prEnv = process.env;
|
||||||
|
Object.keys(prEnv).forEach((key: string) => {
|
||||||
|
let keyName = key;
|
||||||
|
if (isWindows) {
|
||||||
|
keyName = key.toLowerCase();
|
||||||
|
}
|
||||||
|
this.envs[keyName] = { name: keyName, value: prEnv[key] };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
console.log(
|
||||||
|
`Configuration directory URI: '${this.configDirUriProvider
|
||||||
|
.configDirUri()
|
||||||
|
.toString()}'`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExecPath(): Promise<string> {
|
||||||
|
return process.execPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVariables(): Promise<EnvVariable[]> {
|
||||||
|
return Object.keys(this.envs).map((key) => this.envs[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getValue(key: string): Promise<EnvVariable | undefined> {
|
||||||
|
if (isWindows) {
|
||||||
|
key = key.toLowerCase();
|
||||||
|
}
|
||||||
|
return this.envs[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConfigDirUri(): Promise<string> {
|
||||||
|
return this.configDirUriProvider.configDirUri().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHomeDirUri(): Promise<string> {
|
||||||
|
return this.homeDirUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDrives(): Promise<string[]> {
|
||||||
|
const uris: string[] = [];
|
||||||
|
const drives = await listDrives();
|
||||||
|
for (const drive of drives) {
|
||||||
|
for (const mountpoint of drive.mountpoints) {
|
||||||
|
if (this.filterHiddenPartitions(mountpoint.path)) {
|
||||||
|
uris.push(FileUri.create(mountpoint.path).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters hidden and system partitions.
|
||||||
|
*/
|
||||||
|
private filterHiddenPartitions(path: string): boolean {
|
||||||
|
// OS X: This is your sleep-image. When your Mac goes to sleep it writes the contents of its memory to the hard disk. (https://bit.ly/2R6cztl)
|
||||||
|
if (path === '/private/var/vm') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Ubuntu: This system partition is simply the boot partition created when the computers mother board runs UEFI rather than BIOS. (https://bit.ly/2N5duHr)
|
||||||
|
if (path === '/boot/efi') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,21 +2,16 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
|||||||
import { Container } from '@theia/core/shared/inversify';
|
import { Container } from '@theia/core/shared/inversify';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { BoardSearch, BoardsService } from '../../common/protocol';
|
import { BoardSearch, BoardsService } from '../../common/protocol';
|
||||||
import {
|
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||||
configureBackendApplicationConfigProvider,
|
|
||||||
createBaseContainer,
|
|
||||||
startDaemon,
|
|
||||||
} from './test-bindings';
|
|
||||||
|
|
||||||
describe('boards-service-impl', () => {
|
describe('boards-service-impl', () => {
|
||||||
let boardService: BoardsService;
|
let boardService: BoardsService;
|
||||||
let toDispose: DisposableCollection;
|
let toDispose: DisposableCollection;
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
configureBackendApplicationConfigProvider();
|
|
||||||
this.timeout(20_000);
|
this.timeout(20_000);
|
||||||
toDispose = new DisposableCollection();
|
toDispose = new DisposableCollection();
|
||||||
const container = createContainer();
|
const container = await createContainer();
|
||||||
await start(container, toDispose);
|
await start(container, toDispose);
|
||||||
boardService = container.get<BoardsService>(BoardsService);
|
boardService = container.get<BoardsService>(BoardsService);
|
||||||
});
|
});
|
||||||
@ -94,7 +89,7 @@ describe('boards-service-impl', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createContainer(): Container {
|
async function createContainer(): Promise<Container> {
|
||||||
return createBaseContainer();
|
return createBaseContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,337 @@
|
|||||||
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { waitForEvent } from '@theia/core/lib/common/promise-util';
|
||||||
|
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 'fs';
|
||||||
|
import { join } from '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 { 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 } },
|
||||||
|
({ boardsService }) => assertTeensyAvailable(boardsService)
|
||||||
|
);
|
||||||
|
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> = {},
|
||||||
|
otherExpect?: (services: Services) => MaybePromise<void>
|
||||||
|
): Promise<string> {
|
||||||
|
const toDispose = new DisposableCollection();
|
||||||
|
const params = { configDirPath: newTempConfigDirPath(), configOverrides };
|
||||||
|
const container = await createContainer(params);
|
||||||
|
try {
|
||||||
|
await start(container, toDispose);
|
||||||
|
await assertFunctionalCli(container, otherExpect);
|
||||||
|
const configDirUriProvider =
|
||||||
|
container.get<ConfigDirUriProvider>(ConfigDirUriProvider);
|
||||||
|
return FileUri.fsPath(configDirUriProvider.configDirUri());
|
||||||
|
} finally {
|
||||||
|
const daemon = container.get<ArduinoDaemonImpl>(ArduinoDaemonImpl);
|
||||||
|
// Wait for the daemon stop event. All subprocesses (such as `serial-discovery` and `mdns-discovery`) must terminate.
|
||||||
|
// Otherwise, `EPERM: operation not permitted, unlink` is thrown on Windows when "corrupting" the `directories.data` folder for the tests.
|
||||||
|
await Promise.all([
|
||||||
|
waitForEvent(daemon.onDaemonStopped, 5_000),
|
||||||
|
Promise.resolve(toDispose.dispose()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 start(container, toDispose);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start(
|
||||||
|
container: Container,
|
||||||
|
toDispose: DisposableCollection
|
||||||
|
): Promise<void> {
|
||||||
|
await startDaemon(container, toDispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
@ -10,11 +10,7 @@ import {
|
|||||||
CoreService,
|
CoreService,
|
||||||
SketchesService,
|
SketchesService,
|
||||||
} from '../../common/protocol';
|
} from '../../common/protocol';
|
||||||
import {
|
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||||
configureBackendApplicationConfigProvider,
|
|
||||||
createBaseContainer,
|
|
||||||
startDaemon,
|
|
||||||
} from './test-bindings';
|
|
||||||
|
|
||||||
const testTimeout = 30_000;
|
const testTimeout = 30_000;
|
||||||
const setupTimeout = 5 * 60 * 1_000; // five minutes
|
const setupTimeout = 5 * 60 * 1_000; // five minutes
|
||||||
@ -25,14 +21,10 @@ describe('core-service-impl', () => {
|
|||||||
let container: Container;
|
let container: Container;
|
||||||
let toDispose: DisposableCollection;
|
let toDispose: DisposableCollection;
|
||||||
|
|
||||||
before(() => {
|
|
||||||
configureBackendApplicationConfigProvider();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.timeout(setupTimeout);
|
this.timeout(setupTimeout);
|
||||||
toDispose = new DisposableCollection();
|
toDispose = new DisposableCollection();
|
||||||
container = createContainer();
|
container = await createContainer();
|
||||||
await start(container, toDispose);
|
await start(container, toDispose);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -97,10 +89,12 @@ async function start(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createContainer(): Container {
|
async function createContainer(): Promise<Container> {
|
||||||
return createBaseContainer((bind) => {
|
return createBaseContainer({
|
||||||
|
additionalBindings: (bind, rebind) => {
|
||||||
bind(TestCommandRegistry).toSelf().inSingletonScope();
|
bind(TestCommandRegistry).toSelf().inSingletonScope();
|
||||||
bind(CommandRegistry).toService(TestCommandRegistry);
|
rebind(CommandRegistry).toService(TestCommandRegistry);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,22 +2,16 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
|||||||
import { Container } from '@theia/core/shared/inversify';
|
import { Container } from '@theia/core/shared/inversify';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { LibrarySearch, LibraryService } from '../../common/protocol';
|
import { LibrarySearch, LibraryService } from '../../common/protocol';
|
||||||
import { LibraryServiceImpl } from '../../node/library-service-impl';
|
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||||
import {
|
|
||||||
configureBackendApplicationConfigProvider,
|
|
||||||
createBaseContainer,
|
|
||||||
startDaemon,
|
|
||||||
} from './test-bindings';
|
|
||||||
|
|
||||||
describe('library-service-impl', () => {
|
describe('library-service-impl', () => {
|
||||||
let libraryService: LibraryService;
|
let libraryService: LibraryService;
|
||||||
let toDispose: DisposableCollection;
|
let toDispose: DisposableCollection;
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
configureBackendApplicationConfigProvider();
|
|
||||||
this.timeout(20_000);
|
this.timeout(20_000);
|
||||||
toDispose = new DisposableCollection();
|
toDispose = new DisposableCollection();
|
||||||
const container = createContainer();
|
const container = await createContainer();
|
||||||
await start(container, toDispose);
|
await start(container, toDispose);
|
||||||
libraryService = container.get<LibraryService>(LibraryService);
|
libraryService = container.get<LibraryService>(LibraryService);
|
||||||
});
|
});
|
||||||
@ -72,11 +66,8 @@ describe('library-service-impl', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createContainer(): Container {
|
async function createContainer(): Promise<Container> {
|
||||||
return createBaseContainer((bind) => {
|
return createBaseContainer();
|
||||||
bind(LibraryServiceImpl).toSelf().inSingletonScope();
|
|
||||||
bind(LibraryService).toService(LibraryServiceImpl);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function start(
|
async function start(
|
||||||
|
@ -11,11 +11,7 @@ import { sync as rimrafSync } from 'rimraf';
|
|||||||
import { Sketch, SketchesService } from '../../common/protocol';
|
import { Sketch, SketchesService } from '../../common/protocol';
|
||||||
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
|
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
|
||||||
import { ErrnoException } from '../../node/utils/errors';
|
import { ErrnoException } from '../../node/utils/errors';
|
||||||
import {
|
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||||
configureBackendApplicationConfigProvider,
|
|
||||||
createBaseContainer,
|
|
||||||
startDaemon,
|
|
||||||
} from './test-bindings';
|
|
||||||
|
|
||||||
const testTimeout = 10_000;
|
const testTimeout = 10_000;
|
||||||
|
|
||||||
@ -24,9 +20,8 @@ describe('sketches-service-impl', () => {
|
|||||||
let toDispose: DisposableCollection;
|
let toDispose: DisposableCollection;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
configureBackendApplicationConfigProvider();
|
|
||||||
toDispose = new DisposableCollection();
|
toDispose = new DisposableCollection();
|
||||||
container = createContainer();
|
container = await createContainer();
|
||||||
await start(container, toDispose);
|
await start(container, toDispose);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -257,6 +252,6 @@ async function start(
|
|||||||
await startDaemon(container, toDispose);
|
await startDaemon(container, toDispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createContainer(): Container {
|
async function createContainer(): Promise<Container> {
|
||||||
return createBaseContainer();
|
return createBaseContainer();
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,20 @@ import { ILogger, Loggable } from '@theia/core/lib/common/logger';
|
|||||||
import { LogLevel } from '@theia/core/lib/common/logger-protocol';
|
import { LogLevel } from '@theia/core/lib/common/logger-protocol';
|
||||||
import { waitForEvent } from '@theia/core/lib/common/promise-util';
|
import { waitForEvent } from '@theia/core/lib/common/promise-util';
|
||||||
import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
|
import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
|
||||||
import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||||
|
import { ProcessUtils } from '@theia/core/lib/node/process-utils';
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
ContainerModule,
|
ContainerModule,
|
||||||
injectable,
|
injectable,
|
||||||
interfaces,
|
interfaces,
|
||||||
} from '@theia/core/shared/inversify';
|
} from '@theia/core/shared/inversify';
|
||||||
|
import * as deepmerge from 'deepmerge';
|
||||||
|
import { promises as fs, mkdirSync } from 'fs';
|
||||||
|
import { dump as dumpYaml } from 'js-yaml';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { path as tempPath, track } from 'temp';
|
||||||
import {
|
import {
|
||||||
ArduinoDaemon,
|
ArduinoDaemon,
|
||||||
AttachedBoardsChangeEvent,
|
AttachedBoardsChangeEvent,
|
||||||
@ -33,6 +40,7 @@ import {
|
|||||||
IndexUpdateDidFailParams,
|
IndexUpdateDidFailParams,
|
||||||
IndexUpdateParams,
|
IndexUpdateParams,
|
||||||
LibraryPackage,
|
LibraryPackage,
|
||||||
|
LibraryService,
|
||||||
NotificationServiceClient,
|
NotificationServiceClient,
|
||||||
NotificationServiceServer,
|
NotificationServiceServer,
|
||||||
OutputMessage,
|
OutputMessage,
|
||||||
@ -44,10 +52,12 @@ import {
|
|||||||
import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
|
import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
|
||||||
import { BoardDiscovery } from '../../node/board-discovery';
|
import { BoardDiscovery } from '../../node/board-discovery';
|
||||||
import { BoardsServiceImpl } from '../../node/boards-service-impl';
|
import { BoardsServiceImpl } from '../../node/boards-service-impl';
|
||||||
|
import { CLI_CONFIG, CliConfig, DefaultCliConfig } from '../../node/cli-config';
|
||||||
import { ConfigServiceImpl } from '../../node/config-service-impl';
|
import { ConfigServiceImpl } from '../../node/config-service-impl';
|
||||||
import { CoreClientProvider } from '../../node/core-client-provider';
|
import { CoreClientProvider } from '../../node/core-client-provider';
|
||||||
import { CoreServiceImpl } from '../../node/core-service-impl';
|
import { CoreServiceImpl } from '../../node/core-service-impl';
|
||||||
import { IsTempSketch } from '../../node/is-temp-sketch';
|
import { IsTempSketch } from '../../node/is-temp-sketch';
|
||||||
|
import { LibraryServiceImpl } from '../../node/library-service-impl';
|
||||||
import { MonitorManager } from '../../node/monitor-manager';
|
import { MonitorManager } from '../../node/monitor-manager';
|
||||||
import { MonitorService } from '../../node/monitor-service';
|
import { MonitorService } from '../../node/monitor-service';
|
||||||
import {
|
import {
|
||||||
@ -56,7 +66,12 @@ import {
|
|||||||
} from '../../node/monitor-service-factory';
|
} from '../../node/monitor-service-factory';
|
||||||
import { SettingsReader } from '../../node/settings-reader';
|
import { SettingsReader } from '../../node/settings-reader';
|
||||||
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
|
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
|
||||||
import { EnvVariablesServer } from '../../node/theia/env-variables/env-variables-server';
|
import {
|
||||||
|
ConfigDirUriProvider,
|
||||||
|
EnvVariablesServer,
|
||||||
|
} from '../../node/theia/env-variables/env-variables-server';
|
||||||
|
|
||||||
|
const tracked = track();
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
class ConsoleLogger extends MockLogger {
|
class ConsoleLogger extends MockLogger {
|
||||||
@ -234,12 +249,64 @@ class TestResponseService implements ResponseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBaseContainer(
|
class TestConfigDirUriProvider extends ConfigDirUriProvider {
|
||||||
containerCustomizations?: (
|
constructor(private readonly configDirPath: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
override configDirUri(): URI {
|
||||||
|
return FileUri.create(this.configDirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldKeepTestFolder(): boolean {
|
||||||
|
return (
|
||||||
|
typeof process.env.ARDUINO_IDE__KEEP_TEST_FOLDER === 'string' &&
|
||||||
|
/true/i.test(process.env.ARDUINO_IDE__KEEP_TEST_FOLDER)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newTempConfigDirPath(
|
||||||
|
prefix = 'arduino-ide--slow-tests'
|
||||||
|
): string {
|
||||||
|
let tempDirPath;
|
||||||
|
if (shouldKeepTestFolder()) {
|
||||||
|
tempDirPath = tempPath(prefix);
|
||||||
|
mkdirSync(tempDirPath, { recursive: true });
|
||||||
|
console.log(
|
||||||
|
`Detected ARDUINO_IDE__KEEP_TEST_FOLDER=true, keeping temporary test configuration folders: ${tempDirPath}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tempDirPath = tracked.mkdirSync();
|
||||||
|
}
|
||||||
|
return join(tempDirPath, '.testArduinoIDE');
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateBaseContainerParams {
|
||||||
|
readonly cliConfig?: CliConfig | (() => Promise<CliConfig>);
|
||||||
|
readonly configDirPath?: string;
|
||||||
|
readonly additionalBindings?: (
|
||||||
bind: interfaces.Bind,
|
bind: interfaces.Bind,
|
||||||
rebind: interfaces.Rebind
|
rebind: interfaces.Rebind
|
||||||
) => void
|
) => void;
|
||||||
): Container {
|
}
|
||||||
|
|
||||||
|
export async function createBaseContainer(
|
||||||
|
params?: CreateBaseContainerParams
|
||||||
|
): Promise<Container> {
|
||||||
|
const configDirUriProvider = new TestConfigDirUriProvider(
|
||||||
|
params?.configDirPath || newTempConfigDirPath()
|
||||||
|
);
|
||||||
|
if (params?.cliConfig) {
|
||||||
|
const config =
|
||||||
|
typeof params.cliConfig === 'function'
|
||||||
|
? await params.cliConfig()
|
||||||
|
: params.cliConfig;
|
||||||
|
await writeCliConfigFile(
|
||||||
|
FileUri.fsPath(configDirUriProvider.configDirUri()),
|
||||||
|
config
|
||||||
|
);
|
||||||
|
}
|
||||||
const container = new Container({ defaultScope: 'Singleton' });
|
const container = new Container({ defaultScope: 'Singleton' });
|
||||||
const module = new ContainerModule((bind, unbind, isBound, rebind) => {
|
const module = new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||||
bind(CoreClientProvider).toSelf().inSingletonScope();
|
bind(CoreClientProvider).toSelf().inSingletonScope();
|
||||||
@ -263,6 +330,7 @@ export function createBaseContainer(
|
|||||||
return child.get<MonitorService>(MonitorService);
|
return child.get<MonitorService>(MonitorService);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
bind(ConfigDirUriProvider).toConstantValue(configDirUriProvider);
|
||||||
bind(EnvVariablesServer).toSelf().inSingletonScope();
|
bind(EnvVariablesServer).toSelf().inSingletonScope();
|
||||||
bind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
|
bind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
|
||||||
bind(SilentArduinoDaemon).toSelf().inSingletonScope();
|
bind(SilentArduinoDaemon).toSelf().inSingletonScope();
|
||||||
@ -274,6 +342,7 @@ export function createBaseContainer(
|
|||||||
bind(NotificationServiceServer).toService(TestNotificationServiceServer);
|
bind(NotificationServiceServer).toService(TestNotificationServiceServer);
|
||||||
bind(ConfigServiceImpl).toSelf().inSingletonScope();
|
bind(ConfigServiceImpl).toSelf().inSingletonScope();
|
||||||
bind(ConfigService).toService(ConfigServiceImpl);
|
bind(ConfigService).toService(ConfigServiceImpl);
|
||||||
|
bind(CommandRegistry).toSelf().inSingletonScope();
|
||||||
bind(CommandService).toService(CommandRegistry);
|
bind(CommandService).toService(CommandRegistry);
|
||||||
bindContributionProvider(bind, CommandContribution);
|
bindContributionProvider(bind, CommandContribution);
|
||||||
bind(TestBoardDiscovery).toSelf().inSingletonScope();
|
bind(TestBoardDiscovery).toSelf().inSingletonScope();
|
||||||
@ -282,14 +351,48 @@ export function createBaseContainer(
|
|||||||
bind(SketchesServiceImpl).toSelf().inSingletonScope();
|
bind(SketchesServiceImpl).toSelf().inSingletonScope();
|
||||||
bind(SketchesService).toService(SketchesServiceImpl);
|
bind(SketchesService).toService(SketchesServiceImpl);
|
||||||
bind(SettingsReader).toSelf().inSingletonScope();
|
bind(SettingsReader).toSelf().inSingletonScope();
|
||||||
if (containerCustomizations) {
|
bind(LibraryServiceImpl).toSelf().inSingletonScope();
|
||||||
containerCustomizations(bind, rebind);
|
bind(LibraryService).toService(LibraryServiceImpl);
|
||||||
}
|
bind(ProcessUtils).toSelf().inSingletonScope();
|
||||||
|
params?.additionalBindings?.(bind, rebind);
|
||||||
});
|
});
|
||||||
container.load(module);
|
container.load(module);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeCliConfigFile(
|
||||||
|
containerFolderPath: string,
|
||||||
|
cliConfig: CliConfig
|
||||||
|
): Promise<void> {
|
||||||
|
await fs.mkdir(containerFolderPath, { recursive: true });
|
||||||
|
const yaml = dumpYaml(cliConfig);
|
||||||
|
const cliConfigPath = join(containerFolderPath, CLI_CONFIG);
|
||||||
|
await fs.writeFile(cliConfigPath, yaml);
|
||||||
|
console.debug(`Created CLI configuration file at ${cliConfigPath}:
|
||||||
|
${yaml}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCliConfig(
|
||||||
|
configDirPath: string,
|
||||||
|
configOverrides: Partial<DefaultCliConfig> = {}
|
||||||
|
): Promise<DefaultCliConfig> {
|
||||||
|
const directories = {
|
||||||
|
data: join(configDirPath, 'data', 'Arduino15'),
|
||||||
|
downloads: join(configDirPath, 'data', 'Arduino15', 'staging'),
|
||||||
|
builtin: join(configDirPath, 'data', 'Arduino15', 'libraries'),
|
||||||
|
user: join(configDirPath, 'user', 'Arduino'),
|
||||||
|
};
|
||||||
|
for (const directoryPath of Object.values(directories)) {
|
||||||
|
await fs.mkdir(directoryPath, { recursive: true });
|
||||||
|
}
|
||||||
|
const config = { directories };
|
||||||
|
const mergedOverrides = deepmerge(configOverrides, <DefaultCliConfig>{
|
||||||
|
logging: { level: 'trace' },
|
||||||
|
});
|
||||||
|
return deepmerge(config, mergedOverrides);
|
||||||
|
}
|
||||||
|
|
||||||
export async function startDaemon(
|
export async function startDaemon(
|
||||||
container: Container,
|
container: Container,
|
||||||
toDispose: DisposableCollection,
|
toDispose: DisposableCollection,
|
||||||
@ -313,18 +416,3 @@ export async function startDaemon(
|
|||||||
await startCustomizations(container, toDispose);
|
await startCustomizations(container, toDispose);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function configureBackendApplicationConfigProvider(): void {
|
|
||||||
try {
|
|
||||||
BackendApplicationConfigProvider.get();
|
|
||||||
} catch (err) {
|
|
||||||
if (
|
|
||||||
err instanceof Error &&
|
|
||||||
err.message.includes('BackendApplicationConfigProvider#set')
|
|
||||||
) {
|
|
||||||
BackendApplicationConfigProvider.set({
|
|
||||||
configDirName: '.testArduinoIDE',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -6296,11 +6296,6 @@ deepmerge@*, deepmerge@^4.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||||
|
|
||||||
deepmerge@2.0.1:
|
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.0.1.tgz#25c1c24f110fb914f80001b925264dd77f3f4312"
|
|
||||||
integrity sha512-VIPwiMJqJ13ZQfaCsIFnp5Me9tnjURiaIFxfz7EH0Ci0dTSQpZtSLrqOicXqEd/z2r+z+Klk9GzmnRsgpgbOsQ==
|
|
||||||
|
|
||||||
default-compare@^1.0.0:
|
default-compare@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f"
|
resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user