Commit 44a5070e authored by Michael Droettboom's avatar Michael Droettboom Committed by GitHub

Merge pull request #145 from mdboom/sequence-promises

Ensure that package loading runs in sequence
parents af569129 3f11d15b
...@@ -18,6 +18,7 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -18,6 +18,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
// Package loading // Package loading
var packages = undefined; var packages = undefined;
let loadedPackages = new Array(); let loadedPackages = new Array();
var loadPackagePromise = new Promise((resolve) => resolve());
let _uri_to_package_name = (package_uri) => { let _uri_to_package_name = (package_uri) => {
// Generate a unique package name from URI // Generate a unique package name from URI
...@@ -33,7 +34,7 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -33,7 +34,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
} }
}; };
let loadPackage = (names) => { let _loadPackage = (names) => {
// DFS to find all dependencies of the requested packages // DFS to find all dependencies of the requested packages
let packages = window.pyodide.packages.dependencies; let packages = window.pyodide.packages.dependencies;
let queue = [].concat(names || []); let queue = [].concat(names || []);
...@@ -44,24 +45,25 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -44,24 +45,25 @@ var languagePluginLoader = new Promise((resolve, reject) => {
const package = _uri_to_package_name(package_uri); const package = _uri_to_package_name(package_uri);
if (package == null) { if (package == null) {
throw new Error(`Invalid package name or URI '${package_uri}'`); console.error(`Invalid package name or URI '${package_uri}'`);
return;
} else if (package == package_uri) { } else if (package == package_uri) {
package_uri = 'default channel'; package_uri = 'default channel';
} }
if (package in loadedPackages) { if (package in loadedPackages) {
if (package_uri != loadedPackages[package]) { if (package_uri != loadedPackages[package]) {
throw new Error( console.error(`URI mismatch, attempting to load package ` +
`URI mismatch, attempting to load package ` + `${package} from ${package_uri} while it is already ` +
`${package} from ${package_uri} while it is already ` + `loaded from ${loadedPackages[package]}!`);
`loaded from ${loadedPackages[package]}!`); return;
} }
} else if (package in toLoad) { } else if (package in toLoad) {
if (package_uri != toLoad[package]) { if (package_uri != toLoad[package]) {
throw new Error( console.error(`URI mismatch, attempting to load package ` +
`URI mismatch, attempting to load package ` + `${package} from ${package_uri} while it is already ` +
`${package} from ${package_uri} while it is already ` + `being loaded from ${toLoad[package]}!`);
`being loaded from ${toLoad[package]}!`); return;
} }
} else { } else {
console.log(`Loading ${package} from ${package_uri}`); console.log(`Loading ${package} from ${package_uri}`);
...@@ -121,6 +123,13 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -121,6 +123,13 @@ var languagePluginLoader = new Promise((resolve, reject) => {
return promise; return promise;
}; };
let loadPackage = (names) => {
/* We want to make sure that only one loadPackage invocation runs at any
* given time, so this creates a "chain" of promises. */
loadPackagePromise = loadPackagePromise.then(() => _loadPackage(names));
return loadPackagePromise;
};
function fixRecursionLimit(pyodide) { function fixRecursionLimit(pyodide) {
// The Javascript/Wasm call stack may be too small to handle the default // The Javascript/Wasm call stack may be too small to handle the default
// Python call stack limit of 1000 frames. This is generally the case on // Python call stack limit of 1000 frames. This is generally the case on
......
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
console.info = function(message) { console.info = function(message) {
window.logs.push(message); window.logs.push(message);
} }
console.error = function(message) {
window.logs.push(message);
}
</script> </script>
<script src="pyodide_dev.js"></script> <script src="pyodide_dev.js"></script>
</head> </head>
......
...@@ -91,12 +91,15 @@ class SeleniumWrapper: ...@@ -91,12 +91,15 @@ class SeleniumWrapper:
return self.driver.execute_script(catch) return self.driver.execute_script(catch)
def load_package(self, packages): def load_package(self, packages):
from selenium.common.exceptions import TimeoutException
self.run_js( self.run_js(
'window.done = false\n' + 'window.done = false\n' +
'pyodide.loadPackage({!r})'.format(packages) + 'pyodide.loadPackage({!r})'.format(packages) +
'.then(function() { window.done = true; })') '.finally(function() { window.done = true; })')
self.wait_until_packages_loaded()
def wait_until_packages_loaded(self):
from selenium.common.exceptions import TimeoutException
try: try:
self.wait.until(PackageLoaded()) self.wait.until(PackageLoaded())
except TimeoutException as exc: except TimeoutException as exc:
......
import pytest import pytest
from selenium.common.exceptions import WebDriverException
def test_load_from_url(selenium_standalone, web_server): def test_load_from_url(selenium_standalone, web_server):
...@@ -18,22 +17,19 @@ def test_load_from_url(selenium_standalone, web_server): ...@@ -18,22 +17,19 @@ def test_load_from_url(selenium_standalone, web_server):
def test_uri_mismatch(selenium_standalone): def test_uri_mismatch(selenium_standalone):
selenium_standalone.load_package('pyparsing') selenium_standalone.load_package('pyparsing')
with pytest.raises(WebDriverException, selenium_standalone.load_package('http://some_url/pyparsing.js')
match="URI mismatch, attempting " assert ("URI mismatch, attempting to load package pyparsing" in
"to load package pyparsing"): selenium_standalone.logs)
selenium_standalone.load_package('http://some_url/pyparsing.js')
assert "Invalid package name or URI" not in selenium_standalone.logs assert "Invalid package name or URI" not in selenium_standalone.logs
def test_invalid_package_name(selenium): def test_invalid_package_name(selenium):
with pytest.raises(WebDriverException, selenium.load_package('wrong name+$')
match="Invalid package name or URI"): assert "Invalid package name or URI" in selenium.logs
selenium.load_package('wrong name+$')
selenium.clean_logs() selenium.clean_logs()
with pytest.raises(WebDriverException, selenium.load_package('tcp://some_url')
match="Invalid package name or URI"): assert "Invalid package name or URI" in selenium.logs
selenium.load_package('tcp://some_url')
@pytest.mark.parametrize('packages', [['pyparsing', 'pytz'], @pytest.mark.parametrize('packages', [['pyparsing', 'pytz'],
...@@ -44,7 +40,29 @@ def test_load_packages_multiple(selenium_standalone, packages): ...@@ -44,7 +40,29 @@ def test_load_packages_multiple(selenium_standalone, packages):
selenium.load_package(packages) selenium.load_package(packages)
selenium.run(f'import {packages[0]}') selenium.run(f'import {packages[0]}')
selenium.run(f'import {packages[1]}') selenium.run(f'import {packages[1]}')
# The long must show that each package is loaded exactly once, # The log must show that each package is loaded exactly once,
# including when one package is a dependency of the other
# ('pyparsing' and 'matplotlib')
assert selenium.logs.count(f'Loading {packages[0]}') == 1
assert selenium.logs.count(f'Loading {packages[1]}') == 1
@pytest.mark.parametrize('packages', [['pyparsing', 'pytz'],
['pyparsing', 'matplotlib']],
ids='-'.join)
def test_load_packages_sequential(selenium_standalone, packages):
selenium = selenium_standalone
promises = ','.join(
'pyodide.loadPackage("{}")'.format(x) for x in packages
)
selenium.run_js(
'window.done = false\n' +
'Promise.all([{}])'.format(promises) +
'.finally(function() { window.done = true; })')
selenium.wait_until_packages_loaded()
selenium.run(f'import {packages[0]}')
selenium.run(f'import {packages[1]}')
# The log must show that each package is loaded exactly once,
# including when one package is a dependency of the other # including when one package is a dependency of the other
# ('pyparsing' and 'matplotlib') # ('pyparsing' and 'matplotlib')
assert selenium.logs.count(f'Loading {packages[0]}') == 1 assert selenium.logs.count(f'Loading {packages[0]}') == 1
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment