Skip to content

Commit 4135a18

Browse files
author
Alberto Iannaccone
authored
add unit tests for monitor utils (#522)
* wip tests * test monitor utils
1 parent e550c8d commit 4135a18

File tree

6 files changed

+307
-133
lines changed

6 files changed

+307
-133
lines changed

Diff for: arduino-ide-extension/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"download": "^7.1.0",
101101
"grpc_tools_node_protoc_ts": "^4.1.0",
102102
"mocha": "^7.0.0",
103+
"mockdate": "^3.0.5",
103104
"moment": "^2.24.0",
104105
"protoc": "^1.0.4",
105106
"shelljs": "^0.8.3",

Diff for: arduino-ide-extension/src/browser/monitor/monitor-connection.ts

+117-120
Original file line numberDiff line numberDiff line change
@@ -71,129 +71,15 @@ export class MonitorConnection {
7171

7272
@postConstruct()
7373
protected init(): void {
74-
this.monitorServiceClient.onMessage(async (port) => {
75-
const w = new WebSocket(`ws://localhost:${port}`);
76-
w.onmessage = (res) => {
77-
const messages = JSON.parse(res.data);
78-
this.onReadEmitter.fire({ messages });
79-
};
80-
});
81-
82-
// let i = 0;
83-
// this.monitorServiceClient.onMessage(async (msg) => {
84-
// // msg received
85-
// i++;
86-
// if (i % 1000 === 0) {
87-
// // console.log(msg);
88-
// }
89-
// });
90-
91-
this.monitorServiceClient.onError(async (error) => {
92-
let shouldReconnect = false;
93-
if (this.state) {
94-
const { code, config } = error;
95-
const { board, port } = config;
96-
const options = { timeout: 3000 };
97-
switch (code) {
98-
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
99-
console.debug(
100-
`Connection was canceled by client: ${MonitorConnection.State.toString(
101-
this.state
102-
)}.`
103-
);
104-
break;
105-
}
106-
case MonitorError.ErrorCodes.DEVICE_BUSY: {
107-
this.messageService.warn(
108-
`Connection failed. Serial port is busy: ${Port.toString(port)}.`,
109-
options
110-
);
111-
shouldReconnect = this.autoConnect;
112-
this.monitorErrors.push(error);
113-
break;
114-
}
115-
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
116-
this.messageService.info(
117-
`Disconnected ${Board.toString(board, {
118-
useFqbn: false,
119-
})} from ${Port.toString(port)}.`,
120-
options
121-
);
122-
break;
123-
}
124-
case undefined: {
125-
this.messageService.error(
126-
`Unexpected error. Reconnecting ${Board.toString(
127-
board
128-
)} on port ${Port.toString(port)}.`,
129-
options
130-
);
131-
console.error(JSON.stringify(error));
132-
shouldReconnect = this.connected && this.autoConnect;
133-
break;
134-
}
135-
}
136-
const oldState = this.state;
137-
this.state = undefined;
138-
this.onConnectionChangedEmitter.fire(this.state);
139-
if (shouldReconnect) {
140-
if (this.monitorErrors.length >= 10) {
141-
this.messageService.warn(
142-
`Failed to reconnect ${Board.toString(board, {
143-
useFqbn: false,
144-
})} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(
145-
port
146-
)} serial port is busy. after 10 consecutive attempts.`
147-
);
148-
this.monitorErrors.length = 0;
149-
} else {
150-
const attempts = this.monitorErrors.length || 1;
151-
if (this.reconnectTimeout !== undefined) {
152-
// Clear the previous timer.
153-
window.clearTimeout(this.reconnectTimeout);
154-
}
155-
const timeout = attempts * 1000;
156-
this.messageService.warn(
157-
`Reconnecting ${Board.toString(board, {
158-
useFqbn: false,
159-
})} to ${Port.toString(port)} in ${attempts} seconds...`,
160-
{ timeout }
161-
);
162-
this.reconnectTimeout = window.setTimeout(
163-
() => this.connect(oldState.config),
164-
timeout
165-
);
166-
}
167-
}
168-
}
169-
});
74+
this.monitorServiceClient.onMessage(this.handleMessage.bind(this));
75+
this.monitorServiceClient.onError(this.handleError.bind(this));
17076
this.boardsServiceProvider.onBoardsConfigChanged(
17177
this.handleBoardConfigChange.bind(this)
17278
);
173-
this.notificationCenter.onAttachedBoardsChanged((event) => {
174-
if (this.autoConnect && this.connected) {
175-
const { boardsConfig } = this.boardsServiceProvider;
176-
if (
177-
this.boardsServiceProvider.canUploadTo(boardsConfig, {
178-
silent: false,
179-
})
180-
) {
181-
const { attached } = AttachedBoardsChangeEvent.diff(event);
182-
if (
183-
attached.boards.some(
184-
(board) =>
185-
!!board.port && BoardsConfig.Config.sameAs(boardsConfig, board)
186-
)
187-
) {
188-
const { selectedBoard: board, selectedPort: port } = boardsConfig;
189-
const { baudRate } = this.monitorModel;
190-
this.disconnect().then(() =>
191-
this.connect({ board, port, baudRate })
192-
);
193-
}
194-
}
195-
}
196-
});
79+
this.notificationCenter.onAttachedBoardsChanged(
80+
this.handleAttachedBoardsChanged.bind(this)
81+
);
82+
19783
// Handles the `baudRate` changes by reconnecting if required.
19884
this.monitorModel.onChange(({ property }) => {
19985
if (property === 'baudRate' && this.autoConnect && this.connected) {
@@ -203,6 +89,14 @@ export class MonitorConnection {
20389
});
20490
}
20591

92+
async handleMessage(port: string): Promise<void> {
93+
const w = new WebSocket(`ws://localhost:${port}`);
94+
w.onmessage = (res) => {
95+
const messages = JSON.parse(res.data);
96+
this.onReadEmitter.fire({ messages });
97+
};
98+
}
99+
206100
get connected(): boolean {
207101
return !!this.state;
208102
}
@@ -234,6 +128,109 @@ export class MonitorConnection {
234128
}
235129
}
236130

131+
handleError(error: MonitorError): void {
132+
let shouldReconnect = false;
133+
if (this.state) {
134+
const { code, config } = error;
135+
const { board, port } = config;
136+
const options = { timeout: 3000 };
137+
switch (code) {
138+
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
139+
console.debug(
140+
`Connection was canceled by client: ${MonitorConnection.State.toString(
141+
this.state
142+
)}.`
143+
);
144+
break;
145+
}
146+
case MonitorError.ErrorCodes.DEVICE_BUSY: {
147+
this.messageService.warn(
148+
`Connection failed. Serial port is busy: ${Port.toString(port)}.`,
149+
options
150+
);
151+
shouldReconnect = this.autoConnect;
152+
this.monitorErrors.push(error);
153+
break;
154+
}
155+
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
156+
this.messageService.info(
157+
`Disconnected ${Board.toString(board, {
158+
useFqbn: false,
159+
})} from ${Port.toString(port)}.`,
160+
options
161+
);
162+
break;
163+
}
164+
case undefined: {
165+
this.messageService.error(
166+
`Unexpected error. Reconnecting ${Board.toString(
167+
board
168+
)} on port ${Port.toString(port)}.`,
169+
options
170+
);
171+
console.error(JSON.stringify(error));
172+
shouldReconnect = this.connected && this.autoConnect;
173+
break;
174+
}
175+
}
176+
const oldState = this.state;
177+
this.state = undefined;
178+
this.onConnectionChangedEmitter.fire(this.state);
179+
if (shouldReconnect) {
180+
if (this.monitorErrors.length >= 10) {
181+
this.messageService.warn(
182+
`Failed to reconnect ${Board.toString(board, {
183+
useFqbn: false,
184+
})} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(
185+
port
186+
)} serial port is busy. after 10 consecutive attempts.`
187+
);
188+
this.monitorErrors.length = 0;
189+
} else {
190+
const attempts = this.monitorErrors.length || 1;
191+
if (this.reconnectTimeout !== undefined) {
192+
// Clear the previous timer.
193+
window.clearTimeout(this.reconnectTimeout);
194+
}
195+
const timeout = attempts * 1000;
196+
this.messageService.warn(
197+
`Reconnecting ${Board.toString(board, {
198+
useFqbn: false,
199+
})} to ${Port.toString(port)} in ${attempts} seconds...`,
200+
{ timeout }
201+
);
202+
this.reconnectTimeout = window.setTimeout(
203+
() => this.connect(oldState.config),
204+
timeout
205+
);
206+
}
207+
}
208+
}
209+
}
210+
211+
handleAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
212+
if (this.autoConnect && this.connected) {
213+
const { boardsConfig } = this.boardsServiceProvider;
214+
if (
215+
this.boardsServiceProvider.canUploadTo(boardsConfig, {
216+
silent: false,
217+
})
218+
) {
219+
const { attached } = AttachedBoardsChangeEvent.diff(event);
220+
if (
221+
attached.boards.some(
222+
(board) =>
223+
!!board.port && BoardsConfig.Config.sameAs(boardsConfig, board)
224+
)
225+
) {
226+
const { selectedBoard: board, selectedPort: port } = boardsConfig;
227+
const { baudRate } = this.monitorModel;
228+
this.disconnect().then(() => this.connect({ board, port, baudRate }));
229+
}
230+
}
231+
}
232+
}
233+
237234
async connect(config: MonitorConfig): Promise<Status> {
238235
if (this.connected) {
239236
const disconnectStatus = await this.disconnect();

Diff for: arduino-ide-extension/src/browser/monitor/monitor-utils.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { Line, SerialMonitorOutput } from './serial-monitor-send-output';
22

3-
export function messageToLines(
3+
export function messagesToLines(
44
messages: string[],
5-
prevLines: Line[],
5+
prevLines: Line[] = [],
6+
charCount = 0,
67
separator = '\n'
78
): [Line[], number] {
89
const linesToAdd: Line[] = prevLines.length
910
? [prevLines[prevLines.length - 1]]
1011
: [{ message: '', lineLen: 0 }];
11-
let charCount = 0;
1212

1313
for (const message of messages) {
1414
const messageLen = message.length;
@@ -38,11 +38,12 @@ export function messageToLines(
3838

3939
export function truncateLines(
4040
lines: Line[],
41-
charCount: number
41+
charCount: number,
42+
maxCharacters: number = SerialMonitorOutput.MAX_CHARACTERS
4243
): [Line[], number] {
43-
let charsToDelete = charCount - SerialMonitorOutput.MAX_CHARACTERS;
44+
let charsToDelete = charCount - maxCharacters;
4445
let lineIndex = 0;
45-
while (charsToDelete > 0) {
46+
while (charsToDelete > 0 || lineIndex > 0) {
4647
const firstLineLength = lines[lineIndex]?.lineLen;
4748

4849
if (charsToDelete >= firstLineLength) {
@@ -55,6 +56,7 @@ export function truncateLines(
5556

5657
// delete all previous lines
5758
lines.splice(0, lineIndex);
59+
lineIndex = 0;
5860

5961
const newFirstLine = lines[0]?.message?.substring(charsToDelete);
6062
const deletedCharsCount = firstLineLength - newFirstLine.length;

Diff for: arduino-ide-extension/src/browser/monitor/serial-monitor-send-output.tsx

+5-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { areEqual, FixedSizeList as List } from 'react-window';
55
import { MonitorModel } from './monitor-model';
66
import { MonitorConnection } from './monitor-connection';
77
import dateFormat = require('dateformat');
8-
import { messageToLines, truncateLines } from './monitor-utils';
8+
import { messagesToLines, truncateLines } from './monitor-utils';
99

1010
export type Line = { message: string; timestamp?: Date; lineLen: number };
1111

@@ -59,14 +59,12 @@ export class SerialMonitorOutput extends React.Component<
5959
this.scrollToBottom();
6060
this.toDisposeBeforeUnmount.pushAll([
6161
this.props.monitorConnection.onRead(({ messages }) => {
62-
const [newLines, charsToAddCount] = messageToLines(
62+
const [newLines, totalCharCount] = messagesToLines(
6363
messages,
64-
this.state.lines
65-
);
66-
const [lines, charCount] = truncateLines(
67-
newLines,
68-
this.state.charCount + charsToAddCount
64+
this.state.lines,
65+
this.state.charCount
6966
);
67+
const [lines, charCount] = truncateLines(newLines, totalCharCount);
7068

7169
this.setState({
7270
lines,

0 commit comments

Comments
 (0)