summaryrefslogtreecommitdiffstats
path: root/tests/pyodide_testrunner
diff options
context:
space:
mode:
Diffstat (limited to 'tests/pyodide_testrunner')
-rw-r--r--tests/pyodide_testrunner/.gitignore1
-rw-r--r--tests/pyodide_testrunner/index.html56
-rw-r--r--tests/pyodide_testrunner/run.py131
-rw-r--r--tests/pyodide_testrunner/test-runner.js38
4 files changed, 226 insertions, 0 deletions
diff --git a/tests/pyodide_testrunner/.gitignore b/tests/pyodide_testrunner/.gitignore
new file mode 100644
index 0000000..704d307
--- /dev/null
+++ b/tests/pyodide_testrunner/.gitignore
@@ -0,0 +1 @@
+*.whl
diff --git a/tests/pyodide_testrunner/index.html b/tests/pyodide_testrunner/index.html
new file mode 100644
index 0000000..b3e1b8b
--- /dev/null
+++ b/tests/pyodide_testrunner/index.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+ <title>Pygls Testsuite</title>
+
+ <style>
+ @media (prefers-color-scheme: dark) {
+ * {
+ background-color: #222;
+ color: white;
+ }
+
+ }
+ </style>
+</head>
+
+<body>
+ <div>
+ <pre id="console"></pre>
+ </div>
+ <button id="exit-code" disabled></button>
+ <script>
+ let log = document.getElementById("console")
+ let exitCode = document.getElementById("exit-code")
+
+ function print(event) {
+ log.innerText += event.data
+ }
+
+ // Use a web worker to prevent freezing the UI
+ function runTests(whl) {
+ let worker = new Worker(`test-runner.js?whl=${whl}`)
+ worker.addEventListener('message', (event) => {
+
+ if (event.data.exitCode !== undefined) {
+ exitCode.innerText = event.data.exitCode
+ exitCode.disabled = false
+ return
+ }
+
+ print(event)
+ })
+ }
+
+ let queryParams = new URLSearchParams(window.location.search)
+ runTests(queryParams.get('whl'))
+
+ </script>
+</body>
+
+</html>
diff --git a/tests/pyodide_testrunner/run.py b/tests/pyodide_testrunner/run.py
new file mode 100644
index 0000000..7b56374
--- /dev/null
+++ b/tests/pyodide_testrunner/run.py
@@ -0,0 +1,131 @@
+import os
+import pathlib
+import shutil
+import subprocess
+import sys
+import tempfile
+
+from functools import partial
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from multiprocessing import Process, Queue
+
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import WebDriverException
+from selenium.webdriver.support import expected_conditions as EC
+
+
+# Path to the root of the repo.
+REPO = pathlib.Path(__file__).parent.parent.parent
+BROWSERS = {
+ "chrome": (webdriver.Chrome, webdriver.ChromeOptions),
+ "firefox": (webdriver.Firefox, webdriver.FirefoxOptions),
+}
+
+
+def build_wheel() -> str:
+ """Build a wheel package of ``pygls`` and its testsuite.
+
+ In order to test pygls under pyodide, we need to load the code for both pygls and its
+ testsuite. This is done by building a wheel.
+
+ To avoid messing with the repo this is all done under a temp directory.
+ """
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ # Copy all required files.
+ dest = pathlib.Path(tmpdir)
+
+ # So that we don't have to fuss with packaging, copy the test suite into `pygls`
+ # as a sub module.
+ directories = [("pygls", "pygls"), ("tests", "pygls/tests")]
+
+ for src, target in directories:
+ shutil.copytree(REPO / src, dest / target)
+
+ files = ["pyproject.toml", "poetry.lock", "README.md", "ThirdPartyNotices.txt"]
+
+ for src in files:
+ shutil.copy(REPO / src, dest)
+
+ # Convert the lock file to requirements.txt.
+ # Ensures reproducible behavour for testing.
+ subprocess.run(
+ [
+ "poetry",
+ "export",
+ "-f",
+ "requirements.txt",
+ "--output",
+ "requirements.txt",
+ ],
+ cwd=dest,
+ )
+ subprocess.run(
+ ["poetry", "run", "pip", "install", "-r", "requirements.txt"], cwd=dest
+ )
+ # Build the wheel
+ subprocess.run(["poetry", "build", "--format", "wheel"], cwd=dest)
+ whl = list((dest / "dist").glob("*.whl"))[0]
+ shutil.copy(whl, REPO / "tests/pyodide_testrunner")
+
+ return whl.name
+
+
+def spawn_http_server(q: Queue, directory: str):
+ """A http server is needed to serve the files to the browser."""
+
+ handler_class = partial(SimpleHTTPRequestHandler, directory=directory)
+ server = ThreadingHTTPServer(("localhost", 0), handler_class)
+ q.put(server.server_port)
+
+ server.serve_forever()
+
+
+def main():
+ exit_code = 1
+ whl = build_wheel()
+
+ q = Queue()
+ server_process = Process(
+ target=spawn_http_server,
+ args=(q, REPO / "tests/pyodide_testrunner"),
+ daemon=True,
+ )
+ server_process.start()
+ port = q.get()
+
+ print("Running tests...")
+ try:
+ driver_cls, options_cls = BROWSERS[os.environ.get("BROWSER", "chrome")]
+
+ options = options_cls()
+ if "CI" in os.environ:
+ options.binary_location = "/usr/bin/google-chrome"
+ options.add_argument("--headless")
+
+ driver = driver_cls(options=options)
+ driver.get(f"http://localhost:{port}?whl={whl}")
+
+ wait = WebDriverWait(driver, 120)
+ try:
+ button = wait.until(EC.element_to_be_clickable((By.ID, "exit-code")))
+ exit_code = int(button.text)
+ except WebDriverException as e:
+ print(f"Error while running test: {e!r}")
+ exit_code = 1
+
+ console = driver.find_element(By.ID, "console")
+ print(console.text)
+ finally:
+ if hasattr(server_process, "kill"):
+ server_process.kill()
+ else:
+ server_process.terminate()
+
+ return exit_code
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tests/pyodide_testrunner/test-runner.js b/tests/pyodide_testrunner/test-runner.js
new file mode 100644
index 0000000..dbbc01f
--- /dev/null
+++ b/tests/pyodide_testrunner/test-runner.js
@@ -0,0 +1,38 @@
+importScripts("https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js")
+
+// Used to redirect pyodide's stdout to the webpage.
+function patchedStdout(...args) {
+ postMessage(args[0])
+}
+
+async function runTests(whl) {
+ console.log("Loading pyodide")
+ let pyodide = await loadPyodide({
+ indexURL: "https://cdn.jsdelivr.net/pyodide/v0.21.3/full/"
+ })
+
+ console.log("Installing dependencies")
+ await pyodide.loadPackage("micropip")
+ await pyodide.runPythonAsync(`
+ import sys
+ import micropip
+
+ await micropip.install('pytest')
+ await micropip.install('pytest-asyncio')
+ await micropip.install('${whl}')
+ `)
+
+ console.log('Running testsuite')
+
+ // Patch stdout to redirect the output.
+ pyodide.globals.get('sys').stdout.write = patchedStdout
+ await pyodide.runPythonAsync(`
+ import pytest
+ exit_code = pytest.main(['--color', 'no', '--pyargs', 'pygls.tests'])
+ `)
+
+ postMessage({ exitCode: pyodide.globals.get('exit_code') })
+}
+
+let queryParams = new URLSearchParams(self.location.search)
+runTests(queryParams.get('whl'))