mirror of https://github.com/pyodide/pyodide.git
Merge pull request #70 from iodide-project/chromedriver
Add testing with Chrome
This commit is contained in:
commit
b5e9630f0d
|
@ -17,7 +17,7 @@ jobs:
|
|||
# Set up the Debian testing repo, and then install g++ from there...
|
||||
sudo bash -c "echo \"deb http://ftp.us.debian.org/debian testing main contrib non-free\" >> /etc/apt/sources.list"
|
||||
sudo apt-get update
|
||||
sudo apt-get install node-less cmake build-essential clang-format-6.0 flake8 uglifyjs python3-yaml
|
||||
sudo apt-get install node-less cmake build-essential clang-format-6.0 flake8 uglifyjs python3-yaml chromium
|
||||
sudo apt-get install -t testing g++-8
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 80 --slave /usr/bin/g++ g++ /usr/bin/g++-8
|
||||
|
@ -28,10 +28,14 @@ jobs:
|
|||
# Get recent version of Firefox and geckodriver
|
||||
wget -O firefox.tar.bz2 https://download.mozilla.org/\?product\=firefox-nightly-latest-ssl\&os\=linux64\&lang\=en-US
|
||||
tar jxf firefox.tar.bz2
|
||||
|
||||
wget https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz
|
||||
tar zxf geckodriver-v0.20.1-linux64.tar.gz -C firefox
|
||||
|
||||
# Get recent version of chromedriver
|
||||
wget https://chromedriver.storage.googleapis.com/2.40/chromedriver_linux64.zip
|
||||
unzip chromedriver_linux64.zip
|
||||
mv chromedriver firefox
|
||||
|
||||
# This Debian is so old, it doesn't know about wasm as a mime type, which then
|
||||
# causes Firefox to complain when loading it. Let's just add the new mime type.
|
||||
sudo bash -c "echo 'application/wasm wasm' >> /etc/mime.types"
|
||||
|
@ -43,9 +47,7 @@ jobs:
|
|||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-emsdk-{{ checksum "emsdk/Makefile" }}-v2
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-emsdk-
|
||||
- v1-emsdk-{{ checksum "emsdk/Makefile" }}-v4
|
||||
|
||||
- run:
|
||||
name: build
|
||||
|
@ -56,7 +58,7 @@ jobs:
|
|||
- save_cache:
|
||||
paths:
|
||||
- ./emsdk/emsdk
|
||||
key: v1-emsdk-{{ checksum "emsdk/Makefile" }}-v2
|
||||
key: v1-emsdk-{{ checksum "emsdk/Makefile" }}-v4
|
||||
|
||||
- run:
|
||||
name: test
|
||||
|
|
6
Makefile
6
Makefile
|
@ -96,10 +96,14 @@ build/renderedhtml.css: src/renderedhtml.less
|
|||
lessc $< $@
|
||||
|
||||
|
||||
test: all build/test.html
|
||||
test: all build/test.html build/test_data.txt
|
||||
py.test test -v
|
||||
|
||||
|
||||
build/test_data.txt: test/data.txt
|
||||
cp test/data.txt build/test_data.txt
|
||||
|
||||
|
||||
lint:
|
||||
flake8 src
|
||||
flake8 test
|
||||
|
|
|
@ -23,8 +23,8 @@ def run_native(hostpython, code):
|
|||
return float(output.strip().split()[-1])
|
||||
|
||||
|
||||
def run_wasm(code):
|
||||
s = conftest.SeleniumWrapper()
|
||||
def run_wasm(code, cls):
|
||||
s = cls()
|
||||
try:
|
||||
s.load_package('numpy')
|
||||
s.run(code)
|
||||
|
@ -38,12 +38,18 @@ def run_wasm(code):
|
|||
return runtime
|
||||
|
||||
|
||||
def run_both(hostpython, code):
|
||||
def run_all(hostpython, code):
|
||||
a = run_native(hostpython, code)
|
||||
print(a)
|
||||
b = run_wasm(code)
|
||||
print(b)
|
||||
result = (a, b)
|
||||
print("native:", a)
|
||||
b = run_wasm(code, conftest.FirefoxWrapper)
|
||||
print("firefox:", b)
|
||||
c = run_wasm(code, conftest.ChromeWrapper)
|
||||
print("chrome:", c)
|
||||
result = {
|
||||
'native': a,
|
||||
'firefox': b,
|
||||
'chrome': c
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
|
@ -92,7 +98,7 @@ def main(hostpython):
|
|||
results = {}
|
||||
for k, v in get_benchmarks():
|
||||
print(k)
|
||||
results[k] = run_both(hostpython, v)
|
||||
results[k] = run_all(hostpython, v)
|
||||
return results
|
||||
|
||||
|
||||
|
|
|
@ -11,20 +11,24 @@ with open(sys.argv[-2]) as fp:
|
|||
|
||||
results = []
|
||||
for k, v in content.items():
|
||||
results.append((k, v[1] / v[0]))
|
||||
results.append((k, v['firefox'] / v['native'], v['chrome'] / v['native']))
|
||||
results.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
names = [x[0] for x in results]
|
||||
values = [x[1] for x in results]
|
||||
firefox = [x[1] for x in results]
|
||||
chrome = [x[2] for x in results]
|
||||
|
||||
width = 0.35
|
||||
y_pos = np.arange(len(results))
|
||||
ax.barh(y_pos, values, align='center')
|
||||
ax.set_yticks(y_pos)
|
||||
ax.barh(y_pos, firefox, width, color='#ff9400', label='firefox')
|
||||
ax.barh(y_pos + width, chrome, width, color='#45a1ff', label='chrome')
|
||||
ax.set_yticks(y_pos + width / 2)
|
||||
ax.set_yticklabels(names)
|
||||
ax.invert_yaxis()
|
||||
ax.set_xlabel('Slowdown factor (WebAssembly:Native)')
|
||||
ax.set_title('Python benchmarks')
|
||||
ax.axvline(1.0, color='red')
|
||||
ax.grid()
|
||||
ax.legend(loc='lower right')
|
||||
|
||||
plt.savefig(sys.argv[-1])
|
||||
|
|
|
@ -11,9 +11,11 @@ emsdk/emsdk:
|
|||
./emsdk install --build=Release sdk-tag-1.38.4-64bit binaryen-tag-1.38.4-64bit ; \
|
||||
cd .. ; \
|
||||
(cat patches/*.patch | patch -p1) ; \
|
||||
cp files/* emsdk/emscripten/tag-1.38.4/src/ ; \
|
||||
cd emsdk/binaryen/tag-1.38.4_64bit_binaryen/ ; \
|
||||
make ; \
|
||||
cd ../.. ; \
|
||||
cp binaryen/tag-1.38.4/bin/binaryen.js binaryen/tag-1.38.4_64bit_binaryen/bin ; \
|
||||
./emsdk activate --embedded --build=Release sdk-tag-1.38.4-64bit binaryen-tag-1.38.4-64bit \
|
||||
)
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,546 @@
|
|||
// {{PREAMBLE_ADDITIONS}}
|
||||
|
||||
var STACK_ALIGN = {{{ STACK_ALIGN }}};
|
||||
|
||||
#if ASSERTIONS
|
||||
// stack management, and other functionality that is provided by the compiled code,
|
||||
// should not be used before it is ready
|
||||
stackSave = stackRestore = stackAlloc = setTempRet0 = getTempRet0 = function() {
|
||||
abort('cannot use the stack before compiled code is ready to run, and has provided stack access');
|
||||
};
|
||||
#endif
|
||||
|
||||
function staticAlloc(size) {
|
||||
assert(!staticSealed);
|
||||
var ret = STATICTOP;
|
||||
STATICTOP = (STATICTOP + size + 15) & -16;
|
||||
return ret;
|
||||
}
|
||||
|
||||
function dynamicAlloc(size) {
|
||||
assert(DYNAMICTOP_PTR);
|
||||
var ret = HEAP32[DYNAMICTOP_PTR>>2];
|
||||
var end = (ret + size + 15) & -16;
|
||||
HEAP32[DYNAMICTOP_PTR>>2] = end;
|
||||
if (end >= TOTAL_MEMORY) {
|
||||
var success = enlargeMemory();
|
||||
if (!success) {
|
||||
HEAP32[DYNAMICTOP_PTR>>2] = ret;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
{{{ alignMemory }}}
|
||||
|
||||
{{{ getNativeTypeSize }}}
|
||||
|
||||
function warnOnce(text) {
|
||||
if (!warnOnce.shown) warnOnce.shown = {};
|
||||
if (!warnOnce.shown[text]) {
|
||||
warnOnce.shown[text] = 1;
|
||||
Module.printErr(text);
|
||||
}
|
||||
}
|
||||
|
||||
var asm2wasmImports = { // special asm2wasm imports
|
||||
"f64-rem": function(x, y) {
|
||||
return x % y;
|
||||
},
|
||||
"debugger": function() {
|
||||
debugger;
|
||||
}
|
||||
#if NEED_ALL_ASM2WASM_IMPORTS
|
||||
,
|
||||
"f64-to-int": function(x) {
|
||||
return x | 0;
|
||||
},
|
||||
"i32s-div": function(x, y) {
|
||||
return ((x | 0) / (y | 0)) | 0;
|
||||
},
|
||||
"i32u-div": function(x, y) {
|
||||
return ((x >>> 0) / (y >>> 0)) >>> 0;
|
||||
},
|
||||
"i32s-rem": function(x, y) {
|
||||
return ((x | 0) % (y | 0)) | 0;
|
||||
},
|
||||
"i32u-rem": function(x, y) {
|
||||
return ((x >>> 0) % (y >>> 0)) >>> 0;
|
||||
}
|
||||
#endif // NEED_ALL_ASM2WASM_IMPORTS
|
||||
};
|
||||
|
||||
#if RELOCATABLE
|
||||
var loadedDynamicLibraries = [];
|
||||
|
||||
function loadDynamicLibrary(lib) {
|
||||
var libModule;
|
||||
#if WASM
|
||||
var bin;
|
||||
if (lib.buffer) {
|
||||
// we were provided the binary, in a typed array
|
||||
bin = lib;
|
||||
} else {
|
||||
// load the binary synchronously
|
||||
bin = Module['readBinary'](lib);
|
||||
}
|
||||
libModule = loadWebAssemblyModule(bin);
|
||||
#else
|
||||
var src = Module['read'](lib);
|
||||
libModule = eval(src)(
|
||||
alignFunctionTables(),
|
||||
Module
|
||||
);
|
||||
#endif
|
||||
// add symbols into global namespace TODO: weak linking etc.
|
||||
for (var sym in libModule) {
|
||||
if (!Module.hasOwnProperty(sym)) {
|
||||
Module[sym] = libModule[sym];
|
||||
}
|
||||
#if ASSERTIONS == 2
|
||||
else if (sym[0] === '_') {
|
||||
var curr = Module[sym], next = libModule[sym];
|
||||
// don't warn on functions - might be odr, linkonce_odr, etc.
|
||||
if (!(typeof curr === 'function' && typeof next === 'function')) {
|
||||
Module.printErr("warning: trying to dynamically load symbol '" + sym + "' (from '" + lib + "') that already exists (duplicate symbol? or weak linking, which isn't supported yet?)"); // + [curr, ' vs ', next]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
loadedDynamicLibraries.push(libModule);
|
||||
}
|
||||
|
||||
#if WASM
|
||||
// Loads a side module from binary data
|
||||
function loadWebAssemblyModule(binary, loadAsync) {
|
||||
var int32View = new Uint32Array(new Uint8Array(binary.subarray(0, 24)).buffer);
|
||||
assert(int32View[0] == 0x6d736100, 'need to see wasm magic number'); // \0wasm
|
||||
// we should see the dylink section right after the magic number and wasm version
|
||||
assert(binary[8] === 0, 'need the dylink section to be first')
|
||||
var next = 9;
|
||||
function getLEB() {
|
||||
var ret = 0;
|
||||
var mul = 1;
|
||||
while (1) {
|
||||
var byte = binary[next++];
|
||||
ret += ((byte & 0x7f) * mul);
|
||||
mul *= 0x80;
|
||||
if (!(byte & 0x80)) break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
var sectionSize = getLEB();
|
||||
assert(binary[next] === 6); next++; // size of "dylink" string
|
||||
assert(binary[next] === 'd'.charCodeAt(0)); next++;
|
||||
assert(binary[next] === 'y'.charCodeAt(0)); next++;
|
||||
assert(binary[next] === 'l'.charCodeAt(0)); next++;
|
||||
assert(binary[next] === 'i'.charCodeAt(0)); next++;
|
||||
assert(binary[next] === 'n'.charCodeAt(0)); next++;
|
||||
assert(binary[next] === 'k'.charCodeAt(0)); next++;
|
||||
var memorySize = getLEB();
|
||||
var memoryAlign = getLEB();
|
||||
var tableSize = getLEB();
|
||||
var tableAlign = getLEB();
|
||||
// alignments are powers of 2
|
||||
memoryAlign = Math.pow(2, memoryAlign);
|
||||
tableAlign = Math.pow(2, tableAlign);
|
||||
// finalize alignments and verify them
|
||||
memoryAlign = Math.max(memoryAlign, STACK_ALIGN); // we at least need stack alignment
|
||||
assert(tableAlign === 1);
|
||||
// prepare memory
|
||||
var memoryStart = alignMemory(getMemory(memorySize + memoryAlign), memoryAlign); // TODO: add to cleanups
|
||||
// The static area consists of explicitly initialized data, followed by zero-initialized data.
|
||||
// The latter may need zeroing out if the MAIN_MODULE has already used this memory area before
|
||||
// dlopen'ing the SIDE_MODULE. Since we don't know the size of the explicitly initialized data
|
||||
// here, we just zero the whole thing, which is suboptimal, but should at least resolve bugs
|
||||
// from uninitialized memory.
|
||||
for (var i = memoryStart; i < memoryStart + memorySize; ++i) HEAP8[i] = 0;
|
||||
// prepare env imports
|
||||
var env = Module['asmLibraryArg'];
|
||||
// TODO: use only memoryBase and tableBase, need to update asm.js backend
|
||||
var table = Module['wasmTable'];
|
||||
var oldTableSize = table.length;
|
||||
env['memoryBase'] = env['gb'] = memoryStart;
|
||||
env['tableBase'] = env['fb'] = oldTableSize;
|
||||
var originalTable = table;
|
||||
table.grow(tableSize);
|
||||
assert(table === originalTable);
|
||||
// zero-initialize memory and table TODO: in some cases we can tell it is already zero initialized
|
||||
for (var i = env['memoryBase']; i < env['memoryBase'] + memorySize; i++) {
|
||||
HEAP8[i] = 0;
|
||||
}
|
||||
for (var i = env['tableBase']; i < env['tableBase'] + tableSize; i++) {
|
||||
table.set(i, null);
|
||||
}
|
||||
// copy currently exported symbols so the new module can import them
|
||||
for (var x in Module) {
|
||||
if (!(x in env)) {
|
||||
env[x] = Module[x];
|
||||
}
|
||||
}
|
||||
var info = {
|
||||
global: {
|
||||
'NaN': NaN,
|
||||
'Infinity': Infinity,
|
||||
},
|
||||
'global.Math': Math,
|
||||
env: env,
|
||||
'asm2wasm': asm2wasmImports
|
||||
};
|
||||
#if ASSERTIONS
|
||||
var oldTable = [];
|
||||
for (var i = 0; i < oldTableSize; i++) {
|
||||
oldTable.push(table.get(i));
|
||||
}
|
||||
#endif
|
||||
|
||||
function postInstantiation(instance) {
|
||||
var exports = {};
|
||||
#if ASSERTIONS
|
||||
// the table should be unchanged
|
||||
assert(table === originalTable);
|
||||
assert(table === Module['wasmTable']);
|
||||
if (instance.exports['table']) {
|
||||
assert(table === instance.exports['table']);
|
||||
}
|
||||
// the old part of the table should be unchanged
|
||||
for (var i = 0; i < oldTableSize; i++) {
|
||||
assert(table.get(i) === oldTable[i], 'old table entries must remain the same');
|
||||
}
|
||||
// verify that the new table region was filled in
|
||||
for (var i = 0; i < tableSize; i++) {
|
||||
assert(table.get(oldTableSize + i) !== undefined, 'table entry was not filled in');
|
||||
}
|
||||
#endif
|
||||
for (var e in instance.exports) {
|
||||
var value = instance.exports[e];
|
||||
if (typeof value === 'object') {
|
||||
// a breaking change in the wasm spec, globals are now objects
|
||||
// https://github.com/WebAssembly/mutable-global/issues/1
|
||||
value = value.value;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
// relocate it - modules export the absolute value, they can't relocate before they export
|
||||
#if EMULATED_FUNCTION_POINTERS
|
||||
// it may be a function pointer
|
||||
if (e.substr(0, 3) == 'fp$' && typeof instance.exports[e.substr(3)] === 'function') {
|
||||
value = value + env['tableBase'];
|
||||
} else {
|
||||
#endif
|
||||
value = value + env['memoryBase'];
|
||||
#if EMULATED_FUNCTION_POINTERS
|
||||
}
|
||||
#endif
|
||||
}
|
||||
exports[e] = value;
|
||||
}
|
||||
// initialize the module
|
||||
var init = exports['__post_instantiate'];
|
||||
if (init) {
|
||||
if (runtimeInitialized) {
|
||||
init();
|
||||
} else {
|
||||
// we aren't ready to run compiled code yet
|
||||
__ATINIT__.push(init);
|
||||
}
|
||||
}
|
||||
return exports;
|
||||
}
|
||||
|
||||
if (loadAsync) {
|
||||
return WebAssembly.instantiate(binary, info).then(function(result) {
|
||||
return postInstantiation(result.instance);
|
||||
});
|
||||
} else {
|
||||
var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), info);
|
||||
return postInstantiation(instance);
|
||||
}
|
||||
}
|
||||
Module['loadWebAssemblyModule'] = loadWebAssemblyModule;
|
||||
|
||||
#endif // WASM
|
||||
#endif // RELOCATABLE
|
||||
|
||||
#if EMULATED_FUNCTION_POINTERS
|
||||
function getFunctionTables(module) {
|
||||
if (!module) module = Module;
|
||||
var tables = {};
|
||||
for (var t in module) {
|
||||
if (/^FUNCTION_TABLE_.*/.test(t)) {
|
||||
var table = module[t];
|
||||
if (typeof table === 'object') tables[t.substr('FUNCTION_TABLE_'.length)] = table;
|
||||
}
|
||||
}
|
||||
return tables;
|
||||
}
|
||||
|
||||
function alignFunctionTables(module) {
|
||||
var tables = getFunctionTables(module);
|
||||
var maxx = 0;
|
||||
for (var sig in tables) {
|
||||
maxx = Math.max(maxx, tables[sig].length);
|
||||
}
|
||||
assert(maxx >= 0);
|
||||
for (var sig in tables) {
|
||||
var table = tables[sig];
|
||||
while (table.length < maxx) table.push(0);
|
||||
}
|
||||
return maxx;
|
||||
}
|
||||
|
||||
#if RELOCATABLE
|
||||
// register functions from a new module being loaded
|
||||
function registerFunctions(sigs, newModule) {
|
||||
sigs.forEach(function(sig) {
|
||||
if (!Module['FUNCTION_TABLE_' + sig]) {
|
||||
Module['FUNCTION_TABLE_' + sig] = [];
|
||||
}
|
||||
});
|
||||
var oldMaxx = alignFunctionTables(); // align the new tables we may have just added
|
||||
var newMaxx = alignFunctionTables(newModule);
|
||||
var maxx = oldMaxx + newMaxx;
|
||||
sigs.forEach(function(sig) {
|
||||
var newTable = newModule['FUNCTION_TABLE_' + sig];
|
||||
var oldTable = Module['FUNCTION_TABLE_' + sig];
|
||||
assert(newTable !== oldTable);
|
||||
assert(oldTable.length === oldMaxx);
|
||||
for (var i = 0; i < newTable.length; i++) {
|
||||
oldTable.push(newTable[i]);
|
||||
}
|
||||
assert(oldTable.length === maxx);
|
||||
});
|
||||
assert(maxx === alignFunctionTables()); // align the ones we didn't touch
|
||||
}
|
||||
// export this so side modules can use it
|
||||
Module['registerFunctions'] = registerFunctions;
|
||||
#endif // RELOCATABLE
|
||||
#endif // EMULATED_FUNCTION_POINTERS
|
||||
|
||||
#if WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS
|
||||
var jsCallStartIndex = {{{ JSCALL_START_INDEX }}};
|
||||
var jsCallSigOrder = {{{ JSON.stringify(JSCALL_SIG_ORDER) }}};
|
||||
var jsCallNumSigs = Object.keys(jsCallSigOrder).length;
|
||||
var functionPointers = new Array(jsCallNumSigs * {{{ RESERVED_FUNCTION_POINTERS }}});
|
||||
#else // WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS == 0
|
||||
var jsCallStartIndex = 1;
|
||||
var functionPointers = new Array({{{ RESERVED_FUNCTION_POINTERS }}});
|
||||
#endif // WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS
|
||||
|
||||
// 'sig' parameter is only used on LLVM wasm backend
|
||||
function addFunction(func, sig) {
|
||||
#if WASM_BACKEND
|
||||
assert(typeof sig !== 'undefined',
|
||||
'Second argument of addFunction should be a wasm function signature ' +
|
||||
'string');
|
||||
#endif // WASM_BACKEND
|
||||
#if ASSERTIONS
|
||||
if (typeof sig === 'undefined') {
|
||||
Module.printErr('warning: addFunction(): You should provide a wasm function signature string as a second argument. This is not necessary for asm.js and asm2wasm, but is required for the LLVM wasm backend, so it is recommended for full portability.');
|
||||
}
|
||||
#endif // ASSERTIONS
|
||||
#if EMULATED_FUNCTION_POINTERS == 0
|
||||
#if WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS
|
||||
var base = jsCallSigOrder[sig] * {{{ RESERVED_FUNCTION_POINTERS }}};
|
||||
#else // WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS == 0
|
||||
var base = 0;
|
||||
#endif // WASM_BACKEND_WITH_RESERVED_FUNCTION_POINTERS
|
||||
for (var i = base; i < base + {{{ RESERVED_FUNCTION_POINTERS }}}; i++) {
|
||||
if (!functionPointers[i]) {
|
||||
functionPointers[i] = func;
|
||||
return jsCallStartIndex + i;
|
||||
}
|
||||
}
|
||||
throw 'Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.';
|
||||
#else
|
||||
#if WASM
|
||||
// we can simply append to the wasm table
|
||||
var table = Module['wasmTable'];
|
||||
var ret = table.length;
|
||||
table.grow(1);
|
||||
table.set(ret, func);
|
||||
return ret;
|
||||
#else
|
||||
alignFunctionTables(); // XXX we should rely on this being an invariant
|
||||
var tables = getFunctionTables();
|
||||
var ret = -1;
|
||||
for (var sig in tables) {
|
||||
var table = tables[sig];
|
||||
if (ret < 0) ret = table.length;
|
||||
else assert(ret === table.length);
|
||||
table.push(func);
|
||||
}
|
||||
return ret;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
function removeFunction(index) {
|
||||
#if EMULATED_FUNCTION_POINTERS == 0
|
||||
functionPointers[index-jsCallStartIndex] = null;
|
||||
#else
|
||||
alignFunctionTables(); // XXX we should rely on this being an invariant
|
||||
var tables = getFunctionTables();
|
||||
for (var sig in tables) {
|
||||
tables[sig][index] = null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
var funcWrappers = {};
|
||||
|
||||
function getFuncWrapper(func, sig) {
|
||||
if (!func) return; // on null pointer, return undefined
|
||||
assert(sig);
|
||||
if (!funcWrappers[sig]) {
|
||||
funcWrappers[sig] = {};
|
||||
}
|
||||
var sigCache = funcWrappers[sig];
|
||||
if (!sigCache[func]) {
|
||||
// optimize away arguments usage in common cases
|
||||
if (sig.length === 1) {
|
||||
sigCache[func] = function dynCall_wrapper() {
|
||||
return dynCall(sig, func);
|
||||
};
|
||||
} else if (sig.length === 2) {
|
||||
sigCache[func] = function dynCall_wrapper(arg) {
|
||||
return dynCall(sig, func, [arg]);
|
||||
};
|
||||
} else {
|
||||
// general case
|
||||
sigCache[func] = function dynCall_wrapper() {
|
||||
return dynCall(sig, func, Array.prototype.slice.call(arguments));
|
||||
};
|
||||
}
|
||||
}
|
||||
return sigCache[func];
|
||||
}
|
||||
|
||||
#if RUNTIME_DEBUG
|
||||
var runtimeDebug = true; // Switch to false at runtime to disable logging at the right times
|
||||
|
||||
var printObjectList = [];
|
||||
|
||||
function prettyPrint(arg) {
|
||||
if (typeof arg == 'undefined') return '!UNDEFINED!';
|
||||
if (typeof arg == 'boolean') arg = arg + 0;
|
||||
if (!arg) return arg;
|
||||
var index = printObjectList.indexOf(arg);
|
||||
if (index >= 0) return '<' + arg + '|' + index + '>';
|
||||
if (arg.toString() == '[object HTMLImageElement]') {
|
||||
return arg + '\n\n';
|
||||
}
|
||||
if (arg.byteLength) {
|
||||
return '{' + Array.prototype.slice.call(arg, 0, Math.min(arg.length, 400)) + '}'; // Useful for correct arrays, less so for compiled arrays, see the code below for that
|
||||
var buf = new ArrayBuffer(32);
|
||||
var i8buf = new Int8Array(buf);
|
||||
var i16buf = new Int16Array(buf);
|
||||
var f32buf = new Float32Array(buf);
|
||||
switch(arg.toString()) {
|
||||
case '[object Uint8Array]':
|
||||
i8buf.set(arg.subarray(0, 32));
|
||||
break;
|
||||
case '[object Float32Array]':
|
||||
f32buf.set(arg.subarray(0, 5));
|
||||
break;
|
||||
case '[object Uint16Array]':
|
||||
i16buf.set(arg.subarray(0, 16));
|
||||
break;
|
||||
default:
|
||||
alert('unknown array for debugging: ' + arg);
|
||||
throw 'see alert';
|
||||
}
|
||||
var ret = '{' + arg.byteLength + ':\n';
|
||||
var arr = Array.prototype.slice.call(i8buf);
|
||||
ret += 'i8:' + arr.toString().replace(/,/g, ',') + '\n';
|
||||
arr = Array.prototype.slice.call(f32buf, 0, 8);
|
||||
ret += 'f32:' + arr.toString().replace(/,/g, ',') + '}';
|
||||
return ret;
|
||||
}
|
||||
if (typeof arg == 'object') {
|
||||
printObjectList.push(arg);
|
||||
return '<' + arg + '|' + (printObjectList.length-1) + '>';
|
||||
}
|
||||
if (typeof arg == 'number') {
|
||||
if (arg > 0) return '0x' + arg.toString(16) + ' (' + arg + ')';
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
#endif
|
||||
|
||||
function makeBigInt(low, high, unsigned) {
|
||||
return unsigned ? ((+((low>>>0)))+((+((high>>>0)))*4294967296.0)) : ((+((low>>>0)))+((+((high|0)))*4294967296.0));
|
||||
}
|
||||
|
||||
function dynCall(sig, ptr, args) {
|
||||
if (args && args.length) {
|
||||
#if ASSERTIONS
|
||||
assert(args.length == sig.length-1);
|
||||
#endif
|
||||
#if ASSERTIONS
|
||||
assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\'');
|
||||
#endif
|
||||
return Module['dynCall_' + sig].apply(null, [ptr].concat(args));
|
||||
} else {
|
||||
#if ASSERTIONS
|
||||
assert(sig.length == 1);
|
||||
#endif
|
||||
#if ASSERTIONS
|
||||
assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\'');
|
||||
#endif
|
||||
return Module['dynCall_' + sig].call(null, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
#if RELOCATABLE
|
||||
// tempRet0 is normally handled in the module. but in relocatable code,
|
||||
// we need to share a single one among all the modules, so they all call
|
||||
// out.
|
||||
var tempRet0 = 0;
|
||||
|
||||
var setTempRet0 = function(value) {
|
||||
tempRet0 = value;
|
||||
}
|
||||
|
||||
var getTempRet0 = function() {
|
||||
return tempRet0;
|
||||
}
|
||||
#endif // RELOCATABLE
|
||||
|
||||
#if RETAIN_COMPILER_SETTINGS
|
||||
var compilerSettings = {{{ JSON.stringify(makeRetainedCompilerSettings()) }}} ;
|
||||
|
||||
function getCompilerSetting(name) {
|
||||
if (!(name in compilerSettings)) return 'invalid compiler setting: ' + name;
|
||||
return compilerSettings[name];
|
||||
}
|
||||
#else // RETAIN_COMPILER_SETTINGS
|
||||
#if ASSERTIONS
|
||||
function getCompilerSetting(name) {
|
||||
throw 'You must build with -s RETAIN_COMPILER_SETTINGS=1 for getCompilerSetting or emscripten_get_compiler_setting to work';
|
||||
}
|
||||
#endif // ASSERTIONS
|
||||
#endif // RETAIN_COMPILER_SETTINGS
|
||||
|
||||
var Runtime = {
|
||||
// FIXME backwards compatibility layer for ports. Support some Runtime.*
|
||||
// for now, fix it there, then remove it from here. That way we
|
||||
// can minimize any period of breakage.
|
||||
dynCall: dynCall, // for SDL2 port
|
||||
#if ASSERTIONS
|
||||
// helpful errors
|
||||
getTempRet0: function() { abort('getTempRet0() is now a top-level function, after removing the Runtime object. Remove "Runtime."') },
|
||||
staticAlloc: function() { abort('staticAlloc() is now a top-level function, after removing the Runtime object. Remove "Runtime."') },
|
||||
stackAlloc: function() { abort('stackAlloc() is now a top-level function, after removing the Runtime object. Remove "Runtime."') },
|
||||
#endif
|
||||
};
|
||||
|
||||
// The address globals begin at. Very low in memory, for code size and optimization opportunities.
|
||||
// Above 0 is static memory, starting with globals.
|
||||
// Then the stack.
|
||||
// Then 'dynamic' memory for sbrk.
|
||||
var GLOBAL_BASE = {{{ GLOBAL_BASE }}};
|
||||
|
||||
#if RELOCATABLE
|
||||
GLOBAL_BASE = alignMemory(GLOBAL_BASE, {{{ MAX_GLOBAL_ALIGN || 1 }}});
|
||||
#endif
|
|
@ -1,253 +0,0 @@
|
|||
diff --git a/src/library.js b/src/library.js
|
||||
index 5fc87ab16..b8ead8fc0 100644
|
||||
--- a/src/library.js
|
||||
+++ b/src/library.js
|
||||
@@ -1755,39 +1755,44 @@ LibraryManager.library = {
|
||||
return handle;
|
||||
}
|
||||
|
||||
+ var lib_module;
|
||||
if (filename === '__self__') {
|
||||
var handle = -1;
|
||||
- var lib_module = Module;
|
||||
+ lib_module = Module;
|
||||
} else {
|
||||
- var target = FS.findObject(filename);
|
||||
- if (!target || target.isFolder || target.isDevice) {
|
||||
- DLFCN.errorMsg = 'Could not find dynamic lib: ' + filename;
|
||||
- return 0;
|
||||
- }
|
||||
- FS.forceLoadFile(target);
|
||||
+ if (Module['preloadedWasm'] !== undefined &&
|
||||
+ Module['preloadedWasm'][filename] !== undefined) {
|
||||
+ lib_module = Module['preloadedWasm'][filename];
|
||||
+ } else {
|
||||
+ var target = FS.findObject(filename);
|
||||
+ if (!target || target.isFolder || target.isDevice) {
|
||||
+ DLFCN.errorMsg = 'Could not find dynamic lib: ' + filename;
|
||||
+ return 0;
|
||||
+ }
|
||||
+ FS.forceLoadFile(target);
|
||||
|
||||
- var lib_module;
|
||||
- try {
|
||||
+ try {
|
||||
#if WASM
|
||||
- // the shared library is a shared wasm library (see tools/shared.py WebAssembly.make_shared_library)
|
||||
- var lib_data = FS.readFile(filename, { encoding: 'binary' });
|
||||
- if (!(lib_data instanceof Uint8Array)) lib_data = new Uint8Array(lib_data);
|
||||
- //Module.printErr('libfile ' + filename + ' size: ' + lib_data.length);
|
||||
- lib_module = loadWebAssemblyModule(lib_data);
|
||||
+ // the shared library is a shared wasm library (see tools/shared.py WebAssembly.make_shared_library)
|
||||
+ var lib_data = FS.readFile(filename, { encoding: 'binary' });
|
||||
+ if (!(lib_data instanceof Uint8Array)) lib_data = new Uint8Array(lib_data);
|
||||
+ //Module.printErr('libfile ' + filename + ' size: ' + lib_data.length);
|
||||
+ lib_module = loadWebAssemblyModule(lib_data);
|
||||
#else
|
||||
- // the shared library is a JS file, which we eval
|
||||
- var lib_data = FS.readFile(filename, { encoding: 'utf8' });
|
||||
- lib_module = eval(lib_data)(
|
||||
- alignFunctionTables(),
|
||||
- Module
|
||||
- );
|
||||
+ // the shared library is a JS file, which we eval
|
||||
+ var lib_data = FS.readFile(filename, { encoding: 'utf8' });
|
||||
+ lib_module = eval(lib_data)(
|
||||
+ alignFunctionTables(),
|
||||
+ Module
|
||||
+ );
|
||||
#endif
|
||||
- } catch (e) {
|
||||
+ } catch (e) {
|
||||
#if ASSERTIONS
|
||||
- Module.printErr('Error in loading dynamic library: ' + e);
|
||||
+ Module.printErr('Error in loading dynamic library: ' + e);
|
||||
#endif
|
||||
- DLFCN.errorMsg = 'Could not evaluate dynamic lib: ' + filename + '\n' + e;
|
||||
- return 0;
|
||||
+ DLFCN.errorMsg = 'Could not evaluate dynamic lib: ' + filename + '\n' + e;
|
||||
+ return 0;
|
||||
+ }
|
||||
}
|
||||
|
||||
// Not all browsers support Object.keys().
|
||||
diff --git a/src/library_browser.js b/src/library_browser.js
|
||||
index 36738391e..4258835ea 100644
|
||||
--- a/src/library_browser.js
|
||||
+++ b/src/library_browser.js
|
||||
@@ -225,6 +225,33 @@ var LibraryBrowser = {
|
||||
};
|
||||
Module['preloadPlugins'].push(audioPlugin);
|
||||
|
||||
+#if (WASM != 0) && (MAIN_MODULE != 0)
|
||||
+ var wasmPlugin = {};
|
||||
+ wasmPlugin['asyncWasmLoadPromise'] = new Promise(
|
||||
+ function(resolve, reject) { return resolve(); });
|
||||
+ wasmPlugin['canHandle'] = function(name) {
|
||||
+ return !Module.noWasmDecoding && (name.endsWith('.so') || name.endsWith('.wasm'));
|
||||
+ };
|
||||
+ wasmPlugin['handle'] = function(byteArray, name, onload, onerror) {
|
||||
+ // loadWebAssemblyModule can not load modules out-of-order, so rather
|
||||
+ // than just running the promises in parallel, this makes a chain of
|
||||
+ // promises to run in series.
|
||||
+ this.asyncWasmLoadPromise = this.asyncWasmLoadPromise.then(
|
||||
+ function() {
|
||||
+ return Module.loadWebAssemblyModule(byteArray, true)
|
||||
+ }).then(
|
||||
+ function(module) {
|
||||
+ Module.preloadedWasm[name] = module;
|
||||
+ onload();
|
||||
+ },
|
||||
+ function(err) {
|
||||
+ console.warn("Couldn't instantiate wasm: " + name + " '" + err + "'");
|
||||
+ onerror();
|
||||
+ });
|
||||
+ };
|
||||
+ Module['preloadPlugins'].push(wasmPlugin);
|
||||
+#endif
|
||||
+
|
||||
// Canvas event setup
|
||||
|
||||
function pointerLockChange() {
|
||||
diff --git a/src/preamble.js b/src/preamble.js
|
||||
index a757e8300..f529fe148 100644
|
||||
--- a/src/preamble.js
|
||||
+++ b/src/preamble.js
|
||||
@@ -1822,6 +1822,9 @@ function removeRunDependency(id) {
|
||||
|
||||
Module["preloadedImages"] = {}; // maps url to image data
|
||||
Module["preloadedAudios"] = {}; // maps url to audio data
|
||||
+#if (WASM != 0) && (MAIN_MODULE != 0)
|
||||
+Module["preloadedWasm"] = {}; // maps url to wasm instance exports
|
||||
+#endif
|
||||
|
||||
#if PGO
|
||||
var PGOMonitor = {
|
||||
diff --git a/src/support.js b/src/support.js
|
||||
index f6c9842ff..99367db70 100644
|
||||
--- a/src/support.js
|
||||
+++ b/src/support.js
|
||||
@@ -86,7 +86,7 @@ function loadDynamicLibrary(lib) {
|
||||
|
||||
#if WASM
|
||||
// Loads a side module from binary data
|
||||
-function loadWebAssemblyModule(binary) {
|
||||
+function loadWebAssemblyModule(binary, loadAsync) {
|
||||
var int32View = new Uint32Array(new Uint8Array(binary.subarray(0, 24)).buffer);
|
||||
assert(int32View[0] == 0x6d736100, 'need to see wasm magic number'); // \0wasm
|
||||
// we should see the dylink section right after the magic number and wasm version
|
||||
@@ -166,59 +166,71 @@ function loadWebAssemblyModule(binary) {
|
||||
oldTable.push(table.get(i));
|
||||
}
|
||||
#endif
|
||||
- // create a module from the instance
|
||||
- var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), info);
|
||||
+
|
||||
+ function postInstantiation(instance) {
|
||||
+ var exports = {};
|
||||
#if ASSERTIONS
|
||||
- // the table should be unchanged
|
||||
- assert(table === originalTable);
|
||||
- assert(table === Module['wasmTable']);
|
||||
- if (instance.exports['table']) {
|
||||
- assert(table === instance.exports['table']);
|
||||
- }
|
||||
- // the old part of the table should be unchanged
|
||||
- for (var i = 0; i < oldTableSize; i++) {
|
||||
- assert(table.get(i) === oldTable[i], 'old table entries must remain the same');
|
||||
- }
|
||||
- // verify that the new table region was filled in
|
||||
- for (var i = 0; i < tableSize; i++) {
|
||||
- assert(table.get(oldTableSize + i) !== undefined, 'table entry was not filled in');
|
||||
- }
|
||||
-#endif
|
||||
- var exports = {};
|
||||
- for (var e in instance.exports) {
|
||||
- var value = instance.exports[e];
|
||||
- if (typeof value === 'object') {
|
||||
- // a breaking change in the wasm spec, globals are now objects
|
||||
- // https://github.com/WebAssembly/mutable-global/issues/1
|
||||
- value = value.value;
|
||||
+ // the table should be unchanged
|
||||
+ assert(table === originalTable);
|
||||
+ assert(table === Module['wasmTable']);
|
||||
+ if (instance.exports['table']) {
|
||||
+ assert(table === instance.exports['table']);
|
||||
+ }
|
||||
+ // the old part of the table should be unchanged
|
||||
+ for (var i = 0; i < oldTableSize; i++) {
|
||||
+ assert(table.get(i) === oldTable[i], 'old table entries must remain the same');
|
||||
+ }
|
||||
+ // verify that the new table region was filled in
|
||||
+ for (var i = 0; i < tableSize; i++) {
|
||||
+ assert(table.get(oldTableSize + i) !== undefined, 'table entry was not filled in');
|
||||
}
|
||||
- if (typeof value === 'number') {
|
||||
- // relocate it - modules export the absolute value, they can't relocate before they export
|
||||
+#endif
|
||||
+ for (var e in instance.exports) {
|
||||
+ var value = instance.exports[e];
|
||||
+ if (typeof value === 'object') {
|
||||
+ // a breaking change in the wasm spec, globals are now objects
|
||||
+ // https://github.com/WebAssembly/mutable-global/issues/1
|
||||
+ value = value.value;
|
||||
+ }
|
||||
+ if (typeof value === 'number') {
|
||||
+ // relocate it - modules export the absolute value, they can't relocate before they export
|
||||
#if EMULATED_FUNCTION_POINTERS
|
||||
- // it may be a function pointer
|
||||
- if (e.substr(0, 3) == 'fp$' && typeof instance.exports[e.substr(3)] === 'function') {
|
||||
- value = value + env['tableBase'];
|
||||
- } else {
|
||||
+ // it may be a function pointer
|
||||
+ if (e.substr(0, 3) == 'fp$' && typeof instance.exports[e.substr(3)] === 'function') {
|
||||
+ value = value + env['tableBase'];
|
||||
+ } else {
|
||||
#endif
|
||||
- value = value + env['memoryBase'];
|
||||
+ value = value + env['memoryBase'];
|
||||
#if EMULATED_FUNCTION_POINTERS
|
||||
- }
|
||||
+ }
|
||||
#endif
|
||||
+ }
|
||||
+ exports[e] = value;
|
||||
}
|
||||
- exports[e] = value;
|
||||
- }
|
||||
- // initialize the module
|
||||
- var init = exports['__post_instantiate'];
|
||||
- if (init) {
|
||||
- if (runtimeInitialized) {
|
||||
- init();
|
||||
- } else {
|
||||
- // we aren't ready to run compiled code yet
|
||||
- __ATINIT__.push(init);
|
||||
+ // initialize the module
|
||||
+ var init = exports['__post_instantiate'];
|
||||
+ if (init) {
|
||||
+ if (runtimeInitialized) {
|
||||
+ init();
|
||||
+ } else {
|
||||
+ // we aren't ready to run compiled code yet
|
||||
+ __ATINIT__.push(init);
|
||||
+ }
|
||||
}
|
||||
+ return exports;
|
||||
+ }
|
||||
+
|
||||
+ if (loadAsync) {
|
||||
+ return WebAssembly.instantiate(binary, info).then(function(result) {
|
||||
+ return postInstantiation(result.instance);
|
||||
+ });
|
||||
+ } else {
|
||||
+ var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), info);
|
||||
+ return postInstantiation(instance);
|
||||
}
|
||||
- return exports;
|
||||
}
|
||||
+Module['loadWebAssemblyModule'] = loadWebAssemblyModule;
|
||||
+
|
||||
#endif // WASM
|
||||
#endif // RELOCATABLE
|
||||
|
|
@ -79,6 +79,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
|
|||
Module.noAudioDecoding = true;
|
||||
let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
if (isFirefox) {
|
||||
console.log("Skipping wasm decoding");
|
||||
Module.noWasmDecoding = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -142,6 +142,12 @@ EM_JS(int, pyproxy_init, (), {
|
|||
isPyProxy: function(jsobj) {
|
||||
return jsobj['$$'] !== undefined && jsobj['$$']['type'] === 'PyProxy';
|
||||
},
|
||||
addExtraKeys: function(result) {
|
||||
result.push('toString');
|
||||
result.push('prototype');
|
||||
result.push('arguments');
|
||||
result.push('caller');
|
||||
},
|
||||
isExtensible: function() { return true },
|
||||
has: function (jsobj, jskey) {
|
||||
ptrobj = this.getPtr(jsobj);
|
||||
|
@ -197,8 +203,7 @@ EM_JS(int, pyproxy_init, (), {
|
|||
var idresult = __pyproxy_ownKeys(ptrobj);
|
||||
var jsresult = Module.hiwire_get_value(idresult);
|
||||
Module.hiwire_decref(idresult);
|
||||
jsresult.push('toString');
|
||||
jsresult.push('prototype');
|
||||
this.addExtraKeys(jsresult);
|
||||
return jsresult;
|
||||
},
|
||||
enumerate: function (jsobj) {
|
||||
|
@ -206,8 +211,7 @@ EM_JS(int, pyproxy_init, (), {
|
|||
var idresult = __pyproxy_enumerate(ptrobj);
|
||||
var jsresult = Module.hiwire_get_value(idresult);
|
||||
Module.hiwire_decref(idresult);
|
||||
jsresult.push('toString');
|
||||
jsresult.push('prototype');
|
||||
this.addExtraKeys(jsresult);
|
||||
return jsresult;
|
||||
},
|
||||
apply: function (jsobj, jsthis, jsargs) {
|
||||
|
|
108
test/conftest.py
108
test/conftest.py
|
@ -2,7 +2,12 @@
|
|||
Various common utilities for testing.
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import multiprocessing
|
||||
import os
|
||||
import pathlib
|
||||
import queue
|
||||
import sys
|
||||
|
||||
try:
|
||||
import pytest
|
||||
|
@ -30,16 +35,11 @@ class PackageLoaded:
|
|||
|
||||
class SeleniumWrapper:
|
||||
def __init__(self):
|
||||
from selenium.webdriver import Firefox
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
|
||||
options = Options()
|
||||
options.add_argument('-headless')
|
||||
driver = Firefox(
|
||||
executable_path='geckodriver', firefox_options=options)
|
||||
driver = self.get_driver()
|
||||
wait = WebDriverWait(driver, timeout=20)
|
||||
driver.get((BUILD_PATH / "test.html").as_uri())
|
||||
driver.get(f'http://127.0.0.1:{PORT}/test.html')
|
||||
wait.until(PyodideInited())
|
||||
self.wait = wait
|
||||
self.driver = driver
|
||||
|
@ -69,12 +69,100 @@ class SeleniumWrapper:
|
|||
yield self.driver.current_url
|
||||
|
||||
|
||||
class FirefoxWrapper(SeleniumWrapper):
|
||||
def get_driver(self):
|
||||
from selenium.webdriver import Firefox
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.common.exceptions import JavascriptException
|
||||
|
||||
options = Options()
|
||||
options.add_argument('-headless')
|
||||
|
||||
self.JavascriptException = JavascriptException
|
||||
|
||||
return Firefox(
|
||||
executable_path='geckodriver', firefox_options=options)
|
||||
|
||||
|
||||
class ChromeWrapper(SeleniumWrapper):
|
||||
def get_driver(self):
|
||||
from selenium.webdriver import Chrome
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
|
||||
options = Options()
|
||||
options.add_argument('--headless')
|
||||
|
||||
self.JavascriptException = WebDriverException
|
||||
|
||||
return Chrome(chrome_options=options)
|
||||
|
||||
|
||||
if pytest is not None:
|
||||
@pytest.fixture
|
||||
def selenium():
|
||||
selenium = SeleniumWrapper()
|
||||
@pytest.fixture(params=['firefox', 'chrome'])
|
||||
def selenium(request):
|
||||
if request.param == 'firefox':
|
||||
cls = FirefoxWrapper
|
||||
elif request.param == 'chrome':
|
||||
cls = ChromeWrapper
|
||||
selenium = cls()
|
||||
try:
|
||||
yield selenium
|
||||
finally:
|
||||
print('\n'.join(str(x) for x in selenium.logs))
|
||||
selenium.driver.quit()
|
||||
|
||||
|
||||
PORT = 0
|
||||
|
||||
|
||||
def spawn_web_server():
|
||||
global PORT
|
||||
|
||||
print("Spawning webserver...")
|
||||
|
||||
q = multiprocessing.Queue()
|
||||
p = multiprocessing.Process(target=run_web_server, args=(q,))
|
||||
|
||||
def shutdown_webserver():
|
||||
q.put("TERMINATE")
|
||||
p.join()
|
||||
atexit.register(shutdown_webserver)
|
||||
|
||||
p.start()
|
||||
PORT = q.get()
|
||||
|
||||
|
||||
def run_web_server(q):
|
||||
import http.server
|
||||
import socketserver
|
||||
|
||||
print("Running webserver...")
|
||||
|
||||
os.chdir(BUILD_PATH)
|
||||
Handler = http.server.SimpleHTTPRequestHandler
|
||||
Handler.extensions_map['.wasm'] = 'application/wasm'
|
||||
|
||||
def dummy_log(*args, **kwargs):
|
||||
pass
|
||||
Handler.log_message = dummy_log
|
||||
|
||||
with socketserver.TCPServer(("", 0), Handler) as httpd:
|
||||
host, port = httpd.server_address
|
||||
print("serving at port", port)
|
||||
q.put(port)
|
||||
|
||||
def service_actions():
|
||||
try:
|
||||
if q.get(False) == "TERMINATE":
|
||||
sys.exit(0)
|
||||
httpd.shutdown()
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
httpd.service_actions = service_actions
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
if multiprocessing.current_process().name == 'MainProcess':
|
||||
spawn_web_server()
|
||||
|
|
|
@ -2,8 +2,6 @@ import os
|
|||
import pathlib
|
||||
import time
|
||||
|
||||
from selenium.common.exceptions import JavascriptException
|
||||
|
||||
|
||||
def test_init(selenium):
|
||||
assert 'Python initialization complete' in selenium.logs
|
||||
|
@ -50,7 +48,7 @@ def test_python2js(selenium):
|
|||
def test_pythonexc2js(selenium):
|
||||
try:
|
||||
selenium.run_js('return pyodide.runPython("5 / 0")')
|
||||
except JavascriptException as e:
|
||||
except selenium.JavascriptException as e:
|
||||
assert('ZeroDivisionError' in str(e))
|
||||
else:
|
||||
assert False, 'Expected exception'
|
||||
|
@ -157,7 +155,7 @@ def test_pyproxy(selenium):
|
|||
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
|
||||
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
|
||||
'__str__', '__subclasshook__', '__weakref__', 'bar', 'baz',
|
||||
'get_value', 'toString', 'prototype'])
|
||||
'get_value', 'toString', 'prototype', 'arguments', 'caller'])
|
||||
assert selenium.run("hasattr(f, 'baz')")
|
||||
selenium.run_js("delete pyodide.pyimport('f').baz")
|
||||
assert not selenium.run("hasattr(f, 'baz')")
|
||||
|
@ -179,7 +177,7 @@ def test_pyproxy_destroy(selenium):
|
|||
"console.assert(f.get_value(1) === 64);\n"
|
||||
"f.destroy();\n"
|
||||
"f.get_value();\n")
|
||||
except JavascriptException as e:
|
||||
except selenium.JavascriptException as e:
|
||||
assert 'Object has already been destroyed' in str(e)
|
||||
else:
|
||||
assert False, 'Expected exception'
|
||||
|
@ -260,7 +258,7 @@ def test_jsproxy_iter(selenium):
|
|||
def test_open_url(selenium):
|
||||
assert selenium.run(
|
||||
"import pyodide\n"
|
||||
"pyodide.open_url('../test/data.txt').read()\n") == 'HELLO\n'
|
||||
"pyodide.open_url('test_data.txt').read()\n") == 'HELLO\n'
|
||||
|
||||
|
||||
def test_run_core_python_test(python_test, selenium):
|
||||
|
@ -269,7 +267,7 @@ def test_run_core_python_test(python_test, selenium):
|
|||
selenium.run(
|
||||
"from test.libregrtest import main\n"
|
||||
"main(['{}'], verbose=True, verbose3=True)".format(python_test))
|
||||
except JavascriptException as e:
|
||||
except selenium.JavascriptException as e:
|
||||
assert str(e).strip().endswith('SystemExit: 0')
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue