test: added compiler + build output path test

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2023-01-19 15:23:20 +01:00 committed by Akos Kitta
parent 6140ae525c
commit 658f117e93
8 changed files with 341 additions and 18 deletions

View File

@ -17,6 +17,7 @@
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
"watch": "tsc -w",
"test": "mocha \"./lib/test/**/*.test.js\"",
"test:slow": "mocha \"./lib/test/**/*.slow-test.js\"",
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
},
"dependencies": {

View File

@ -141,6 +141,16 @@ export const BoardsService = Symbol('BoardsService');
export interface BoardsService
extends Installable<BoardsPackage>,
Searchable<BoardsPackage, BoardSearch> {
install(options: {
item: BoardsPackage;
progressId?: string;
version?: Installable.Version;
noOverwrite?: boolean;
/**
* Only for testing to avoid confirmation dialogs from Windows User Access Control when installing a platform.
*/
skipPostInstall?: boolean;
}): Promise<void>;
getState(): Promise<AvailablePorts>;
getBoardDetails(options: { fqbn: string }): Promise<BoardDetails | undefined>;
getBoardPackage(options: { id: string }): Promise<BoardsPackage | undefined>;

View File

@ -434,6 +434,7 @@ export class BoardsServiceImpl
progressId?: string;
version?: Installable.Version;
noOverwrite?: boolean;
skipPostInstall?: boolean;
}): Promise<void> {
const item = options.item;
const version = !!options.version
@ -450,6 +451,9 @@ export class BoardsServiceImpl
req.setPlatformPackage(platform);
req.setVersion(version);
req.setNoOverwrite(Boolean(options.noOverwrite));
if (options.skipPostInstall) {
req.setSkipPostInstall(true);
}
console.info('>>> Starting boards package installation...', item);

View File

@ -63,7 +63,6 @@ export class CoreClientProvider {
new Emitter<CoreClientProvider.Client>();
private readonly onClientReady = this.onClientReadyEmitter.event;
private ready = new Deferred<void>();
private pending: Deferred<CoreClientProvider.Client> | undefined;
private _client: CoreClientProvider.Client | undefined;
@ -135,14 +134,6 @@ export class CoreClientProvider {
const client = await this.createClient(address);
this.toDisposeOnCloseClient.pushAll([
Disposable.create(() => client.client.close()),
Disposable.create(() => {
this.ready.reject(
new Error(
`Disposed. Creating a new gRPC core client on address ${address}.`
)
);
this.ready = new Deferred();
}),
]);
await this.initInstanceWithFallback(client);
return this.useClient(client);

View File

@ -19,7 +19,7 @@ import {
SketchContainer,
SketchesError,
} from '../common/protocol/sketches-service';
import { NotificationServiceServerImpl } from './notification-service-server';
import { NotificationServiceServer } from '../common/protocol';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { CoreClientAware } from './core-client-provider';
import {
@ -77,8 +77,8 @@ export class SketchesServiceImpl
@inject(ConfigServiceImpl)
private readonly configService: ConfigServiceImpl;
@inject(NotificationServiceServerImpl)
private readonly notificationService: NotificationServiceServerImpl;
@inject(NotificationServiceServer)
private readonly notificationService: NotificationServiceServer;
@inject(EnvVariablesServer)
private readonly envVariableServer: EnvVariablesServer;

View File

@ -0,0 +1,314 @@
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
import {
CommandContribution,
CommandRegistry,
CommandService,
} from '@theia/core/lib/common/command';
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
import { Disposable } from '@theia/core/lib/common/disposable';
import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { ILogger } from '@theia/core/lib/common/logger';
import { isWindows } from '@theia/core/lib/common/os';
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 { FileUri } from '@theia/core/lib/node/file-uri';
import {
Container,
ContainerModule,
injectable,
} from '@theia/core/shared/inversify';
import { expect } from 'chai';
import {
ArduinoDaemon,
AttachedBoardsChangeEvent,
AvailablePorts,
BoardsPackage,
BoardsService,
ConfigService,
ConfigState,
CoreService,
IndexUpdateDidCompleteParams,
IndexUpdateDidFailParams,
IndexUpdateParams,
LibraryPackage,
NotificationServiceClient,
NotificationServiceServer,
OutputMessage,
ProgressMessage,
ResponseService,
Sketch,
SketchesService,
} from '../../common/protocol';
import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
import { BoardDiscovery } from '../../node/board-discovery';
import { BoardsServiceImpl } from '../../node/boards-service-impl';
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 { MonitorManager } from '../../node/monitor-manager';
import { MonitorService } from '../../node/monitor-service';
import {
MonitorServiceFactory,
MonitorServiceFactoryOptions,
} from '../../node/monitor-service-factory';
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
import { EnvVariablesServer } from '../../node/theia/env-variables/env-variables-server';
const testTimeout = 30_000;
const setupTimeout = 5 * 60 * 1_000; // five minutes
const avr = 'arduino:avr';
const uno = 'arduino:avr:uno';
describe('core-service-impl', () => {
let container: Container;
let toDispose: Disposable[];
before(() => {
BackendApplicationConfigProvider.set({ configDirName: 'testArduinoIDE' });
});
beforeEach(async function () {
this.timeout(setupTimeout);
toDispose = [];
container = createContainer();
await start(container, toDispose);
});
afterEach(() => {
let disposable = toDispose.pop();
while (disposable) {
try {
disposable?.dispose();
} catch {}
disposable = toDispose.pop();
}
});
describe('compile', () => {
it('should execute a command with the build path', async function () {
this.timeout(testTimeout);
const coreService = container.get<CoreService>(CoreService);
const sketchesService = container.get<SketchesService>(SketchesService);
const commandService =
container.get<TestCommandRegistry>(TestCommandRegistry);
const sketch = await sketchesService.createNewSketch();
await coreService.compile({
fqbn: uno,
sketch,
optimizeForDebug: false,
sourceOverride: {},
verbose: true,
});
const executedBuildDidCompleteCommands =
commandService.executedCommands.filter(
([command]) =>
command === 'arduino.languageserver.notifyBuildDidComplete'
);
expect(executedBuildDidCompleteCommands.length).to.be.equal(1);
const [, args] = executedBuildDidCompleteCommands[0];
expect(args.length).to.be.equal(1);
const arg = args[0];
expect(typeof arg).to.be.equal('object');
expect('buildOutputUri' in arg).to.be.true;
expect(arg.buildOutputUri).to.be.not.undefined;
const tempBuildPaths = await sketchesService.tempBuildPath(sketch);
if (isWindows) {
expect(tempBuildPaths.length).to.be.greaterThan(1);
} else {
expect(tempBuildPaths.length).to.be.equal(1);
}
const { buildOutputUri } = arg;
const buildOutputPath = FileUri.fsPath(buildOutputUri).toString();
expect(tempBuildPaths.includes(buildOutputPath)).to.be.true;
});
});
});
async function start(
container: Container,
toDispose: Disposable[]
): Promise<void> {
const daemon = container.get<ArduinoDaemonImpl>(ArduinoDaemonImpl);
const configService = container.get<ConfigServiceImpl>(ConfigServiceImpl);
toDispose.push(Disposable.create(() => daemon.stop()));
configService.onStart();
daemon.onStart();
await waitForEvent(daemon.onDaemonStarted, 10_000);
const boardService = container.get<BoardsService>(BoardsService);
const searchResults = await boardService.search({ query: avr });
const platform = searchResults.find(({ id }) => id === avr);
if (!platform) {
throw new Error(`Could not find platform: ${avr}`);
}
await boardService.install({ item: platform, skipPostInstall: true });
}
function createContainer(): Container {
const container = new Container({ defaultScope: 'Singleton' });
const module = new ContainerModule((bind) => {
bind(CoreClientProvider).toSelf().inSingletonScope();
bind(CoreServiceImpl).toSelf().inSingletonScope();
bind(CoreService).toService(CoreServiceImpl);
bind(BoardsServiceImpl).toSelf().inSingletonScope();
bind(BoardsService).toService(BoardsServiceImpl);
bind(TestResponseService).toSelf().inSingletonScope();
bind(ResponseService).toService(TestResponseService);
bind(MonitorManager).toSelf().inSingletonScope();
bind(MonitorServiceFactory).toFactory(
({ container }) =>
(options: MonitorServiceFactoryOptions) => {
const child = container.createChild();
child
.bind<MonitorServiceFactoryOptions>(MonitorServiceFactoryOptions)
.toConstantValue({
...options,
});
child.bind(MonitorService).toSelf();
return child.get<MonitorService>(MonitorService);
}
);
bind(EnvVariablesServer).toSelf().inSingletonScope();
bind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
bind(ArduinoDaemonImpl).toSelf().inSingletonScope();
bind(ArduinoDaemon).toService(ArduinoDaemonImpl);
bind(MockLogger).toSelf().inSingletonScope();
bind(ILogger).toService(MockLogger);
bind(TestNotificationServiceServer).toSelf().inSingletonScope();
bind(NotificationServiceServer).toService(TestNotificationServiceServer);
bind(ConfigServiceImpl).toSelf().inSingletonScope();
bind(ConfigService).toService(ConfigServiceImpl);
bind(TestCommandRegistry).toSelf().inSingletonScope();
bind(CommandRegistry).toService(TestCommandRegistry);
bind(CommandService).toService(CommandRegistry);
bindContributionProvider(bind, CommandContribution);
bind(TestBoardDiscovery).toSelf().inSingletonScope();
bind(BoardDiscovery).toService(TestBoardDiscovery);
bind(IsTempSketch).toSelf().inSingletonScope();
bind(SketchesServiceImpl).toSelf().inSingletonScope();
bind(SketchesService).toService(SketchesServiceImpl);
});
container.load(module);
return container;
}
@injectable()
class TestResponseService implements ResponseService {
readonly outputMessages: OutputMessage[] = [];
readonly progressMessages: ProgressMessage[] = [];
appendToOutput(message: OutputMessage): void {
this.outputMessages.push(message);
}
reportProgress(message: ProgressMessage): void {
this.progressMessages.push(message);
}
}
@injectable()
class TestNotificationServiceServer implements NotificationServiceServer {
readonly events: string[] = [];
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
disposeClient(client: NotificationServiceClient): void {
this.events.push('disposeClient:');
}
notifyDidReinitialize(): void {
this.events.push('notifyDidReinitialize:');
}
notifyIndexUpdateWillStart(params: IndexUpdateParams): void {
this.events.push(`notifyIndexUpdateWillStart:${JSON.stringify(params)}`);
}
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void {
this.events.push(
`notifyIndexUpdateDidProgress:${JSON.stringify(progressMessage)}`
);
}
notifyIndexUpdateDidComplete(params: IndexUpdateDidCompleteParams): void {
this.events.push(`notifyIndexUpdateDidComplete:${JSON.stringify(params)}`);
}
notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void {
this.events.push(`notifyIndexUpdateDidFail:${JSON.stringify(params)}`);
}
notifyDaemonDidStart(port: string): void {
this.events.push(`notifyDaemonDidStart:${port}`);
}
notifyDaemonDidStop(): void {
this.events.push('notifyDaemonDidStop:');
}
notifyConfigDidChange(event: ConfigState): void {
this.events.push(`notifyConfigDidChange:${JSON.stringify(event)}`);
}
notifyPlatformDidInstall(event: { item: BoardsPackage }): void {
this.events.push(`notifyPlatformDidInstall:${JSON.stringify(event)}`);
}
notifyPlatformDidUninstall(event: { item: BoardsPackage }): void {
this.events.push(`notifyPlatformDidUninstall:${JSON.stringify(event)}`);
}
notifyLibraryDidInstall(event: {
item: LibraryPackage | 'zip-install';
}): void {
this.events.push(`notifyLibraryDidInstall:${JSON.stringify(event)}`);
}
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void {
this.events.push(`notifyLibraryDidUninstall:${JSON.stringify(event)}`);
}
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void {
this.events.push(`notifyAttachedBoardsDidChange:${JSON.stringify(event)}`);
}
notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void {
this.events.push(`notifyRecentSketchesDidChange:${JSON.stringify(event)}`);
}
dispose(): void {
this.events.push('dispose:');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
setClient(client: NotificationServiceClient | undefined): void {
this.events.push('setClient:');
}
}
@injectable()
class TestBoardDiscovery extends BoardDiscovery {
mutableAvailablePorts: AvailablePorts = {};
override async start(): Promise<void> {
// NOOP
}
override async stop(): Promise<void> {
// NOOP
}
override get availablePorts(): AvailablePorts {
return this.mutableAvailablePorts;
}
}
@injectable()
class TestCommandRegistry extends CommandRegistry {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
readonly executedCommands: [string, any[]][] = [];
override async executeCommand<T>(
commandId: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...args: any[]
): Promise<T | undefined> {
const { token } = new CancellationTokenSource();
this.onWillExecuteCommandEmitter.fire({
commandId,
args,
token,
waitUntil: () => {
// NOOP
},
});
this.executedCommands.push([commandId, args]);
this.onDidExecuteCommandEmitter.fire({ commandId, args });
return undefined;
}
}

View File

@ -107,6 +107,13 @@
`yarn --network-timeout 1000000 --cwd ${join(repoRoot, extension)}`,
`Building and testing ${extension}`
);
exec(
`yarn --network-timeout 1000000 --cwd ${join(
repoRoot,
extension
)} test:slow`,
`Executing slow tests ${extension}`
);
}
//------------------------+
@ -434,12 +441,7 @@ ${fs
* @param {BufferEncoding|undefined} [encoding="base64"]
* @param {object|undefined} [options]
*/
function hashFile(
file,
algorithm = 'sha512',
encoding = 'base64',
options
) {
function hashFile(file, algorithm = 'sha512', encoding = 'base64', options) {
const crypto = require('crypto');
return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm);

View File

@ -48,6 +48,7 @@
"start": "yarn --cwd ./electron-app start",
"watch": "lerna run watch --parallel",
"test": "lerna run test",
"test:slow": "lerna run test:slow",
"download:plugins": "theia download:plugins",
"update:version": "node ./scripts/update-version.js",
"i18n:generate": "theia nls-extract -e vscode -f \"+(arduino-ide-extension|electron-app|plugins)/**/*.ts?(x)\" -o ./i18n/en.json",