blob: baf0be6d865cafd254ee779ccec3ba97ef3f6400 [file] [log] [blame]
/*
* Copyright 2018 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++} `;
};
})();
function uniqueTest(func, desc) {
test(func, testNum() + desc);
}
// 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 ************************************
******************************************************************************/
const EXPECT_INVALID = false;
/* DATA **********************************************************************/
let externrefs = {};
let externsym = Symbol("externref");
function externref(s) {
if (! (s in externrefs)) externrefs[s] = {[externsym]: s};
return externrefs[s];
}
function is_externref(x) {
return (x !== null && externsym in x) ? 1 : 0;
}
function is_funcref(x) {
return typeof x === "function" ? 1 : 0;
}
function eq_externref(x, y) {
return x === y ? 1 : 0;
}
function eq_funcref(x, y) {
return x === y ? 1 : 0;
}
// Default imports.
var registry = {};
// All tests run asynchronously and return their results as promises. To ensure
// that all tests execute in the correct order, we chain the promises together
// so that a test is only executed when all previous tests have finished their
// execution.
let chain = Promise.resolve();
// Resets the registry between two different WPT tests.
function reinitializeRegistry() {
if (typeof WebAssembly === "undefined") return;
chain = chain.then(_ => {
let spectest = {
externref: externref,
is_externref: is_externref,
is_funcref: is_funcref,
eq_externref: eq_externref,
eq_funcref: eq_funcref,
print: console.log.bind(console),
print_i32: console.log.bind(console),
print_i32_f32: console.log.bind(console),
print_f64_f64: console.log.bind(console),
print_f32: console.log.bind(console),
print_f64: console.log.bind(console),
global_i32: 666,
global_f32: 666,
global_f64: 666,
table: new WebAssembly.Table({
initial: 10,
maximum: 20,
element: "anyfunc"
}),
memory: new WebAssembly.Memory({ initial: 1, maximum: 2 })
};
let handler = {
get(target, prop) {
return prop in target ? target[prop] : {};
}
};
registry = new Proxy({ spectest }, handler);
});
// This function is called at the end of every generated js test file. By
// adding the chain as a promise_test here we make sure that the WPT harness
// waits for all tests in the chain to finish.
promise_test(_ => chain, testNum() + "Reinitialize the default imports");
}
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) {
const test = valid
? "Test that WebAssembly compilation succeeds"
: "Test that WebAssembly compilation fails";
const loc = new Error().stack.toString().replace("Error", "");
let buffer = binary(bytes);
let validated = WebAssembly.validate(buffer);
uniqueTest(_ => {
assert_equals(valid, validated);
}, test);
chain = chain.then(_ => WebAssembly.compile(buffer)).then(
module => {
uniqueTest(_ => {
assert_true(valid, loc);
}, test);
return module;
},
error => {
uniqueTest(_ => {
assert_true(
!valid,
`WebAssembly.compile failed unexpectedly with ${error} at {loc}`
);
}, test);
}
);
return chain;
}
function assert_invalid(bytes) {
module(bytes, EXPECT_INVALID);
}
const assert_malformed = assert_invalid;
function instance(bytes, imports, valid = true) {
const test = valid
? "Test that WebAssembly instantiation succeeds"
: "Test that WebAssembly instantiation fails";
const loc = new Error().stack.toString().replace("Error", "");
chain = Promise.all([imports, chain])
.then(values => {
let imports = values[0] ? values[0] : registry;
return WebAssembly.instantiate(binary(bytes), imports);
})
.then(
pair => {
uniqueTest(_ => {
assert_true(valid, loc);
}, test);
return pair.instance;
},
error => {
uniqueTest(_ => {
assert_true(
!valid,
`unexpected instantiation error, observed ${error} ${loc}`
);
}, test);
return error;
}
);
return chain;
}
function exports(instance) {
return instance.then(inst => {
return { module: inst.exports, spectest: registry.spectest };
});
}
function call(instance, name, args) {
return Promise.all([instance, chain]).then(values => {
return values[0].exports[name](...args);
});
}
function run(action) {
const test = "Run a WebAssembly test without special assertions";
const loc = new Error().stack.toString().replace("Error", "");
chain = Promise.all([chain, action()])
.then(
_ => {
uniqueTest(_ => {}, test);
},
error => {
uniqueTest(_ => {
assert_true(
false,
`unexpected runtime error, observed ${error} ${loc}`
);
}, "run");
}
)
// Clear all exceptions, so that subsequent tests get executed.
.catch(_ => {});
}
function assert_trap(action) {
const test = "Test that a WebAssembly code traps";
const loc = new Error().stack.toString().replace("Error", "");
chain = Promise.all([chain, action()])
.then(
result => {
uniqueTest(_ => {
assert_true(false, loc);
}, test);
},
error => {
uniqueTest(_ => {
assert_true(
error instanceof WebAssembly.RuntimeError,
`expected runtime error, observed ${error} ${loc}`
);
}, test);
}
)
// Clear all exceptions, so that subsequent tests get executed.
.catch(_ => {});
}
function assert_return(action, expected) {
const test = "Test that a WebAssembly code returns a specific result";
const loc = new Error().stack.toString().replace("Error", "");
chain = Promise.all([action(), chain])
.then(
values => {
uniqueTest(_ => {
let actual = values[0];
switch (expected) {
case "nan:canonical":
case "nan:arithmetic":
// Note that JS can't reliably distinguish different NaN values,
// so there's no good way to test that it's a canonical NaN.
assert_true(Number.isNaN(actual), `expected NaN, observed ${actual}.`);
return;
case "ref.func":
assert_true(typeof actual === "function", `expected Wasm function, got ${actual}`);
return;
case "ref.any":
assert_true(actual !== null, `expected Wasm reference, got ${actual}`);
return;
default:
assert_equals(actual, expected);
}
}, test);
},
error => {
uniqueTest(_ => {
assert_true(
false,
`unexpected runtime error, observed ${error} ${loc}`
);
}, test);
}
)
// Clear all exceptions, so that subsequent tests get executed.
.catch(_ => {});
}
let StackOverflow;
try {
(function f() {
1 + f();
})();
} catch (e) {
StackOverflow = e.constructor;
}
function assert_exhaustion(action) {
const test = "Test that a WebAssembly code exhauts the stack space";
const loc = new Error().stack.toString().replace("Error", "");
chain = Promise.all([action(), chain])
.then(
_ => {
uniqueTest(_ => {
assert_true(false, loc);
}, test);
},
error => {
uniqueTest(_ => {
assert_true(
error instanceof StackOverflow,
`expected runtime error, observed ${error} ${loc}`
);
}, test);
}
)
// Clear all exceptions, so that subsequent tests get executed.
.catch(_ => {});
}
function assert_unlinkable(bytes) {
const test = "Test that a WebAssembly module is unlinkable";
const loc = new Error().stack.toString().replace("Error", "");
instance(bytes, registry, EXPECT_INVALID)
.then(
result => {
uniqueTest(_ => {
assert_true(
result instanceof WebAssembly.LinkError,
`expected link error, observed ${result} ${loc}`
);
}, test);
},
_ => {
uniqueTest(_ => {
assert_true(false, loc);
}, test);
}
)
// Clear all exceptions, so that subsequent tests get executed.
.catch(_ => {});
}
function assert_uninstantiable(bytes) {
const test = "Test that a WebAssembly module is uninstantiable";
const loc = new Error().stack.toString().replace("Error", "");
instance(bytes, registry, EXPECT_INVALID)
.then(
result => {
uniqueTest(_ => {
assert_true(
result instanceof WebAssembly.RuntimeError,
`expected link error, observed ${result} ${loc}`
);
}, test);
},
_ => {
uniqueTest(_ => {
assert_true(false, loc);
}, test);
}
)
// Clear all exceptions, so that subsequent tests get executed.
.catch(_ => {});
}
function register(name, instance) {
const test =
"Test that the exports of a WebAssembly module can be registered";
const loc = new Error().stack.toString().replace("Error", "");
let stack = new Error();
chain = Promise.all([instance, chain])
.then(
values => {
registry[name] = values[0].exports;
},
_ => {
uniqueTest(_ => {
assert_true(false, loc);
}, test);
}
)
// Clear all exceptions, so that subsequent tests get executed.
.catch(_ => {});
}
function get(instance, name) {
const test = "Test that an export of a WebAssembly instance can be acquired";
const loc = new Error().stack.toString().replace("Error", "");
chain = Promise.all([instance, chain]).then(
values => {
let v = values[0].exports[name];
return (v instanceof WebAssembly.Global) ? v.value : v;
},
_ => {
uniqueTest(_ => {
assert_true(false, loc);
}, test);
}
);
return chain;
}