From b79f0920d461e0729363fad88e56ee16531aaed7 Mon Sep 17 00:00:00 2001 From: folex <0xdxdy@gmail.com> Date: Sat, 19 Dec 2020 23:59:29 +0300 Subject: [PATCH] JS SDK: fix host functions in mocha tests (#1003) --- package-lock.json | 6 +-- package.json | 6 +-- src/aqua/index.d.ts | 7 +-- src/aqua/index_bg.js | 24 +++++++++- src/fluenceClient.ts | 24 ++++++---- src/stepper.ts | 42 ++++++++-------- src/stepperOutcome.ts | 2 +- src/test/air.spec.ts | 108 ++++++++++++++++++++++++++++++++++++++---- src/test/ast.spec.ts | 2 +- 9 files changed, 168 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a00a518..241c408c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,9 +25,9 @@ } }, "@fluencelabs/aquamarine-stepper": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/@fluencelabs/aquamarine-stepper/-/aquamarine-stepper-0.0.16.tgz", - "integrity": "sha512-yrRMH2ysrxkhOGUe599urPopx6bon44qHppyvE6RKdrE1qVgxYKONWU+BNM+ouzIZ3UW5hoT0gQZ7lmI3HS30g==" + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@fluencelabs/aquamarine-stepper/-/aquamarine-stepper-0.0.21.tgz", + "integrity": "sha512-bw0tdC5+fihzw+BxA02TrNIzMp2reuV21RqPMlDUExh2tbSzHYKBXKOxGsIY10j3QYWpHQZK9N341VnA3nw6Sw==" }, "@sinonjs/commons": { "version": "1.7.2", diff --git a/package.json b/package.json index 9db3f640..183b8afd 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "./dist/fluence.js", "typings": "./dist/fluence.d.ts", "scripts": { - "test": "mocha -r esm -r ts-node/register src/**/*.spec.ts", - "test-ts": "ts-mocha -r esm -p tsconfig.json src/**/*.spec.ts", + "test": "mocha --timeout 10000 -r esm -r ts-node/register src/**/*.spec.ts", + "test-ts": "ts-mocha --timeout 10000 -r esm -p tsconfig.json src/**/*.spec.ts", "package:build": "NODE_ENV=production webpack && npm run package", "package": "tsc && rsync -r src/aqua/*.js dist/aqua", "start": "webpack-dev-server -p", @@ -16,7 +16,7 @@ "author": "Fluence Labs", "license": "Apache-2.0", "dependencies": { - "@fluencelabs/aquamarine-stepper": "0.0.16", + "@fluencelabs/aquamarine-stepper": "0.0.21", "async": "3.2.0", "base64-js": "1.3.1", "bs58": "4.0.1", diff --git a/src/aqua/index.d.ts b/src/aqua/index.d.ts index a586587d..a80675aa 100644 --- a/src/aqua/index.d.ts +++ b/src/aqua/index.d.ts @@ -11,7 +11,8 @@ */ export function invoke(wasm: any, init_user_id: string, aqua: string, prev_data: string, data: string, log_level: string): string; export function ast(wasm: any, script: string): string; +export function return_current_peer_id(wasm: any, peerId: string, arg0: any): void; +export function return_call_service_result(wasm: any, ret: string, arg0: any): void; export function getStringFromWasm0(wasm: any, arg1: any, arg2: any): string -export function getInt32Memory0(wasm: any): number[] -export function passStringToWasm0(wasm: any, arg: any, malloc: any, realloc: any): number -export let WASM_VECTOR_LEN: number; +export function free(wasm: any, ptr: any, len: any): void + diff --git a/src/aqua/index_bg.js b/src/aqua/index_bg.js index ca0063ee..4561bf0e 100644 --- a/src/aqua/index_bg.js +++ b/src/aqua/index_bg.js @@ -36,7 +36,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' }); export function passStringToWasm0(wasm, arg, malloc, realloc) { - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length); @@ -57,8 +56,8 @@ export function passStringToWasm0(wasm, arg, malloc, realloc) { if (code > 0x7F) break; mem[ptr + offset] = code; } - if (offset !== len) { + if (offset !== 0) { arg = arg.slice(offset); } @@ -69,7 +68,9 @@ export function passStringToWasm0(wasm, arg, malloc, realloc) { offset += ret.written; } + WASM_VECTOR_LEN = offset; + return ptr; } @@ -132,3 +133,22 @@ export function ast(wasm, script) { wasm.__wbindgen_free(r0, r1); } } + +export function return_current_peer_id(wasm, peerId, arg0) { + var ptr0 = passStringToWasm0(wasm, peerId, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0(wasm)[arg0 / 4 + 1] = len0; + getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0; +} + +export function return_call_service_result(wasm, ret, arg0) { + var ptr1 = passStringToWasm0(wasm, ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getInt32Memory0(wasm)[arg0 / 4 + 1] = len1; + getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr1; + +} + +export function free(wasm, ptr, len) { + wasm.__wbindgen_free(ptr, len); +} diff --git a/src/fluenceClient.ts b/src/fluenceClient.ts index 218adfe1..72157aea 100644 --- a/src/fluenceClient.ts +++ b/src/fluenceClient.ts @@ -21,12 +21,7 @@ import * as PeerId from "peer-id"; import Multiaddr from "multiaddr" import {FluenceConnection} from "./fluenceConnection"; import {Subscriptions} from "./subscriptions"; -import { - enqueueParticle, - getCurrentParticleId, - popParticle, - setCurrentParticleId -} from "./globalState"; +import {enqueueParticle, getCurrentParticleId, popParticle, setCurrentParticleId} from "./globalState"; import {instantiateInterpreter, InterpreterInvoke} from "./stepper"; import log from "loglevel"; import {waitService} from "./helpers/waitService"; @@ -34,6 +29,8 @@ import {ModuleConfig} from "./moduleConfig"; const bs58 = require('bs58') +const INFO_LOG_LEVEL = 2 + export class FluenceClient { readonly selfPeerId: PeerId; readonly selfPeerIdStr: string; @@ -84,12 +81,21 @@ export class FluenceClient { let stepperOutcomeStr = this.interpreter(particle.init_peer_id, particle.script, JSON.stringify(prevData), JSON.stringify(particle.data)) let stepperOutcome: StepperOutcome = JSON.parse(stepperOutcomeStr); - log.info("inner interpreter outcome:"); - log.info(stepperOutcome); + if (log.getLevel() <= INFO_LOG_LEVEL) { + log.info("inner interpreter outcome:"); + let so = {...stepperOutcome} + try { + so.data = JSON.parse(Buffer.from(so.data).toString("utf8")); + log.info(so); + } catch (e) { + log.info("cannot parse StepperOutcome data as JSON: ", e); + } + } // update data after aquamarine execution let newParticle: Particle = {...particle}; - newParticle.data = JSON.parse(stepperOutcome.call_path) + newParticle.data = stepperOutcome.data + this.subscriptions.update(newParticle) // do nothing if there is no `next_peer_pks` or if client isn't connected to the network diff --git a/src/stepper.ts b/src/stepper.ts index 76d7a69e..acdc406f 100644 --- a/src/stepper.ts +++ b/src/stepper.ts @@ -16,7 +16,7 @@ import {toByteArray} from "base64-js"; import * as aqua from "./aqua" -import {getInt32Memory0, getStringFromWasm0, passStringToWasm0, WASM_VECTOR_LEN} from "./aqua" +import {return_current_peer_id, return_call_service_result, getStringFromWasm0, free} from "./aqua" import {service} from "./service"; import PeerId from "peer-id"; @@ -25,13 +25,13 @@ import {wasmBs64} from "@fluencelabs/aquamarine-stepper"; import Instance = WebAssembly.Instance; import Exports = WebAssembly.Exports; import ExportValue = WebAssembly.ExportValue; -import Imports = WebAssembly.Imports; export type InterpreterInvoke = (init_user_id: string, script: string, prev_data: string, data: string) => string type ImportObject = { "./aquamarine_client_bg.js": { __wbg_callserviceimpl_7d3cf77a2722659e: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any) => void; - __wbg_getcurrentpeeridimpl_154ce1848a306ff5: (arg0: any) => void + __wbg_getcurrentpeeridimpl_154ce1848a306ff5: (arg0: any) => void; + __wbindgen_throw: (arg: any) => void; }; host: LogImport }; @@ -89,7 +89,7 @@ function log_import(cfg: HostImportsConfig): LogImport { log_utf8_string: (level: any, target: any, offset: any, size: any) => { let wasm = cfg.exports; try { - let str = getStringFromWasm0(wasm, offset, size) + let str = getStringFromWasm0(wasm, offset, size); switch (level) { case 1: @@ -106,7 +106,7 @@ function log_import(cfg: HostImportsConfig): LogImport { break; case 5: // we don't want a trace in trace logs - log.debug(str) + log.debug(str) break; } } finally { @@ -127,25 +127,22 @@ function newImportObject(cfg: HostImportsConfig, peerId: PeerId): ImportObject { let serviceId = getStringFromWasm0(wasm, arg1, arg2) let fnName = getStringFromWasm0(wasm, arg3, arg4) let args = getStringFromWasm0(wasm, arg5, arg6); - var ret = service(serviceId, fnName, args); - let retStr = JSON.stringify(ret) - var ptr0 = passStringToWasm0(wasm, retStr, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len0 = WASM_VECTOR_LEN; - getInt32Memory0(wasm)[arg0 / 4 + 1] = len0; - getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0; + let serviceResult = service(serviceId, fnName, args); + let resultStr = JSON.stringify(serviceResult) + return_call_service_result(wasm, resultStr, arg0); } finally { - call_export(wasm.__wbindgen_free, arg1, arg2); - call_export(wasm.__wbindgen_free, arg3, arg4); - call_export(wasm.__wbindgen_free, arg5, arg6); + free(wasm, arg1, arg2) + free(wasm, arg3, arg4) + free(wasm, arg5, arg6) } }, __wbg_getcurrentpeeridimpl_154ce1848a306ff5: (arg0: any) => { + let peerIdStr = peerId.toB58String(); let wasm = cfg.exports; - var ret = peerId.toB58String(); - var ptr0 = passStringToWasm0(wasm, ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len0 = WASM_VECTOR_LEN; - getInt32Memory0(wasm)[arg0 / 4 + 1] = len0; - getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0; + return_current_peer_id(wasm, peerIdStr, arg0); + }, + __wbindgen_throw: (arg: any) => { + console.log(`wbindgen throw: ${JSON.stringify(arg)}`); } }, host: log_import(cfg) @@ -157,7 +154,8 @@ function newLogImport(cfg: HostImportsConfig): ImportObject { host: log_import(cfg), "./aquamarine_client_bg.js": { __wbg_callserviceimpl_7d3cf77a2722659e: _ => {}, - __wbg_getcurrentpeeridimpl_154ce1848a306ff5: _ => {} + __wbg_getcurrentpeeridimpl_154ce1848a306ff5: _ => {}, + __wbindgen_throw: _ => {} }, }; } @@ -165,7 +163,9 @@ function newLogImport(cfg: HostImportsConfig): ImportObject { /// Instantiates AIR interpreter, and returns its `invoke` function as closure /// NOTE: an interpreter is also called a stepper from time to time export async function instantiateInterpreter(peerId: PeerId): Promise { - let cfg = new HostImportsConfig((cfg) => newImportObject(cfg, peerId)) + let cfg = new HostImportsConfig((cfg) => { + return newImportObject(cfg, peerId); + }) let instance = await interpreterInstance(cfg); return (init_user_id: string, script: string, prev_data: string, data: string) => { diff --git a/src/stepperOutcome.ts b/src/stepperOutcome.ts index 7a998062..c6b2de69 100644 --- a/src/stepperOutcome.ts +++ b/src/stepperOutcome.ts @@ -16,6 +16,6 @@ export interface StepperOutcome { ret_code: number, - call_path: string, + data: number[], next_peer_pks: string[] } diff --git a/src/test/air.spec.ts b/src/test/air.spec.ts index ee0ee008..fdf51b6c 100644 --- a/src/test/air.spec.ts +++ b/src/test/air.spec.ts @@ -1,27 +1,115 @@ import 'mocha'; import Fluence from "../fluence"; import {build} from "../particle"; -import { ServiceMultiple } from "../service"; -import { registerService } from "../globalState"; +import {ServiceMultiple} from "../service"; +import {registerService} from "../globalState"; +import {expect} from "chai"; -describe("AIR", () => { - it("call local function", async function () { - let service = new ServiceMultiple("console"); - registerService(service); - service.registerFunction('log', (args: any[]) => { - console.log(`log: ${args}`); +function registerPromiseService(serviceId: string, fnName: string, f: (args: any[]) => T): Promise { + let service = new ServiceMultiple(serviceId); + registerService(service); - return {} + return new Promise((resolve, reject) => { + service.registerFunction(fnName, (args: any[]) => { + resolve(f(args)) + + return {result: f(args)} }) + }) +} + +describe("== AIR suite", () => { + + it("check init_peer_id", async function () { + let serviceId = "init_peer" + let fnName = "id" + let checkPromise = registerPromiseService(serviceId, fnName, (args) => args[0]) let client = await Fluence.local(); - let script = `(call %init_peer_id% ("console" "log") ["hello"])` + let script = `(call %init_peer_id% ("${serviceId}" "${fnName}") [%init_peer_id%])` + + let particle = await build(client.selfPeerId, script, new Map()) + + await client.executeParticle(particle); + + expect(await checkPromise).to.be.equal(client.selfPeerIdStr) + }) + + it("call local function", async function () { + let serviceId = "console" + let fnName = "log" + let checkPromise = registerPromiseService(serviceId, fnName, (args) => args[0]) + + let client = await Fluence.local(); + + let arg = "hello" + let script = `(call %init_peer_id% ("${serviceId}" "${fnName}") ["${arg}"])` // Wrap script into particle, so it can be executed by local WASM runtime let particle = await build(client.selfPeerId, script, new Map()) await client.executeParticle(particle); + + expect(await checkPromise).to.be.equal(arg) + }) + + it("check particle arguments", async function () { + let serviceId = "check" + let fnName = "args" + let checkPromise = registerPromiseService(serviceId, fnName, (args) => args[0]) + + let client = await Fluence.local(); + + let arg = "arg1" + let value = "hello" + let script = `(call %init_peer_id% ("${serviceId}" "${fnName}") [${arg}])` + + + let data = new Map() + data.set("arg1", value) + let particle = await build(client.selfPeerId, script, data) + + await client.executeParticle(particle); + + expect(await checkPromise).to.be.equal(value) + }) + + it("check chain of services work properly", async function () { + this.timeout(5000); + let serviceId1 = "check1" + let fnName1 = "fn1" + let checkPromise1 = registerPromiseService(serviceId1, fnName1, (args) => args[0]) + + let serviceId2 = "check2" + let fnName2 = "fn2" + let checkPromise2 = registerPromiseService(serviceId2, fnName2, (args) => args[0]) + + let serviceId3 = "check3" + let fnName3 = "fn3" + let checkPromise3 = registerPromiseService(serviceId3, fnName3, (args) => args) + + let client = await Fluence.local(); + + let arg1 = "arg1" + let arg2 = "arg2" + + // language=Clojure + let script = `(seq + (seq + (call %init_peer_id% ("${serviceId1}" "${fnName1}") ["${arg1}"] result1) + (call %init_peer_id% ("${serviceId2}" "${fnName2}") ["${arg2}"] result2)) + (call %init_peer_id% ("${serviceId3}" "${fnName3}") [result1 result2])) + ` + + let particle = await build(client.selfPeerId, script, new Map()) + + await client.executeParticle(particle); + + expect(await checkPromise1).to.be.equal(arg1) + expect(await checkPromise2).to.be.equal(arg2) + + expect(await checkPromise3).to.be.deep.equal([{result: arg1}, {result: arg2}]) }) }) diff --git a/src/test/ast.spec.ts b/src/test/ast.spec.ts index 5e90b1cf..12e82994 100644 --- a/src/test/ast.spec.ts +++ b/src/test/ast.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import Fluence from "../fluence"; -describe("AIR AST parsing suite", () => { +describe("== AST parsing suite", () => { it("parse simple script and return ast", async function () { let ast = await Fluence.parseAIR(` (call node ("service" "function") [1 2 3 arg] output)