UNPKG

microvium

Version:

A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.

244 lines (236 loc) 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const native_vm_1 = require("../../lib/native-vm"); const utils_1 = require("../../lib/utils"); const chai_1 = require("chai"); const runtime_types_1 = require("../../lib/runtime-types"); const native_vm_friendly_1 = require("../../lib/native-vm-friendly"); const common_1 = require("../common"); suite('native-api', function () { test('mvm_typeOf', () => { const snapshot = (0, common_1.compileJs) ` const values = [ undefined, null, true, false, 42, -42, 0xffffffff, 1.5, '', 'hello', 'length', '__proto__', { prop: 42 }, [1, 2, 3], () => 'hey', ] for (let i = 0; i < values.length; i++) { let index = i; vmExport(index, () => values[index]) } `; const expectedTypes = [ runtime_types_1.mvm_TeType.VM_T_UNDEFINED, runtime_types_1.mvm_TeType.VM_T_NULL, runtime_types_1.mvm_TeType.VM_T_BOOLEAN, runtime_types_1.mvm_TeType.VM_T_BOOLEAN, runtime_types_1.mvm_TeType.VM_T_NUMBER, runtime_types_1.mvm_TeType.VM_T_NUMBER, runtime_types_1.mvm_TeType.VM_T_NUMBER, runtime_types_1.mvm_TeType.VM_T_NUMBER, runtime_types_1.mvm_TeType.VM_T_STRING, runtime_types_1.mvm_TeType.VM_T_STRING, runtime_types_1.mvm_TeType.VM_T_STRING, runtime_types_1.mvm_TeType.VM_T_STRING, runtime_types_1.mvm_TeType.VM_T_OBJECT, runtime_types_1.mvm_TeType.VM_T_ARRAY, runtime_types_1.mvm_TeType.VM_T_FUNCTION, // () => 'hey', ]; const vm = new native_vm_1.NativeVM(snapshot.data, () => (0, utils_1.unexpected)()); for (const [i, expectedTypeCode] of expectedTypes.entries()) { const f = vm.resolveExport(i); const value = vm.call(f, []); const typeCode = vm.typeOf(value); chai_1.assert.equal(typeCode, expectedTypeCode); } }); test('object manipulation', () => { const snapshot = (0, common_1.compileJs) ` const newObject = () => ({}); const getProp = (o, p) => o[p]; const setProp = (o, p, v) => o[p] = v; const readX = o => o.x; vmExport(1, newObject); vmExport(2, getProp); vmExport(3, setProp); vmExport(4, Reflect.ownKeys); vmExport(5, readX); `; const vm = new native_vm_1.NativeVM(snapshot.data, () => (0, utils_1.unexpected)()); const newObject_ = vm.resolveExport(1); const getProp_ = vm.resolveExport(2); const setProp_ = vm.resolveExport(3); const objectKeys_ = vm.resolveExport(4); const readX_ = vm.resolveExport(5); const newObject = () => vm.call(newObject_, []); const getProp = (o, p) => vm.call(getProp_, [o, vm.newString(p)]); const setProp = (o, p, v) => vm.call(setProp_, [o, vm.newString(p), v]); const objectKeys = (o) => vm.call(objectKeys_, [o]); const getIndex = (arr, i) => vm.call(getProp_, [arr, vm.newNumber(i)]); const readX = (obj) => vm.call(readX_, [obj]); // Note: the node bindings for Value // ([Value.cc](../../native-vm-bindings/Value.cc)) wraps a Microvium GC // handle. If you were doing this in C, you would need to create and release // the handles yourself. // myObject1 = { x: 5, y: 6 } const myObject1 = newObject(); setProp(myObject1, 'x', vm.newNumber(5)); setProp(myObject1, 'y', vm.newNumber(6)); // myObject2 = { x: 'hello', y: myObject1 } const myObject2 = newObject(); setProp(myObject2, 'x', vm.newString('hello')); setProp(myObject2, 'y', myObject1); // object1Keys = Reflect.ownKeys(myObject1) const object1Keys = objectKeys(myObject1); // assert.equal(myObject1.x, 5) chai_1.assert.equal(getProp(myObject1, 'x').toNumber(), 5); // assert.equal(myObject1.y, 6) chai_1.assert.equal(getProp(myObject1, 'y').toNumber(), 6); // assert.equal(myObject2.x, 'hello') chai_1.assert.equal(getProp(myObject2, 'x').toString(), 'hello'); // assert.equal(myObject2.y.x, 5) chai_1.assert.equal(getProp(getProp(myObject2, 'y'), 'x').toNumber(), 5); // assert.equal(object1Keys.length, 2) chai_1.assert.equal(getProp(object1Keys, 'length').toNumber(), 2); // assert.equal(object1Keys.length, 2) chai_1.assert.equal(getIndex(object1Keys, 0).toString(), 'x'); chai_1.assert.equal(getIndex(object1Keys, 1).toString(), 'y'); // The readX function reads the x property using a literal key in the script // itself, which may have different interning characteristics to reading // using an externally-provided key (although it shouldn't). chai_1.assert.equal(readX(myObject1).toNumber(), 5); }); test('array manipulation', () => { const snapshot = (0, common_1.compileJs) ` const newArray = () => []; const getItem = (a, i) => a[i]; const setItem = (a, i, v) => a[i] = v; const arrayLength = (a) => a.length; vmExport(1, newArray); vmExport(2, getItem); vmExport(3, setItem); vmExport(4, arrayLength); `; const vm = new native_vm_1.NativeVM(snapshot.data, () => (0, utils_1.unexpected)()); const newArray_ = vm.resolveExport(1); const getProp_ = vm.resolveExport(2); const setProp_ = vm.resolveExport(3); const arrayLength_ = vm.resolveExport(4); const newArray = () => vm.call(newArray_, []); const getItem = (a, i) => vm.call(getProp_, [a, vm.newNumber(i)]); const setItem = (a, i, v) => vm.call(setProp_, [a, vm.newNumber(i), v]); const arrayLength = (a) => vm.call(arrayLength_, [a]).toNumber(); function copyByteArrayToJS(sourceArr) { const targetArr = newArray(); for (let i = 0; i < sourceArr.length; i++) { setItem(targetArr, i, vm.newNumber(sourceArr[i])); } return targetArr; } function copyByteArrayFromJS(sourceArr) { const result = []; const len = arrayLength(sourceArr); for (let i = 0; i < len; i++) { result[i] = getItem(sourceArr, i).toNumber(); } return result; } const receivedData = [1, 2, 3]; const jsReceivedData = copyByteArrayToJS(receivedData); // Loop back, just for example const sendData = copyByteArrayFromJS(jsReceivedData); chai_1.assert.deepEqual(sendData, receivedData); }); test('uint8array', () => { const snapshot = (0, common_1.compileJs) ` vmExport(1, incrementBuffer) function incrementBuffer(buffer) { for (let i = 0; i < buffer.length; i++) { buffer[i] = (buffer[i] + 1) & 0xFF; } return buffer; } `; const vm = new native_vm_1.NativeVM(snapshot.data, () => (0, utils_1.unexpected)()); const incrementBuffer_ = vm.resolveExport(1); const incrementBuffer = (a) => vm.call(incrementBuffer_, [vm.uint8ArrayFromBytes(a)]).uint8ArrayToBytes(); const myData = Buffer.from([1, 2, 254, 255]); const incremented = incrementBuffer(myData); // The data is passed by value (copy), so the original shouldn't change chai_1.assert.deepEqual(myData, Buffer.from([1, 2, 254, 255])); chai_1.assert.deepEqual(incremented, Buffer.from([2, 3, 255, 0])); }); test('invoke-gc-from-closure', () => { const snapshot = (0, common_1.compileJs) ` const runGC = vmImport(1); vmExport(1, runTest); function runTest() { // Create some garbage to be collected []; // Create a closure. The scope for this closure will be allocated after // the above garbage, which means it will move during a GC cycle as the // heap is compacted. const closure = createClosure(); // Run the closure, which calls the host to trigger a GC cycle. Since // the GC is triggered while the closure is active, the 'closure' // register will point to the closure, which moves. This tests that // nothing breaks if the current closure moves during a GC cycle during // a call to the host. closure(); } function createClosure(x) { return () => { x; // Force this func to be a closure runGC(); }; } `; const vm = new native_vm_friendly_1.NativeVMFriendly(snapshot, { 1: () => vm.garbageCollect() }); vm.resolveExport(1)(); }); test('mvm_stopAfterNInstructions', () => { const snapshot = (0, common_1.compileJs) ` vmExport(1, () => { for (let i = 0; i < 50; i++) {} }) `; const vm = new native_vm_1.NativeVM(snapshot.data, () => (0, utils_1.unexpected)()); const f = vm.resolveExport(1); vm.stopAfterNInstructions(1000); chai_1.assert.equal(vm.getInstructionCountRemaining(), 1000); vm.call(f, []); chai_1.assert.equal(vm.getInstructionCountRemaining(), 340); let err; // Calling `f` again will trigger the error try { vm.call(f, []); } catch (e) { err = e; } chai_1.assert.equal(err.message, "The instruction count set by `mvm_stopAfterNInstructions` has been reached"); chai_1.assert.equal(vm.getInstructionCountRemaining(), 0); err = undefined; // the counter doesn't reset try { vm.call(f, []); } catch (e) { err = e; } chai_1.assert.equal(err.message, "The instruction count set by `mvm_stopAfterNInstructions` has been reached"); }); }); //# sourceMappingURL=native-api.test.js.map