Skip to content

Commit

Permalink
serial-service tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fstasi committed Dec 7, 2021
1 parent 7d10e89 commit 9d2d8ee
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 65 deletions.
10 changes: 6 additions & 4 deletions arduino-ide-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
},
"dependencies": {
"arduino-serial-plotter-webapp": "0.0.15",
"@grpc/grpc-js": "^1.3.7",
"@theia/application-package": "1.19.0",
"@theia/core": "1.19.0",
"@theia/editor": "1.19.0",
"@theia/editor-preview": "1.19.0",
"@theia/editor-preview": "1.19.0",
"@theia/filesystem": "1.19.0",
"@theia/git": "1.19.0",
"@theia/keymaps": "1.19.0",
Expand Down Expand Up @@ -53,10 +52,10 @@
"@types/ps-tree": "^1.1.0",
"@types/react-select": "^3.0.0",
"@types/react-tabs": "^2.3.2",
"@types/sinon": "^7.5.2",
"@types/temp": "^0.8.34",
"@types/which": "^1.3.1",
"ajv": "^6.5.3",
"arduino-serial-plotter-webapp": "0.0.15",
"async-mutex": "^0.3.0",
"atob": "^2.1.2",
"auth0-js": "^9.14.0",
Expand Down Expand Up @@ -97,6 +96,8 @@
"@types/chai-string": "^1.4.2",
"@types/mocha": "^5.2.7",
"@types/react-window": "^1.8.5",
"@types/sinon": "^10.0.6",
"@types/sinon-chai": "^3.2.6",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
"decompress": "^4.2.0",
Expand All @@ -109,7 +110,8 @@
"moment": "^2.24.0",
"protoc": "^1.0.4",
"shelljs": "^0.8.3",
"sinon": "^9.0.1",
"sinon": "^12.0.1",
"sinon-chai": "^3.7.0",
"typemoq": "^2.1.0",
"uuid": "^3.2.1",
"yargs": "^11.1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ export class SerialConnectionManager {
this.messageService.error(
`Please select a board and a port to open the serial connection.`
);
return;
}

