From 64c75640ba6548cd8e68469b2ef738b5601b9848 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 11 Mar 2021 19:02:42 -0600 Subject: [PATCH] ble-pybricks-service: add some tests --- src/ble-pybricks-service/protocol.test.ts | 10 ++ src/ble-pybricks-service/sagas.test.ts | 146 ++++++++++++++++++++++ src/error-log/sagas.test.ts | 11 ++ 3 files changed, 167 insertions(+) create mode 100644 src/ble-pybricks-service/protocol.test.ts create mode 100644 src/ble-pybricks-service/sagas.test.ts diff --git a/src/ble-pybricks-service/protocol.test.ts b/src/ble-pybricks-service/protocol.test.ts new file mode 100644 index 00000000..c730585c --- /dev/null +++ b/src/ble-pybricks-service/protocol.test.ts @@ -0,0 +1,10 @@ +import { Status, statusToFlag } from './protocol'; + +describe('status flags should fit in 32 bits', () => { + test.each(Object.values(Status).filter((x) => typeof x === 'number'))( + '%s', + (status) => { + expect(Math.log2(statusToFlag(status as Status))).toBe(status); + }, + ); +}); diff --git a/src/ble-pybricks-service/sagas.test.ts b/src/ble-pybricks-service/sagas.test.ts new file mode 100644 index 00000000..8487e067 --- /dev/null +++ b/src/ble-pybricks-service/sagas.test.ts @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2021 The Pybricks Authors + +import { AsyncSaga } from '../../test'; +import { + didFailToSendCommand, + didFailToWriteCommand, + didNotifyEvent, + didSendCommand, + didWriteCommand, + eventProtocolError, + sendStopUserProgramCommand, + statusReportEvent, + writeCommand, +} from './actions'; +import { CommandType, ProtocolError } from './protocol'; +import blePybricksService from './sagas'; + +describe('command encoder', () => { + test.each([ + [ + 'stop user program', + sendStopUserProgramCommand(0), + [ + 0x00, // stop user program command + ], + ], + ])('encode %s request', async (_n, request, expected) => { + const saga = new AsyncSaga(blePybricksService); + saga.put(request); + const message = new Uint8Array(expected); + const action = await saga.take(); + expect(action).toEqual(writeCommand(0, message)); + await saga.end(); + }); + + test('commands are serialized', async () => { + const saga = new AsyncSaga(blePybricksService); + + // we send 4 commands + saga.put(sendStopUserProgramCommand(0)); + saga.put(sendStopUserProgramCommand(1)); + saga.put(sendStopUserProgramCommand(2)); + saga.put(sendStopUserProgramCommand(3)); + + // but only two didSendCommand actions meaning only the first two completed + saga.put(didWriteCommand(0)); + saga.put(didWriteCommand(1)); + + // So only 3 commands were actually sent and two didSendCommand were + // dispatched (making 5 total dispatches). The last request is still + // buffered and has not been dispatched. + const numPending = saga.numPending(); + expect(numPending).toEqual(5); + + const message = new Uint8Array([CommandType.StopUserProgram]); + + // every other action is the "write command" action + // and the interleaving actions are "did send command" actions + const action0 = await saga.take(); + expect(action0).toEqual(writeCommand(0, message)); + const action1 = await saga.take(); + expect(action1).toEqual(didSendCommand(0)); + const action2 = await saga.take(); + expect(action2).toEqual(writeCommand(1, message)); + const action3 = await saga.take(); + expect(action3).toEqual(didSendCommand(1)); + const action4 = await saga.take(); + expect(action4).toEqual(writeCommand(2, message)); + + await saga.end(); + }); + + test('fail to send triggers fail to write', async () => { + const saga = new AsyncSaga(blePybricksService); + + saga.put(sendStopUserProgramCommand(0)); + + const message = new Uint8Array([0x00]); + const action1 = await saga.take(); + expect(action1).toEqual(writeCommand(0, message)); + + const err = new Error('test error'); + saga.put(didFailToWriteCommand(0, err)); + + const action2 = await saga.take(); + expect(action2).toEqual(didFailToSendCommand(0, err)); + + await saga.end(); + }); +}); + +describe('event decoder', () => { + test.each([ + [ + 'status report', + [ + 0x00, // status report event + 0x01, // flags count LSB + 0x00, // . + 0x00, // . + 0x00, // flags count MSB + ], + statusReportEvent(0x00000001), + ], + ])('decode %s event', async (_n, message, expected) => { + const saga = new AsyncSaga(blePybricksService); + const notification = new Uint8Array(message); + + saga.put(didNotifyEvent(new DataView(notification.buffer))); + + const action = await saga.take(); + expect(action).toEqual(expected); + + await saga.end(); + }); + + test.each([ + [ + 'unknown event', + [ + 0xff, // **bad event** + 0x01, // **junk** + 0x02, // **junk** + 0x03, // **junk** + 0x04, // **junk** + ], + eventProtocolError( + new ProtocolError( + 'unknown pybricks event type: 0xff', + new DataView(new Uint8Array().buffer), + ), + ), + ], + ])('protocol error', async (_n, message, expected) => { + const saga = new AsyncSaga(blePybricksService); + const notification = new Uint8Array(message); + + saga.put(didNotifyEvent(new DataView(notification.buffer))); + + const action = await saga.take(); + expect(action).toEqual(expected); + + await saga.end(); + }); +}); diff --git a/src/error-log/sagas.test.ts b/src/error-log/sagas.test.ts index a9b1b589..d980bb03 100644 --- a/src/error-log/sagas.test.ts +++ b/src/error-log/sagas.test.ts @@ -2,6 +2,7 @@ // Copyright (c) 2020 The Pybricks Authors import { AsyncSaga } from '../../test'; +import { eventProtocolError } from '../ble-pybricks-service/actions'; import { didFailToWrite } from '../ble-uart/actions'; import { BleDeviceFailToConnectReasonType, @@ -46,6 +47,16 @@ test('bleDataDidFailToWrite', async () => { await saga.end(); }); +test('eventProtocolError', async () => { + const saga = new AsyncSaga(errorLog); + + console.error = jest.fn(); + saga.put(eventProtocolError(new Error('test error'))); + expect(console.error).toHaveBeenCalledTimes(1); + + await saga.end(); +}); + test('bootloaderDidFailToConnect', async () => { const saga = new AsyncSaga(errorLog);