mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-09 18:38:33 +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:
@@ -2,21 +2,16 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Container } from '@theia/core/shared/inversify';
|
||||
import { expect } from 'chai';
|
||||
import { BoardSearch, BoardsService } from '../../common/protocol';
|
||||
import {
|
||||
configureBackendApplicationConfigProvider,
|
||||
createBaseContainer,
|
||||
startDaemon,
|
||||
} from './test-bindings';
|
||||
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||
|
||||
describe('boards-service-impl', () => {
|
||||
let boardService: BoardsService;
|
||||
let toDispose: DisposableCollection;
|
||||
|
||||
before(async function () {
|
||||
configureBackendApplicationConfigProvider();
|
||||
this.timeout(20_000);
|
||||
toDispose = new DisposableCollection();
|
||||
const container = createContainer();
|
||||
const container = await createContainer();
|
||||
await start(container, toDispose);
|
||||
boardService = container.get<BoardsService>(BoardsService);
|
||||
});
|
||||
@@ -94,7 +89,7 @@ describe('boards-service-impl', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function createContainer(): Container {
|
||||
async function createContainer(): Promise<Container> {
|
||||
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,
|
||||
SketchesService,
|
||||
} from '../../common/protocol';
|
||||
import {
|
||||
configureBackendApplicationConfigProvider,
|
||||
createBaseContainer,
|
||||
startDaemon,
|
||||
} from './test-bindings';
|
||||
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||
|
||||
const testTimeout = 30_000;
|
||||
const setupTimeout = 5 * 60 * 1_000; // five minutes
|
||||
@@ -25,14 +21,10 @@ describe('core-service-impl', () => {
|
||||
let container: Container;
|
||||
let toDispose: DisposableCollection;
|
||||
|
||||
before(() => {
|
||||
configureBackendApplicationConfigProvider();
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
this.timeout(setupTimeout);
|
||||
toDispose = new DisposableCollection();
|
||||
container = createContainer();
|
||||
container = await createContainer();
|
||||
await start(container, toDispose);
|
||||
});
|
||||
|
||||
@@ -97,10 +89,12 @@ async function start(
|
||||
});
|
||||
}
|
||||
|
||||
function createContainer(): Container {
|
||||
return createBaseContainer((bind) => {
|
||||
bind(TestCommandRegistry).toSelf().inSingletonScope();
|
||||
bind(CommandRegistry).toService(TestCommandRegistry);
|
||||
async function createContainer(): Promise<Container> {
|
||||
return createBaseContainer({
|
||||
additionalBindings: (bind, rebind) => {
|
||||
bind(TestCommandRegistry).toSelf().inSingletonScope();
|
||||
rebind(CommandRegistry).toService(TestCommandRegistry);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,16 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Container } from '@theia/core/shared/inversify';
|
||||
import { expect } from 'chai';
|
||||
import { LibrarySearch, LibraryService } from '../../common/protocol';
|
||||
import { LibraryServiceImpl } from '../../node/library-service-impl';
|
||||
import {
|
||||
configureBackendApplicationConfigProvider,
|
||||
createBaseContainer,
|
||||
startDaemon,
|
||||
} from './test-bindings';
|
||||
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||
|
||||
describe('library-service-impl', () => {
|
||||
let libraryService: LibraryService;
|
||||
let toDispose: DisposableCollection;
|
||||
|
||||
before(async function () {
|
||||
configureBackendApplicationConfigProvider();
|
||||
this.timeout(20_000);
|
||||
toDispose = new DisposableCollection();
|
||||
const container = createContainer();
|
||||
const container = await createContainer();
|
||||
await start(container, toDispose);
|
||||
libraryService = container.get<LibraryService>(LibraryService);
|
||||
});
|
||||
@@ -72,11 +66,8 @@ describe('library-service-impl', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function createContainer(): Container {
|
||||
return createBaseContainer((bind) => {
|
||||
bind(LibraryServiceImpl).toSelf().inSingletonScope();
|
||||
bind(LibraryService).toService(LibraryServiceImpl);
|
||||
});
|
||||
async function createContainer(): Promise<Container> {
|
||||
return createBaseContainer();
|
||||
}
|
||||
|
||||
async function start(
|
||||
|
||||
@@ -11,11 +11,7 @@ import { sync as rimrafSync } from 'rimraf';
|
||||
import { Sketch, SketchesService } from '../../common/protocol';
|
||||
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
|
||||
import { ErrnoException } from '../../node/utils/errors';
|
||||
import {
|
||||
configureBackendApplicationConfigProvider,
|
||||
createBaseContainer,
|
||||
startDaemon,
|
||||
} from './test-bindings';
|
||||
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||
|
||||
const testTimeout = 10_000;
|
||||
|
||||
@@ -24,9 +20,8 @@ describe('sketches-service-impl', () => {
|
||||
let toDispose: DisposableCollection;
|
||||
|
||||
before(async () => {
|
||||
configureBackendApplicationConfigProvider();
|
||||
toDispose = new DisposableCollection();
|
||||
container = createContainer();
|
||||
container = await createContainer();
|
||||
await start(container, toDispose);
|
||||
});
|
||||
|
||||
@@ -257,6 +252,6 @@ async function start(
|
||||
await startDaemon(container, toDispose);
|
||||
}
|
||||
|
||||
function createContainer(): Container {
|
||||
async function createContainer(): Promise<Container> {
|
||||
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 { waitForEvent } from '@theia/core/lib/common/promise-util';
|
||||
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 {
|
||||
Container,
|
||||
ContainerModule,
|
||||
injectable,
|
||||
interfaces,
|
||||
} 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 {
|
||||
ArduinoDaemon,
|
||||
AttachedBoardsChangeEvent,
|
||||
@@ -33,6 +40,7 @@ import {
|
||||
IndexUpdateDidFailParams,
|
||||
IndexUpdateParams,
|
||||
LibraryPackage,
|
||||
LibraryService,
|
||||
NotificationServiceClient,
|
||||
NotificationServiceServer,
|
||||
OutputMessage,
|
||||
@@ -44,10 +52,12 @@ import {
|
||||
import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
|
||||
import { BoardDiscovery } from '../../node/board-discovery';
|
||||
import { BoardsServiceImpl } from '../../node/boards-service-impl';
|
||||
import { CLI_CONFIG, CliConfig, DefaultCliConfig } from '../../node/cli-config';
|
||||
import { ConfigServiceImpl } from '../../node/config-service-impl';
|
||||
import { CoreClientProvider } from '../../node/core-client-provider';
|
||||
import { CoreServiceImpl } from '../../node/core-service-impl';
|
||||
import { IsTempSketch } from '../../node/is-temp-sketch';
|
||||
import { LibraryServiceImpl } from '../../node/library-service-impl';
|
||||
import { MonitorManager } from '../../node/monitor-manager';
|
||||
import { MonitorService } from '../../node/monitor-service';
|
||||
import {
|
||||
@@ -56,7 +66,12 @@ import {
|
||||
} from '../../node/monitor-service-factory';
|
||||
import { SettingsReader } from '../../node/settings-reader';
|
||||
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()
|
||||
class ConsoleLogger extends MockLogger {
|
||||
@@ -234,12 +249,64 @@ class TestResponseService implements ResponseService {
|
||||
}
|
||||
}
|
||||
|
||||
export function createBaseContainer(
|
||||
containerCustomizations?: (
|
||||
class TestConfigDirUriProvider extends ConfigDirUriProvider {
|
||||
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,
|
||||
rebind: interfaces.Rebind
|
||||
) => void
|
||||
): Container {
|
||||
) => void;
|
||||
}
|
||||
|
||||
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 module = new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(CoreClientProvider).toSelf().inSingletonScope();
|
||||
@@ -263,6 +330,7 @@ export function createBaseContainer(
|
||||
return child.get<MonitorService>(MonitorService);
|
||||
}
|
||||
);
|
||||
bind(ConfigDirUriProvider).toConstantValue(configDirUriProvider);
|
||||
bind(EnvVariablesServer).toSelf().inSingletonScope();
|
||||
bind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
|
||||
bind(SilentArduinoDaemon).toSelf().inSingletonScope();
|
||||
@@ -274,6 +342,7 @@ export function createBaseContainer(
|
||||
bind(NotificationServiceServer).toService(TestNotificationServiceServer);
|
||||
bind(ConfigServiceImpl).toSelf().inSingletonScope();
|
||||
bind(ConfigService).toService(ConfigServiceImpl);
|
||||
bind(CommandRegistry).toSelf().inSingletonScope();
|
||||
bind(CommandService).toService(CommandRegistry);
|
||||
bindContributionProvider(bind, CommandContribution);
|
||||
bind(TestBoardDiscovery).toSelf().inSingletonScope();
|
||||
@@ -282,14 +351,48 @@ export function createBaseContainer(
|
||||
bind(SketchesServiceImpl).toSelf().inSingletonScope();
|
||||
bind(SketchesService).toService(SketchesServiceImpl);
|
||||
bind(SettingsReader).toSelf().inSingletonScope();
|
||||
if (containerCustomizations) {
|
||||
containerCustomizations(bind, rebind);
|
||||
}
|
||||
bind(LibraryServiceImpl).toSelf().inSingletonScope();
|
||||
bind(LibraryService).toService(LibraryServiceImpl);
|
||||
bind(ProcessUtils).toSelf().inSingletonScope();
|
||||
params?.additionalBindings?.(bind, rebind);
|
||||
});
|
||||
container.load(module);
|
||||
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(
|
||||
container: Container,
|
||||
toDispose: DisposableCollection,
|
||||
@@ -313,18 +416,3 @@ export async function startDaemon(
|
||||
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',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user