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
JavaScript
"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