Skip to content

Commit

Permalink
Add write recording capabilities to the mock bindings (#1249)
Browse files Browse the repository at this point in the history
- change echo default
- add a bunch of logging and serialport serialnumbers
  • Loading branch information
reconbot committed Jul 22, 2017
1 parent fb86820 commit e8d9d2b
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .docs/README.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,8 @@ Testing is an important feature of any library. To aid in our own tests we've de
const SerialPort = require('serialport/test');
const MockBinding = SerialPort.Binding;

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

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ Testing is an important feature of any library. To aid in our own tests we've de
const SerialPort = require('serialport/test');
const MockBinding = SerialPort.Binding;

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

Expand Down
11 changes: 8 additions & 3 deletions examples/mocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ const MockBinding = SerialPort.Binding;

const portPath = 'COM_ANYTHING';

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

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

// Create a port
MockBinding.createPort(portPath, { echo: false, record: false });

const port = new SerialPort(portPath);
port.on('open', () => {
Expand Down
46 changes: 37 additions & 9 deletions lib/bindings/mock.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use strict';
const debug = require('debug')('serialport:bindings:mock');
const Buffer = require('safe-buffer').Buffer;
const BaseBinding = require('./base');

let ports = {};
let serialNumber = 0;

function resolveNextTick() {
return new Promise(resolve => process.nextTick(resolve));
Expand All @@ -15,6 +17,7 @@ class MockBinding extends BaseBinding {
this.isOpen = false;
this.port = null;
this.lastWrite = null;
this.recording = new Buffer(0);
this.pendingWrite = null;
}

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

// Create a mock port
static createPort(path, opt) {
serialNumber++;
opt = Object.assign({
echo: true,
echo: false,
record: false,
readyData: Buffer.from('READY')
}, opt);

ports[path] = {
data: Buffer.alloc(0),
echo: opt.echo,
readyData: opt.readyData,
record: opt.record,
readyData: Buffer.from(opt.readyData),
info: {
comName: path,
manufacturer: 'The J5 Robotics Company',
serialNumber: undefined,
serialNumber,
pnpId: undefined,
locationId: undefined,
vendorId: undefined,
productId: undefined
}
};
debug(serialNumber, 'created port', JSON.stringify({ path, opt }));
}

static list() {
Expand All @@ -58,21 +65,27 @@ class MockBinding extends BaseBinding {
if (!this.isOpen) {
throw new Error('Port must be open to pretend to receive data');
}
if (!Buffer.isBuffer(data)) {
data = Buffer.from(data);
}
debug(this.serialNumber, 'emitting data - pending read:', Boolean(this.pendingRead));
this.port.data = Buffer.concat([this.port.data, data]);

if (this.pendingRead) {
process.nextTick(this.pendingRead);
this.pendingRead = null;
}
}

open(path, opt) {
debug(null, `opening path ${path}`);
const port = this.port = ports[path];
return super.open(path, opt)
.then(resolveNextTick)
.then(() => {
if (!port) {
return Promise.reject(new Error(`Port does not exist - please call MockBinding.createPort('${path}') first`));
}
this.serialNumber = port.info.serialNumber;

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

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

close() {
const port = this.port;
debug(this.serialNumber, 'closing port');
if (!port) {
return Promise.reject(new Error('close'));
}
Expand All @@ -104,12 +119,15 @@ class MockBinding extends BaseBinding {
// reset data on close
port.data = Buffer.alloc(0);

debug(this.serialNumber, 'port is closed');
delete this.port;
delete this.serialNumber;
this.isOpen = false;
});
}

read(buffer, offset, length) {
debug(this.serialNumber, 'reading', length, 'bytes');
return super.read(buffer, offset, length)
.then(resolveNextTick())
.then(() => {
Expand All @@ -124,42 +142,52 @@ class MockBinding extends BaseBinding {
const data = this.port.data.slice(0, length);
const readLength = data.copy(buffer, offset);
this.port.data = this.port.data.slice(length);

debug(this.serialNumber, 'read', readLength, 'bytes');
return readLength;
});
}

write(buffer) {
debug(this.serialNumber, 'writing');
if (this.pendingWrite) {
throw new Error('Overlapping writes are not supported and should be queued by the serialport object');
}
this.pendingWrite = super.write(buffer)
.then(resolveNextTick())
.then(resolveNextTick)
.then(() => {
if (!this.isOpen) {
throw new Error('Write canceled');
}
const data = this.lastWrite = Buffer.from(buffer); // copy
if (this.port.record) {
this.recording = Buffer.concat([this.recording, data]);
}
if (this.port.echo) {
process.nextTick(() => {
if (this.isOpen) { this.emitData(data) }
});
}
this.pendingWrite = null;
debug(this.serialNumber, 'writing finished');
});
return this.pendingWrite;
}

update(opt) {
return super.update(opt)
.then(resolveNextTick)
.then(() => {
this.port.openOpt.baudRate = opt.baudRate;
});
}

set(opt) {
return super.set(opt);
return super.set(opt).then(resolveNextTick);
}

get() {
return super.get()
.then(resolveNextTick)
.then(() => {
return {
cts: true,
Expand All @@ -171,10 +199,10 @@ class MockBinding extends BaseBinding {

flush() {
return super.flush()
.then(resolveNextTick)
.then(() => {
this.port.data = Buffer.alloc(0);
})
.then(resolveNextTick());
});
}

drain() {
Expand Down

0 comments on commit e8d9d2b

Please sign in to comment.