Skip to content

Commit e8d9d2b

Browse files
authored
Add write recording capabilities to the mock bindings (#1249)
- change echo default - add a bunch of logging and serialport serialnumbers
1 parent fb86820 commit e8d9d2b

4 files changed

Lines changed: 49 additions & 16 deletions

File tree

.docs/README.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ Testing is an important feature of any library. To aid in our own tests we've de
330330
const SerialPort = require('serialport/test');
331331
const MockBinding = SerialPort.Binding;
332332

333-
// Create a port and disable the echo.
334-
MockBinding.createPort('/dev/ROBOT', { echo: false });
333+
// Create a port and enable the echo and recording.
334+
MockBinding.createPort('/dev/ROBOT', { echo: true, record: true })
335335
cont port = new SerialPort('/dev/ROBOT')
336336
```
337337

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,8 @@ Testing is an important feature of any library. To aid in our own tests we've de
377377
const SerialPort = require('serialport/test');
378378
const MockBinding = SerialPort.Binding;
379379

380-
// Create a port and disable the echo.
381-
MockBinding.createPort('/dev/ROBOT', { echo: false });
380+
// Create a port and enable the echo and recording.
381+
MockBinding.createPort('/dev/ROBOT', { echo: true, record: true })
382382
cont port = new SerialPort('/dev/ROBOT')
383383
```
384384

examples/mocking.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@ const MockBinding = SerialPort.Binding;
88

99
const portPath = 'COM_ANYTHING';
1010

11-
// By default the mock bindings pretend to be an arduino with the `arduinoEcho` program loaded.
11+
// The mock bindings can pretend to be an arduino with the `arduinoEcho` program loaded.
1212
// This will echo any byte written to the port and will emit "READY" data after opening.
13+
// You enable this by passing `echo: true`
1314

14-
// Create a port and disable the echo.
15-
MockBinding.createPort(portPath, { echo: false });
15+
// Another additional option is `record`, if `record: true` is present all
16+
// writes will be recorded into a single buffer for the lifetime of the port
17+
// it can be read from `port.binding.recording`.
18+
19+
// Create a port
20+
MockBinding.createPort(portPath, { echo: false, record: false });
1621

1722
const port = new SerialPort(portPath);
1823
port.on('open', () => {

lib/bindings/mock.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
'use strict';
2+
const debug = require('debug')('serialport:bindings:mock');
23
const Buffer = require('safe-buffer').Buffer;
34
const BaseBinding = require('./base');
45

56
let ports = {};
7+
let serialNumber = 0;
68

79
function resolveNextTick() {
810
return new Promise(resolve => process.nextTick(resolve));
@@ -15,6 +17,7 @@ class MockBinding extends BaseBinding {
1517
this.isOpen = false;
1618
this.port = null;
1719
this.lastWrite = null;
20+
this.recording = new Buffer(0);
1821
this.pendingWrite = null;
1922
}
2023

@@ -25,25 +28,29 @@ class MockBinding extends BaseBinding {
2528

2629
// Create a mock port
2730
static createPort(path, opt) {
31+
serialNumber++;
2832
opt = Object.assign({
29-
echo: true,
33+
echo: false,
34+
record: false,
3035
readyData: Buffer.from('READY')
3136
}, opt);
3237

3338
ports[path] = {
3439
data: Buffer.alloc(0),
3540
echo: opt.echo,
36-
readyData: opt.readyData,
41+
record: opt.record,
42+
readyData: Buffer.from(opt.readyData),
3743
info: {
3844
comName: path,
3945
manufacturer: 'The J5 Robotics Company',
40-
serialNumber: undefined,
46+
serialNumber,
4147
pnpId: undefined,
4248
locationId: undefined,
4349
vendorId: undefined,
4450
productId: undefined
4551
}
4652
};
53+
debug(serialNumber, 'created port', JSON.stringify({ path, opt }));
4754
}
4855

4956
static list() {
@@ -58,21 +65,27 @@ class MockBinding extends BaseBinding {
5865
if (!this.isOpen) {
5966
throw new Error('Port must be open to pretend to receive data');
6067
}
68+
if (!Buffer.isBuffer(data)) {
69+
data = Buffer.from(data);
70+
}
71+
debug(this.serialNumber, 'emitting data - pending read:', Boolean(this.pendingRead));
6172
this.port.data = Buffer.concat([this.port.data, data]);
62-
6373
if (this.pendingRead) {
6474
process.nextTick(this.pendingRead);
6575
this.pendingRead = null;
6676
}
6777
}
6878

6979
open(path, opt) {
80+
debug(null, `opening path ${path}`);
7081
const port = this.port = ports[path];
7182
return super.open(path, opt)
83+
.then(resolveNextTick)
7284
.then(() => {
7385
if (!port) {
7486
return Promise.reject(new Error(`Port does not exist - please call MockBinding.createPort('${path}') first`));
7587
}
88+
this.serialNumber = port.info.serialNumber;
7689

7790
if (port.openOpt && port.openOpt.lock) {
7891
return Promise.reject(new Error('Port is locked cannot open'));
@@ -84,6 +97,7 @@ class MockBinding extends BaseBinding {
8497

8598
port.openOpt = Object.assign({}, opt);
8699
this.isOpen = true;
100+
debug(this.serialNumber, 'port is open');
87101
if (port.echo) {
88102
process.nextTick(() => {
89103
if (this.isOpen) { this.emitData(port.readyData) }
@@ -94,6 +108,7 @@ class MockBinding extends BaseBinding {
94108

95109
close() {
96110
const port = this.port;
111+
debug(this.serialNumber, 'closing port');
97112
if (!port) {
98113
return Promise.reject(new Error('close'));
99114
}
@@ -104,12 +119,15 @@ class MockBinding extends BaseBinding {
104119
// reset data on close
105120
port.data = Buffer.alloc(0);
106121

122+
debug(this.serialNumber, 'port is closed');
107123
delete this.port;
124+
delete this.serialNumber;
108125
this.isOpen = false;
109126
});
110127
}
111128

112129
read(buffer, offset, length) {
130+
debug(this.serialNumber, 'reading', length, 'bytes');
113131
return super.read(buffer, offset, length)
114132
.then(resolveNextTick())
115133
.then(() => {
@@ -124,42 +142,52 @@ class MockBinding extends BaseBinding {
124142
const data = this.port.data.slice(0, length);
125143
const readLength = data.copy(buffer, offset);
126144
this.port.data = this.port.data.slice(length);
127-
145+
debug(this.serialNumber, 'read', readLength, 'bytes');
128146
return readLength;
129147
});
130148
}
131149

132150
write(buffer) {
151+
debug(this.serialNumber, 'writing');
152+
if (this.pendingWrite) {
153+
throw new Error('Overlapping writes are not supported and should be queued by the serialport object');
154+
}
133155
this.pendingWrite = super.write(buffer)
134-
.then(resolveNextTick())
156+
.then(resolveNextTick)
135157
.then(() => {
136158
if (!this.isOpen) {
137159
throw new Error('Write canceled');
138160
}
139161
const data = this.lastWrite = Buffer.from(buffer); // copy
162+
if (this.port.record) {
163+
this.recording = Buffer.concat([this.recording, data]);
164+
}
140165
if (this.port.echo) {
141166
process.nextTick(() => {
142167
if (this.isOpen) { this.emitData(data) }
143168
});
144169
}
145170
this.pendingWrite = null;
171+
debug(this.serialNumber, 'writing finished');
146172
});
147173
return this.pendingWrite;
148174
}
149175

150176
update(opt) {
151177
return super.update(opt)
178+
.then(resolveNextTick)
152179
.then(() => {
153180
this.port.openOpt.baudRate = opt.baudRate;
154181
});
155182
}
156183

157184
set(opt) {
158-
return super.set(opt);
185+
return super.set(opt).then(resolveNextTick);
159186
}
160187

161188
get() {
162189
return super.get()
190+
.then(resolveNextTick)
163191
.then(() => {
164192
return {
165193
cts: true,
@@ -171,10 +199,10 @@ class MockBinding extends BaseBinding {
171199

172200
flush() {
173201
return super.flush()
202+
.then(resolveNextTick)
174203
.then(() => {
175204
this.port.data = Buffer.alloc(0);
176-
})
177-
.then(resolveNextTick());
205+
});
178206
}
179207

180208
drain() {

0 commit comments

Comments
 (0)