blob: ddc9f254131807d983040e49b9b1bff84b7dcba2 [file] [log] [blame]
/*
* Copyright 2017 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
let testNum = (function() {
let count = 1;
return function() {
return `#${count++} `;
}
})();
// WPT's assert_throw uses a list of predefined, hardcoded known errors. Since
// it is not aware of the WebAssembly error types (yet), implement our own
// version.
function assertThrows(func, err) {
let caught = false;
try {
func();
} catch(e) {
assert_true(e instanceof err, `expected ${err.name}, observed ${e.constructor.name}`);
caught = true;
}
assert_true(caught, testNum() + "assertThrows must catch any error.")
}
/******************************************************************************
***************************** WAST HARNESS ************************************
******************************************************************************/
// For assertions internal to our test harness.
function _assert(x) {
if (!x) {
throw new Error(`Assertion failure: ${x}`);
}
}
// A simple sum type that can either be a valid Value or an Error.
function Result(type, maybeValue) {
this.value = maybeValue;
this.type = type;
};
Result.VALUE = 'VALUE';
Result.ERROR = 'ERROR';
function ValueResult(val) { return new Result(Result.VALUE, val); }
function ErrorResult(err) { return new Result(Result.ERROR, err); }
Result.prototype.isError = function() { return this.type === Result.ERROR; }
const EXPECT_INVALID = false;
/* DATA **********************************************************************/
let soft_validate = true;
let $$;
// Default imports.
var registry = {};
// Resets the registry between two different WPT tests.
function reinitializeRegistry() {
if (typeof WebAssembly === 'undefined')
return;
registry = {
spectest: {
print: print,
global: 666,
table: new WebAssembly.Table({initial: 10, maximum: 20, element: 'funcref'}),
memory: new WebAssembly.Memory({initial: 1, maximum: 2})
}
};
}
reinitializeRegistry();
/* WAST POLYFILL *************************************************************/
function binary(bytes) {
let buffer = new ArrayBuffer(bytes.length);
let view = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; ++i) {
view[i] = bytes.charCodeAt(i);
}
return buffer;
}
/**
* Returns a compiled module, or throws if there was an error at compilation.
*/
function module(bytes, valid = true) {
let buffer = binary(bytes);
let validated;
try {
validated = WebAssembly.validate(buffer);
} catch (e) {
throw new Error(`WebAssembly.validate throws ${typeof e}: ${e}${e.stack}`);
}
if (validated !== valid) {
// Try to get a more precise error message from the WebAssembly.CompileError.
let err = '';
try {
new WebAssembly.Module(buffer);
} catch (e) {
if (e instanceof WebAssembly.CompileError)
throw new WebAssembly.CompileError(`WebAssembly.validate error: ${e.toString()}${e.stack}\n`);
else
throw new Error(`WebAssembly.validate throws ${typeof e}: ${e}${e.stack}`);
}
throw new Error(`WebAssembly.validate was expected to fail, but didn't`);
}
let module;
try {
module = new WebAssembly.Module(buffer);
} catch(e) {
if (valid)
throw new Error('WebAssembly.Module ctor unexpectedly throws ${typeof e}: ${e}${e.stack}');
throw e;
}
return module;
}
function uniqueTest(func, desc) {
test(func, testNum() + desc);
}
function assert_invalid(bytes) {
uniqueTest(() => {
try {
module(bytes, /* valid */ false);
throw new Error('did not fail');
} catch(e) {
assert_true(e instanceof WebAssembly.CompileError, "expected invalid failure:");
}
}, "A wast module that should be invalid or malformed.");
}
const assert_malformed = assert_invalid;
function assert_soft_invalid(bytes) {
uniqueTest(() => {
try {
module(bytes, /* valid */ soft_validate);
if (soft_validate)
throw new Error('did not fail');
} catch(e) {
if (soft_validate)
assert_true(e instanceof WebAssembly.CompileError, "expected soft invalid failure:");
}
}, "A wast module that *could* be invalid under certain engines.");
}
function instance(bytes, imports = registry, valid = true) {
if (imports instanceof Result) {
if (imports.isError())
return imports;
imports = imports.value;
}
let err = null;
let m, i;
try {
let m = module(bytes);
i = new WebAssembly.Instance(m, imports);
} catch(e) {
err = e;
}
if (valid) {
uniqueTest(() => {
let instantiated = err === null;
if (!instantiated) print(err);
assert_true(instantiated, err);
}, "module successfully instantiated");
}
return err !== null ? ErrorResult(err) : ValueResult(i);
}
function register(name, instance) {
_assert(instance instanceof Result);
if (instance.isError())
return;
registry[name] = instance.value.exports;
}
function call(instance, name, args) {
_assert(instance instanceof Result);
if (instance.isError())
return instance;
let err = null;
let result;
try {
result = instance.value.exports[name](...args);
} catch(e) {
err = e;
}
return err !== null ? ErrorResult(err) : ValueResult(result);
};
function get(instance, name) {
_assert(instance instanceof Result);
if (instance.isError())
return instance;
return ValueResult(instance.value.exports[name]);
}
function exports(name, instance) {
_assert(instance instanceof Result);
if (instance.isError())
return instance;
return ValueResult({ [name]: instance.value.exports });
}
function run(action) {
print("running new test: " + (new Error()).stack);
let result = action();
_assert(result instanceof Result);
uniqueTest(() => {
if (result.isError())
throw result.value;
}, "A wast test that runs without any special assertion.");
}
function assert_unlinkable(bytes) {
let result = instance(bytes, registry, EXPECT_INVALID);
_assert(result instanceof Result);
uniqueTest(() => {
assert_true(result.isError(), 'expected error result');
if (result.isError()) {
let e = result.value;
assert_true(e instanceof WebAssembly.LinkError, `expected link error, observed ${e}:`);
}
}, "A wast module that is unlinkable.");
}
function assert_uninstantiable(bytes) {
let result = instance(bytes, registry, EXPECT_INVALID);
_assert(result instanceof Result);
uniqueTest(() => {
assert_true(result.isError(), 'expected error result');
if (result.isError()) {
let e = result.value;
assert_true(e instanceof WebAssembly.RuntimeError, `expected runtime error, observed ${e}:`);
}
}, "A wast module that is uninstantiable.");
}
function assert_trap(action) {
let result = action();
_assert(result instanceof Result);
uniqueTest(() => {
assert_true(result.isError(), 'expected error result');
if (result.isError()) {
let e = result.value;
assert_true(e instanceof WebAssembly.RuntimeError, `expected runtime error, observed ${e}:`);
}
}, "A wast module that must trap at runtime.");
}
let StackOverflow;
try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor }
function assert_exhaustion(action) {
let result = action();
_assert(result instanceof Result);
uniqueTest(() => {
assert_true(result.isError(), 'expected error result');
if (result.isError()) {
let e = result.value;
assert_true(e instanceof StackOverflow, `expected stack overflow error, observed ${e}:`);
}
}, "A wast module that must exhaust the stack space.");
}
function assert_return(action, ...expectedValues) {
if (expectedValues[0] instanceof Result) {
if (expectedValues[0].isError())
return;
expected = expected.value;
}
let result = action();
_assert(result instanceof Result);
uniqueTest(() => {
assert_true(!result.isError(), `expected success result, got: ${result.value}.`);
if (!result.isError()) {
assert_equals(result.value, expectedValues.length < 2 ? expectedValues[0] : expectedValues);
};
}, "A wast module that must return a particular value.");
};
function assert_return_nan(action) {
let result = action();
_assert(result instanceof Result);
uniqueTest(() => {
assert_true(!result.isError(), 'expected success result');
if (!result.isError()) {
assert_true(Number.isNaN(result.value), `expected NaN, observed ${result.value}.`);
};
}, "A wast module that must return NaN.");
}