if (!this.webSocket && this.wsPort) {
Expand Down
69 changes: 44 additions & 25 deletions arduino-ide-extension/src/node/serial/serial-service-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,6 @@ namespace ErrorWithCode {

@injectable()
export class SerialServiceImpl implements SerialService {
@named(SerialServiceName)
@inject(ILogger)
protected readonly logger: ILogger;

@inject(MonitorClientProvider)
protected readonly serialClientProvider: MonitorClientProvider;

@inject(WebSocketService)
protected readonly webSocketService: WebSocketService;

protected theiaFEClient?: SerialServiceClient;
protected serialConfig?: SerialConfig;

Expand All @@ -88,6 +78,18 @@ export class SerialServiceImpl implements SerialService {

uploadInProgress = false;

constructor(
@inject(ILogger)
@named(SerialServiceName)
protected readonly logger: ILogger,

@inject(MonitorClientProvider)
protected readonly serialClientProvider: MonitorClientProvider,

@inject(WebSocketService)
protected readonly webSocketService: WebSocketService
) {}

async isSerialPortOpen(): Promise<boolean> {
return !!this.serialConnection;
}
Expand Down Expand Up @@ -115,7 +117,6 @@ export class SerialServiceImpl implements SerialService {
public async connectSerialIfRequired(): Promise<void> {
if (this.uploadInProgress) return;
const clients = await this.clientsAttached();
this.logger.info(`WS clients: ${clients}`);
clients > 0 ? await this.connect() : await this.disconnect();
}

Expand Down Expand Up @@ -144,7 +145,7 @@ export class SerialServiceImpl implements SerialService {
this.webSocketService.sendMessage(JSON.stringify(msg));
}

async connect(): Promise<Status> {
private async connect(): Promise<Status> {
if (!this.serialConfig) {
return Status.CONFIG_MISSING;
}
Expand All @@ -155,8 +156,6 @@ export class SerialServiceImpl implements SerialService {
)} on port ${Port.toString(this.serialConfig.port)}...`
);

// check if the board/port is available

if (this.serialConnection) {
return Status.ALREADY_CONNECTED;
}
Expand Down Expand Up @@ -187,7 +186,6 @@ export class SerialServiceImpl implements SerialService {
// Log the original, unexpected error.
this.logger.error(error);
}
// });
}).bind(this)
);

Expand Down Expand Up @@ -259,9 +257,19 @@ export class SerialServiceImpl implements SerialService {
}
req.setConfig(monitorConfig);

return new Promise<Status>((resolve) => {
if (this.serialConnection) {
this.serialConnection.duplex.write(req, () => {
if (!this.serialConnection) {
return await this.disconnect();
}

const writeTimeout = new Promise<Status>((resolve) => {
setTimeout(async () => {
resolve(Status.NOT_CONNECTED);
}, 1000);
});

const writePromise = (serialConnection: any) => {
return new Promise<Status>((resolve) => {
serialConnection.duplex.write(req, () => {
const boardName = this.serialConfig?.board
? Board.toString(this.serialConfig.board, {
useFqbn: false,
Expand All @@ -276,14 +284,23 @@ export class SerialServiceImpl implements SerialService {
);
resolve(Status.OK);
});
return;
}
this.disconnect().then(() => resolve(Status.NOT_CONNECTED));
});
});
};

const status = await Promise.race([
writeTimeout,
writePromise(this.serialConnection),
]);

if (status === Status.NOT_CONNECTED) {
this.disconnect();
}

return status;
}

public async disconnect(reason?: SerialError): Promise<Status> {
return new Promise<Status>((resolve, reject) => {
return new Promise<Status>((resolve) => {
try {
if (this.onMessageReceived) {
this.onMessageReceived.dispose();
Expand All @@ -299,12 +316,14 @@ export class SerialServiceImpl implements SerialService {
reason &&
reason.code === SerialError.ErrorCodes.CLIENT_CANCEL
) {
return Status.OK;
resolve(Status.OK);
return;
}
this.logger.info('>>> Disposing serial connection...');
if (!this.serialConnection) {
this.logger.warn('<<< Not connected. Nothing to dispose.');
return Status.NOT_CONNECTED;
resolve(Status.NOT_CONNECTED);
return;
}
const { duplex, config } = this.serialConnection;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@
// return { dispose: () => {} };
// });

// serialServiceClient
// .setup((mock) => mock.onWebSocketChanged(It.isAny()))
// .returns((h) => {
// handleWebSocketChanged = h;
// return { dispose: () => {} };
// });

// serialService
// .setup((m) => m.disconnect())
// .returns(() => Promise.resolve(Status.OK));
Expand Down
166 changes: 166 additions & 0 deletions arduino-ide-extension/src/test/node/serial-service-impl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { SerialServiceImpl } from './../../node/serial/serial-service-impl';
import { IMock, It, Mock } from 'typemoq';
import { createSandbox } from 'sinon';
import * as sinonChai from 'sinon-chai';
import { expect, use } from 'chai';
use(sinonChai);

import { ILogger } from '@theia/core/lib/common/logger';
import { MonitorClientProvider } from '../../node/serial/monitor-client-provider';
import { WebSocketService } from '../../node/web-socket/web-socket-service';
import { MonitorServiceClient } from '../../node/cli-protocol/cc/arduino/cli/monitor/v1/monitor_grpc_pb';

describe.only('SerialServiceImpl', () => {
let subject: SerialServiceImpl;

let logger: IMock<ILogger>;
let serialClientProvider: IMock<MonitorClientProvider>;
let webSocketService: IMock<WebSocketService>;

beforeEach(() => {
logger = Mock.ofType<ILogger>();
logger.setup((b) => b.info(It.isAnyString()));
logger.setup((b) => b.warn(It.isAnyString()));
logger.setup((b) => b.error(It.isAnyString()));

serialClientProvider = Mock.ofType<MonitorClientProvider>();
webSocketService = Mock.ofType<WebSocketService>();

subject = new SerialServiceImpl(
logger.object,
serialClientProvider.object,
webSocketService.object
);
});

// context('when a serial connection is requested', () => {
// const sandbox = createSandbox();
// beforeEach(() => {
// subject.uploadInProgress = false;
// sandbox.spy(subject, 'disconnect');
// sandbox.spy(subject, 'updateWsConfigParam');
// });

// afterEach(function () {
// sandbox.restore();
// });

// context('and an upload is in progress', () => {
// beforeEach(async () => {
// subject.uploadInProgress = true;
// });

// it('should not change the connection status', async () => {
// await subject.connectSerialIfRequired();
// expect(subject.disconnect).to.have.callCount(0);
// });
// });

// context('and there is no upload in progress', () => {
// beforeEach(async () => {
// subject.uploadInProgress = false;
// });

// context('and there are 0 attached ws clients', () => {
// it('should disconnect', async () => {
// await subject.connectSerialIfRequired();
// expect(subject.disconnect).to.have.been.calledOnce;
// });
// });

// context('and there are > 0 attached ws clients', () => {
// beforeEach(() => {
// webSocketService
// .setup((b) => b.getConnectedClientsNumber())
// .returns(() => 1);
// });

// it('should not call the disconenct', async () => {
// await subject.connectSerialIfRequired();
// expect(subject.disconnect).to.have.callCount(0);
// });
// });
// });
// });

// context('when a disconnection is requested', () => {
// const sandbox = createSandbox();
// beforeEach(() => {});

// afterEach(function () {
// sandbox.restore();
// });

// context('and a serialConnection is not set', () => {
// it('should return a NOT_CONNECTED status', async () => {
// const status = await subject.disconnect();
// expect(status).to.be.equal(Status.NOT_CONNECTED);
// });
// });

// context('and a serialConnection is set', async () => {
// beforeEach(async () => {
// sandbox.spy(subject, 'updateWsConfigParam');
// await subject.disconnect();
// });

// it('should dispose the serialConnection', async () => {
// const serialConnectionOpen = await subject.isSerialPortOpen();
// expect(serialConnectionOpen).to.be.false;
// });

// it('should call updateWsConfigParam with disconnected status', async () => {
// expect(subject.updateWsConfigParam).to.be.calledWith({
// connected: false,
// });
// });
// });
// });

context('when a new config is passed in', () => {
const sandbox = createSandbox();
beforeEach(async () => {
subject.uploadInProgress = false;
webSocketService
.setup((b) => b.getConnectedClientsNumber())
.returns(() => 1);

serialClientProvider
.setup((b) => b.client())
.returns(async () => {
return {
streamingOpen: () => {
return {
on: (str: string, cb: any) => {},
write: (chunk: any, cb: any) => {
cb();
},
cancel: () => {},
};
},
} as MonitorServiceClient;
});

sandbox.spy(subject, 'disconnect');

await subject.setSerialConfig({
board: { name: 'test' },
port: { address: 'test', protocol: 'test' },
});
});

afterEach(function () {
sandbox.restore();
subject.dispose();
});

// it('should disconnect from previous connection', async () => {
// expect(subject.disconnect).to.be.called;
// });

it('should create the serialConnection', async () => {
const serialConnectionOpen = await subject.isSerialPortOpen();
expect(serialConnectionOpen).to.be.true;
});
});
});
Loading

0 comments on commit 9d2d8ee

Please sign in to comment.