UNPKG

rc-js-util

Version:

A collection of TS and C++ utilities to help writing performant and correct applications, achieved through strict typing and (removable) invariant checking.

138 lines 7.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SanitizedEmscriptenTestModule = exports.getEmscriptenTestModuleOptions = void 0; const tslib_1 = require("tslib"); const get_wasm_test_memory_js_1 = require("../util/get-wasm-test-memory.js"); const get_emscripten_wrapper_js_1 = require("./get-emscripten-wrapper.js"); const _fp_js_1 = require("../../fp/_fp.js"); const _production_js_1 = require("../../production/_production.js"); const test_reset_life_cycle_js_1 = require("../../test-util/test_reset-life-cycle.js"); const _debug_js_1 = require("../../debug/_debug.js"); const reference_counted_strategy_js_1 = require("./reference-counted-strategy.js"); const emscriptenTestModuleOptions = { disabledErrors: { // looks like asan writes to stderr regardless of option... startsWith: ["==42==WARNING: AddressSanitizer failed to allocate 0xfffffff"], }, initialMemoryPages: 128, maxMemoryPages: 8192, quitThrowsWith: {}, shared: false, }; /** * @public * Provides "sensible" options for a {@link SanitizedEmscriptenTestModule}. */ function getEmscriptenTestModuleOptions(overrides) { return Object.assign(Object.assign({}, emscriptenTestModuleOptions), overrides); } exports.getEmscriptenTestModuleOptions = getEmscriptenTestModuleOptions; /** * @public * A wrapper for running emscripten modules built with ASAN. To see the full report, you must call `endEmscriptenProgram`. * @typeParam TEmscriptenBindings - The generated WASM bindings. * @typeParam TWrapperExtensions - Extensions to the test wrapper itself, i.e. overwrite the module with test specific logic. */ class SanitizedEmscriptenTestModule { constructor(testModule, options, extension) { this.testModule = testModule; this.options = options; this.extension = extension; this.state = { currentDisabledErrors: {}, loggedErrors: new Array(), }; this.state.currentDisabledErrors = this.options.disabledErrors || {}; } initialize() { return tslib_1.__awaiter(this, void 0, void 0, function* () { (0, test_reset_life_cycle_js_1.Test_resetLifeCycle)(); this.state.loggedErrors.length = 0; const memory = (0, get_wasm_test_memory_js_1.getWasmTestMemory)({ initial: this.options.initialMemoryPages, maximum: this.options.maxMemoryPages, shared: this.options.shared, }); const options = this.options; const state = this.state; // avoid referencing `this` in closures, it prevents the test module from being gc'd in debug builds this._wrapper = (yield (0, get_emscripten_wrapper_js_1.getEmscriptenWrapper)(memory, this.testModule, new reference_counted_strategy_js_1.ReferenceCountedStrategy(), new get_emscripten_wrapper_js_1.EmscriptenWrapperOptions([]), Object.assign({ ASAN_OPTIONS: "allocator_may_return_null=1", print: _fp_js_1._Fp.noOp, printErr: (error) => { if (isErrorExcluded(state.currentDisabledErrors, error)) { _debug_js_1._Debug.verboseLog(["WASM"], "Ignoring logged error by exclusion:\n" + error); return; } state.loggedErrors.push(error); _debug_js_1._Debug.logError(error); }, // legacy handler (this doesn't appear to be used anymore...) quit: () => { // emscripten hits asserts if quit doesn't interrupt execution // the default behavior in node is to kill the process which would kill the tests // by throwing something unique we can catch but avoid swallowing actual errors throw options.quitThrowsWith; }, onExit: (statusCode) => { // noinspection SuspiciousTypeOfGuard - we want to know if they change the API if (typeof statusCode !== "number") { throw _production_js_1._Production.createError("Unsupported emscripten version, expected to get a status code..."); } if (statusCode === 0) { // we could just use their object, but for simplicity we normalize to "the old way" throw options.quitThrowsWith; } // there's a non-zero status code, emscripten is pretty good at reporting this, so let them do it... } }, this.extension))); // -sEXIT_RUNTIME=1 does not play well with threads + no main, manually keep the runtime alive this.wrapper.instance.runtimeKeepalivePush(); }); } get wrapper() { if (this._wrapper == null) { throw _production_js_1._Production.createError("initialize must be called first"); } return this._wrapper; } /** * NB you MUST set the flag `-s EXIT_RUNTIME=1` for this to work. */ endEmscriptenProgram(errorLoggingAllowed = false) { const idStore = this.wrapper.interopIds; if (idStore.idBuffer != null) { this.wrapper.rootNode.getLinked().unlink(idStore.idBuffer.resourceHandle); } this.runWithDisabledErrors(Object.assign(Object.assign({}, this.state.currentDisabledErrors), this.options.disabledShutdownErrors), () => { // kick off asan checks try { this.wrapper.instance.runtimeKeepalivePop(); this.wrapper.instance._jsUtilEndProgram(0); } catch (error) { if (error !== emscriptenTestModuleOptions.quitThrowsWith) { throw error; } } }); if (this.state.loggedErrors.length > 0 && !errorLoggingAllowed) { const messages = ["The C++ logged an error to the console, this is considered a failure.", "Reprinting errors:"].concat(this.state.loggedErrors); throw _production_js_1._Production.createError(messages.join("\n")); } this.wrapper.rootNode.getLinked().unlinkAll(); } runWithDisabledErrors(exclusions, callback) { const original = this.state.currentDisabledErrors; this.state.currentDisabledErrors = Object.assign(Object.assign({}, this.state.currentDisabledErrors), exclusions); callback(); this.state.currentDisabledErrors = original; } } exports.SanitizedEmscriptenTestModule = SanitizedEmscriptenTestModule; function isErrorExcluded(disabledErrors, error) { let excluded = false; if (disabledErrors.exactMatch) { excluded = disabledErrors.exactMatch.some(x => x === error); } if (!excluded && disabledErrors.startsWith) { excluded = disabledErrors.startsWith.some(x => error.startsWith(x)); } return excluded; } //# sourceMappingURL=sanitized-emscripten-test-module.js.map