add basic flash-firmware sagas tests

So far just testing the success paths, no errors.
This commit is contained in:
David Lechner
2021-01-21 12:37:29 -06:00
parent 9d7845237c
commit 6f6bf26854
4 changed files with 478 additions and 1 deletions
+2
View File
@@ -19,6 +19,7 @@
"@testing-library/user-event": "^12.6.0",
"@types/file-saver": "^2.0.1",
"@types/jest": "^25.2.3",
"@types/jszip": "^3.4.1",
"@types/node": "^12.0.0",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
@@ -29,6 +30,7 @@
"@types/zen-push": "^0.1.1",
"ace-builds": "^1.4.12",
"file-saver": "^2.0.5",
"jszip": "^3.5.0",
"license-webpack-plugin": "^2.3.11",
"node-sass": "^4.14.1",
"prop-types": "^15.7.2",
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`flashFirmware normal flow 1`] = `
Object {
"options": Array [
"-mno-unicode",
],
"script": "print(\\"test\\")",
"type": "mpy.action.compile",
}
`;
exports[`flashFirmware user supplied firmware.zip 1`] = `
Object {
"options": Array [
"-mno-unicode",
],
"script": "print(\\"test\\")",
"type": "mpy.action.compile",
}
`;
exports[`flashFirmware user supplied main.py 1`] = `
Object {
"options": Array [
"-mno-unicode",
],
"script": "print(\\"test\\")",
"type": "mpy.action.compile",
}
`;
+437
View File
@@ -0,0 +1,437 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
import { FirmwareMetadata } from '@pybricks/firmware';
import JSZip from 'jszip';
import { AsyncSaga } from '../../test';
import {
didFinish,
didProgress,
didStart,
flashFirmware as flashFirmwareAction,
} from '../actions/flash-firmware';
import {
BootloaderProgramRequestAction,
checksumRequest,
checksumResponse,
connect,
didConnect,
didRequest,
eraseRequest,
eraseResponse,
infoRequest,
infoResponse,
initRequest,
initResponse,
programRequest,
programResponse,
rebootRequest,
} from '../actions/lwp3-bootloader';
import { didCompile } from '../actions/mpy';
import { HubType, Result } from '../protocols/lwp3-bootloader';
import { EditorState } from '../reducers/editor';
import { SettingsState } from '../reducers/settings';
import { createCountFunc } from '../utils/iter';
import flashFirmware from './flash-firmware';
afterEach(() => {
jest.restoreAllMocks();
});
describe('flashFirmware', () => {
test('normal flow', async () => {
const metadata: FirmwareMetadata = {
'metadata-version': '1.0.0',
'device-id': HubType.MoveHub,
'checksum-type': 'sum',
'firmware-version': '1.2.3',
'max-firmware-size': 1024,
'mpy-abi-version': 5,
'mpy-cross-options': ['-mno-unicode'],
'user-mpy-offset': 100,
};
const zip = new JSZip();
zip.file('firmware-base.bin', new Uint8Array(64));
zip.file('firmware.metadata.json', JSON.stringify(metadata));
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
jest.spyOn(window, 'fetch').mockResolvedValueOnce(
new Response(await zip.generateAsync({ type: 'blob' })),
);
const saga = new AsyncSaga(flashFirmware, { nextMessageId: createCountFunc() });
saga.setState({ settings: { flashCurrentProgram: false } as SettingsState });
// saga is triggered by this action
saga.put(flashFirmwareAction());
// first step is to connect to the hub bootloader
let action = await saga.take();
expect(action).toEqual(connect());
saga.put(didConnect());
// then find out what kind of hub it is
action = await saga.take();
expect(action).toEqual(infoRequest(0));
saga.put(didRequest(0));
saga.put(infoResponse(0x01000000, 0x08005000, 0x081f800, HubType.MoveHub));
// then compile main.py to .mpy
action = await saga.take();
expect(action).toMatchSnapshot();
const mpySize = 20;
const mpyBinaryData = new Uint8Array(mpySize);
saga.put(didCompile(mpyBinaryData));
// then start flashing the firmware
// should get didStart action just before starting to erase
action = await saga.take();
expect(action).toEqual(didStart());
// erase first
action = await saga.take();
expect(action).toEqual(eraseRequest(1));
saga.put(didRequest(1));
saga.put(eraseResponse(Result.OK));
// then write the new firmware
const totalFirmwareSize = metadata['user-mpy-offset'] + mpySize + 8;
action = await saga.take();
expect(action).toEqual(initRequest(2, totalFirmwareSize));
saga.put(didRequest(2));
saga.put(initResponse(Result.OK));
const dummyPayload = new ArrayBuffer(0);
let id = 2;
for (let count = 1, offset = 0; ; count++, offset += 14) {
action = await saga.take();
expect(action).toEqual(
programRequest(++id, 0x08005000 + offset, dummyPayload),
);
expect((action as BootloaderProgramRequestAction).payload.byteLength).toBe(
Math.min(14, totalFirmwareSize - offset),
);
saga.put(didRequest(id));
action = await saga.take();
expect(action).toEqual(didProgress(offset / totalFirmwareSize));
// Have to be careful that a checksum request is not sent after
// last payload is sent, otherwise the hub gets confused.
if (offset + 14 >= totalFirmwareSize) {
break;
}
if (count % 10 === 0) {
action = await saga.take();
expect(action).toEqual(checksumRequest(++id));
saga.put(didRequest(id));
saga.put(checksumResponse(0));
}
}
// hub indicates success
saga.put(programResponse(0, totalFirmwareSize));
action = await saga.take();
expect(action).toEqual(didProgress(1));
// and finally reboot the hub
action = await saga.take();
expect(action).toEqual(rebootRequest(++id));
saga.put(didRequest(id));
// then we are done
action = await saga.take();
expect(action).toEqual(didFinish());
await saga.end();
});
test('user supplied firmware.zip', async () => {
const metadata: FirmwareMetadata = {
'metadata-version': '1.0.0',
'device-id': HubType.MoveHub,
'checksum-type': 'sum',
'firmware-version': '1.2.3',
'max-firmware-size': 1024,
'mpy-abi-version': 5,
'mpy-cross-options': ['-mno-unicode'],
'user-mpy-offset': 100,
};
const zip = new JSZip();
zip.file('firmware-base.bin', new Uint8Array(64));
zip.file('firmware.metadata.json', JSON.stringify(metadata));
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
const saga = new AsyncSaga(flashFirmware, { nextMessageId: createCountFunc() });
saga.setState({ settings: { flashCurrentProgram: false } as SettingsState });
// saga is triggered by this action
saga.put(flashFirmwareAction(await zip.generateAsync({ type: 'arraybuffer' })));
// the first step is to compile main.py to .mpy
let action = await saga.take();
expect(action).toMatchSnapshot();
const mpySize = 20;
const mpyBinaryData = new Uint8Array(mpySize);
saga.put(didCompile(mpyBinaryData));
// then connect to the hub bootloader
action = await saga.take();
expect(action).toEqual(connect());
saga.put(didConnect());
// then find out what kind of hub it is
action = await saga.take();
expect(action).toEqual(infoRequest(0));
saga.put(didRequest(0));
saga.put(infoResponse(0x01000000, 0x08005000, 0x081f800, HubType.MoveHub));
// then start flashing the firmware
// should get didStart action just before starting to erase
action = await saga.take();
expect(action).toEqual(didStart());
// erase first
action = await saga.take();
expect(action).toEqual(eraseRequest(1));
saga.put(didRequest(1));
saga.put(eraseResponse(Result.OK));
// then write the new firmware
const totalFirmwareSize = metadata['user-mpy-offset'] + mpySize + 8;
action = await saga.take();
expect(action).toEqual(initRequest(2, totalFirmwareSize));
saga.put(didRequest(2));
saga.put(initResponse(Result.OK));
const dummyPayload = new ArrayBuffer(0);
let id = 2;
for (let count = 1, offset = 0; ; count++, offset += 14) {
action = await saga.take();
expect(action).toEqual(
programRequest(++id, 0x08005000 + offset, dummyPayload),
);
expect((action as BootloaderProgramRequestAction).payload.byteLength).toBe(
Math.min(14, totalFirmwareSize - offset),
);
saga.put(didRequest(id));
action = await saga.take();
expect(action).toEqual(didProgress(offset / totalFirmwareSize));
// Have to be careful that a checksum request is not sent after
// last payload is sent, otherwise the hub gets confused.
if (offset + 14 >= totalFirmwareSize) {
break;
}
if (count % 10 === 0) {
action = await saga.take();
expect(action).toEqual(checksumRequest(++id));
saga.put(didRequest(id));
saga.put(checksumResponse(0));
}
}
// hub indicates success
saga.put(programResponse(0, totalFirmwareSize));
action = await saga.take();
expect(action).toEqual(didProgress(1));
// and finally reboot the hub
action = await saga.take();
expect(action).toEqual(rebootRequest(++id));
saga.put(didRequest(id));
// then we are done
action = await saga.take();
expect(action).toEqual(didFinish());
await saga.end();
});
test('user supplied main.py', async () => {
const metadata: FirmwareMetadata = {
'metadata-version': '1.0.0',
'device-id': HubType.MoveHub,
'checksum-type': 'sum',
'firmware-version': '1.2.3',
'max-firmware-size': 1024,
'mpy-abi-version': 5,
'mpy-cross-options': ['-mno-unicode'],
'user-mpy-offset': 100,
};
const zip = new JSZip();
zip.file('firmware-base.bin', new Uint8Array(64));
zip.file('firmware.metadata.json', JSON.stringify(metadata));
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
jest.spyOn(window, 'fetch').mockResolvedValueOnce(
new Response(await zip.generateAsync({ type: 'blob' })),
);
const editor = {
getValue: () => 'print("test")',
};
const saga = new AsyncSaga(flashFirmware, { nextMessageId: createCountFunc() });
saga.setState({
editor: { current: editor } as EditorState,
settings: { flashCurrentProgram: true } as SettingsState,
});
// saga is triggered by this action
saga.put(flashFirmwareAction());
// first step is to connect to the hub bootloader
let action = await saga.take();
expect(action).toEqual(connect());
saga.put(didConnect());
// then find out what kind of hub it is
action = await saga.take();
expect(action).toEqual(infoRequest(0));
saga.put(didRequest(0));
saga.put(infoResponse(0x01000000, 0x08005000, 0x081f800, HubType.MoveHub));
// then compile main.py to .mpy
action = await saga.take();
expect(action).toMatchSnapshot();
const mpySize = 20;
const mpyBinaryData = new Uint8Array(mpySize);
saga.put(didCompile(mpyBinaryData));
// then start flashing the firmware
// should get didStart action just before starting to erase
action = await saga.take();
expect(action).toEqual(didStart());
// erase first
action = await saga.take();
expect(action).toEqual(eraseRequest(1));
saga.put(didRequest(1));
saga.put(eraseResponse(Result.OK));
// then write the new firmware
const totalFirmwareSize = metadata['user-mpy-offset'] + mpySize + 8;
action = await saga.take();
expect(action).toEqual(initRequest(2, totalFirmwareSize));
saga.put(didRequest(2));
saga.put(initResponse(Result.OK));
const dummyPayload = new ArrayBuffer(0);
let id = 2;
for (let count = 1, offset = 0; ; count++, offset += 14) {
action = await saga.take();
expect(action).toEqual(
programRequest(++id, 0x08005000 + offset, dummyPayload),
);
expect((action as BootloaderProgramRequestAction).payload.byteLength).toBe(
Math.min(14, totalFirmwareSize - offset),
);
saga.put(didRequest(id));
action = await saga.take();
expect(action).toEqual(didProgress(offset / totalFirmwareSize));
// Have to be careful that a checksum request is not sent after
// last payload is sent, otherwise the hub gets confused.
if (offset + 14 >= totalFirmwareSize) {
break;
}
if (count % 10 === 0) {
action = await saga.take();
expect(action).toEqual(checksumRequest(++id));
saga.put(didRequest(id));
saga.put(checksumResponse(0));
}
}
// hub indicates success
saga.put(programResponse(0, totalFirmwareSize));
action = await saga.take();
expect(action).toEqual(didProgress(1));
// and finally reboot the hub
action = await saga.take();
expect(action).toEqual(rebootRequest(++id));
saga.put(didRequest(id));
// then we are done
action = await saga.take();
expect(action).toEqual(didFinish());
await saga.end();
});
});
+8 -1
View File
@@ -1935,6 +1935,13 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/jszip@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@types/jszip/-/jszip-3.4.1.tgz#e7a4059486e494c949ef750933d009684227846f"
integrity sha512-TezXjmf3lj+zQ651r6hPqvSScqBLvyPI9FxdXBqpEwBijNGQ2NXpaFW/7joGzveYkKQUil7iiDHLo6LV71Pc0A==
dependencies:
jszip "*"
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@@ -7389,7 +7396,7 @@ jsprim@^1.2.2:
array-includes "^3.1.2"
object.assign "^4.1.2"
jszip@^3.5.0:
jszip@*, jszip@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6"
integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==