summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/test/emscripten
diff options
context:
space:
mode:
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/emscripten')
-rwxr-xr-xsrc/contrib/SDL-3.2.20/test/emscripten/driver.py184
-rw-r--r--src/contrib/SDL-3.2.20/test/emscripten/joystick-pre.js25
-rw-r--r--src/contrib/SDL-3.2.20/test/emscripten/pre.js54
-rwxr-xr-xsrc/contrib/SDL-3.2.20/test/emscripten/server.py102
4 files changed, 365 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/test/emscripten/driver.py b/src/contrib/SDL-3.2.20/test/emscripten/driver.py
new file mode 100755
index 0000000..ee91610
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/test/emscripten/driver.py
@@ -0,0 +1,184 @@
1#!/usr/bin/env python
2
3import argparse
4import contextlib
5import logging
6import os
7import pathlib
8import shlex
9import sys
10import time
11from typing import Optional
12import urllib.parse
13
14from selenium import webdriver
15import selenium.common.exceptions
16from selenium.webdriver.common.by import By
17from selenium.webdriver.support.ui import WebDriverWait
18
19
20logger = logging.getLogger(__name__)
21
22
23class SDLSeleniumTestDriver:
24 def __init__(self, server: str, test: str, arguments: list[str], browser: str, firefox_binary: Optional[str]=None, chrome_binary: Optional[str]=None):
25 self. server = server
26 self.test = test
27 self.arguments = arguments
28 self.chrome_binary = chrome_binary
29 self.firefox_binary = firefox_binary
30 self.driver = None
31 self.stdout_printed = False
32 self.failed_messages: list[str] = []
33 self.return_code = None
34
35 options = [
36 "--headless",
37 ]
38
39 driver_contructor = None
40 match browser:
41 case "firefox":
42 driver_contructor = webdriver.Firefox
43 driver_options = webdriver.FirefoxOptions()
44 if self.firefox_binary:
45 driver_options.binary_location = self.firefox_binary
46 case "chrome":
47 driver_contructor = webdriver.Chrome
48 driver_options = webdriver.ChromeOptions()
49 if self.chrome_binary:
50 driver_options.binary_location = self.chrome_binary
51 options.append("--no-sandbox")
52 if driver_contructor is None:
53 raise ValueError(f"Invalid {browser=}")
54 for o in options:
55 driver_options.add_argument(o)
56 logger.debug("About to create driver")
57 self.driver = driver_contructor(options=driver_options)
58
59 @property
60 def finished(self):
61 return len(self.failed_messages) > 0 or self.return_code is not None
62
63 def __del__(self):
64 if self.driver:
65 self.driver.quit()
66
67 @property
68 def url(self):
69 req = {
70 "loghtml": "1",
71 "SDL_ASSERT": "abort",
72 }
73 for key, value in os.environ.items():
74 if key.startswith("SDL_"):
75 req[key] = value
76 req.update({f"arg_{i}": a for i, a in enumerate(self.arguments, 1) })
77 req_str = urllib.parse.urlencode(req)
78 return f"{self.server}/{self.test}.html?{req_str}"
79
80 @contextlib.contextmanager
81 def _selenium_catcher(self):
82 try:
83 yield
84 success = True
85 except selenium.common.exceptions.UnexpectedAlertPresentException as e:
86 # FIXME: switch context, verify text of dialog and answer "a" for abort
87 wait = WebDriverWait(self.driver, timeout=2)
88 try:
89 alert = wait.until(lambda d: d.switch_to.alert)
90 except selenium.common.exceptions.NoAlertPresentException:
91 self.failed_messages.append(e.msg)
92 return False
93 self.failed_messages.append(alert)
94 if "Assertion failure" in e.msg and "[ariA]" in e.msg:
95 alert.send_keys("a")
96 alert.accept()
97 else:
98 self.failed_messages.append(e.msg)
99 success = False
100 return success
101
102 def get_stdout_and_print(self):
103 if self.stdout_printed:
104 return
105 with self._selenium_catcher():
106 div_terminal = self.driver.find_element(by=By.ID, value="terminal")
107 assert div_terminal
108 text = div_terminal.text
109 print(text)
110 self.stdout_printed = True
111
112 def update_return_code(self):
113 with self._selenium_catcher():
114 div_process_quit = self.driver.find_element(by=By.ID, value="process-quit")
115 if not div_process_quit:
116 return
117 if div_process_quit.text != "":
118 try:
119 self.return_code = int(div_process_quit.text)
120 except ValueError:
121 raise ValueError(f"process-quit element contains invalid data: {div_process_quit.text:r}")
122
123 def loop(self):
124 print(f"Connecting to \"{self.url}\"", file=sys.stderr)
125 self.driver.get(url=self.url)
126 self.driver.implicitly_wait(0.2)
127
128 while True:
129 self.update_return_code()
130 if self.finished:
131 break
132 time.sleep(0.1)
133
134 self.get_stdout_and_print()
135 if not self.stdout_printed:
136 self.failed_messages.append("Failed to get stdout/stderr")
137
138
139
140def main() -> int:
141 parser = argparse.ArgumentParser(allow_abbrev=False, description="Selenium SDL test driver")
142 parser.add_argument("--browser", default="firefox", choices=["firefox", "chrome"], help="browser")
143 parser.add_argument("--server", default="http://localhost:8080", help="Server where SDL tests live")
144 parser.add_argument("--verbose", action="store_true", help="Verbose logging")
145 parser.add_argument("--chrome-binary", help="Chrome binary")
146 parser.add_argument("--firefox-binary", help="Firefox binary")
147
148 index_double_dash = sys.argv.index("--")
149 if index_double_dash < 0:
150 parser.error("Missing test arguments. Need -- <FILENAME> <ARGUMENTS>")
151 driver_arguments = sys.argv[1:index_double_dash]
152 test = pathlib.Path(sys.argv[index_double_dash+1]).name
153 test_arguments = sys.argv[index_double_dash+2:]
154
155 args = parser.parse_args(args=driver_arguments)
156
157 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
158
159 logger.debug("driver_arguments=%r test=%r test_arguments=%r", driver_arguments, test, test_arguments)
160
161 sdl_test_driver = SDLSeleniumTestDriver(
162 server=args.server,
163 test=test,
164 arguments=test_arguments,
165 browser=args.browser,
166 chrome_binary=args.chrome_binary,
167 firefox_binary=args.firefox_binary,
168 )
169 sdl_test_driver.loop()
170
171 rc = sdl_test_driver.return_code
172 if sdl_test_driver.failed_messages:
173 for msg in sdl_test_driver.failed_messages:
174 print(f"FAILURE MESSAGE: {msg}", file=sys.stderr)
175 if rc == 0:
176 print(f"Test signaled success (rc=0) but a failure happened", file=sys.stderr)
177 rc = 1
178 sys.stdout.flush()
179 logger.info("Exit code = %d", rc)
180 return rc
181
182
183if __name__ == "__main__":
184 raise SystemExit(main())
diff --git a/src/contrib/SDL-3.2.20/test/emscripten/joystick-pre.js b/src/contrib/SDL-3.2.20/test/emscripten/joystick-pre.js
new file mode 100644
index 0000000..5fd789d
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/test/emscripten/joystick-pre.js
@@ -0,0 +1,25 @@
1Module['arguments'] = ['0'];
2//Gamepads don't appear until a button is pressed and the joystick/gamepad tests expect one to be connected
3Module['preRun'].push(function()
4{
5 Module['print']("Waiting for gamepad...");
6 Module['addRunDependency']("gamepad");
7 window.addEventListener('gamepadconnected', function()
8 {
9 //OK, got one
10 Module['removeRunDependency']("gamepad");
11 }, false);
12
13 //chrome
14 if(!!navigator.webkitGetGamepads)
15 {
16 var timeout = function()
17 {
18 if(navigator.webkitGetGamepads()[0] !== undefined)
19 Module['removeRunDependency']("gamepad");
20 else
21 setTimeout(timeout, 100);
22 }
23 setTimeout(timeout, 100);
24 }
25});
diff --git a/src/contrib/SDL-3.2.20/test/emscripten/pre.js b/src/contrib/SDL-3.2.20/test/emscripten/pre.js
new file mode 100644
index 0000000..74ebd1c
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/test/emscripten/pre.js
@@ -0,0 +1,54 @@
1const searchParams = new URLSearchParams(window.location.search);
2
3Module.preRun = () => {
4};
5
6const arguments = [];
7for (let i = 1; true; i++) {
8 const arg_i = searchParams.get(`arg_${i}`);
9 if (arg_i == null) {
10 break;
11 }
12 arguments.push(arg_i);
13}
14
15Module.arguments = arguments;
16
17if (searchParams.get("loghtml") === "1") {
18 const divTerm = document.createElement("div");
19 divTerm.id = "terminal";
20 document.body.append(divTerm);
21
22 function printToStdOut(msg, id) {
23 const divMsg = document.createElement("div", {class: "stdout"});
24 divMsg.id = id;
25 divMsg.append(document.createTextNode(msg));
26 divTerm.append(divMsg);
27 return divMsg;
28 }
29
30 Module.print = (msg) => {
31 console.log(msg);
32 printToStdOut(msg, "stdout");
33 }
34
35 Module.printErr = (msg) => {
36 console.error(msg);
37 const e = printToStdOut(msg, "stderr");
38 e.style = "color:red";
39 }
40
41 const divQuit = document.createElement("div");
42 divQuit.id = "process-quit";
43 document.body.append(divQuit);
44
45 Module.quit = (msg) => {
46 divQuit.innerText = msg;
47 console.log(`QUIT: ${msg}`)
48 }
49
50 Module.onabort = (msg) => {
51 printToStdOut(`ABORT: ${msg}`, "stderr");
52 console.log(`ABORT: ${msg}`);
53 }
54}
diff --git a/src/contrib/SDL-3.2.20/test/emscripten/server.py b/src/contrib/SDL-3.2.20/test/emscripten/server.py
new file mode 100755
index 0000000..103d164
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/test/emscripten/server.py
@@ -0,0 +1,102 @@
1#!/usr/bin/env python
2
3# Based on http/server.py from Python
4
5from argparse import ArgumentParser
6import contextlib
7from http.server import SimpleHTTPRequestHandler
8from http.server import ThreadingHTTPServer
9import os
10import socket
11
12
13class MyHTTPRequestHandler(SimpleHTTPRequestHandler):
14 extensions_map = {
15 ".manifest": "text/cache-manifest",
16 ".html": "text/html",
17 ".png": "image/png",
18 ".jpg": "image/jpg",
19 ".svg": "image/svg+xml",
20 ".css": "text/css",
21 ".js": "application/x-javascript",
22 ".wasm": "application/wasm",
23 "": "application/octet-stream",
24 }
25
26 def __init__(self, *args, maps=None, **kwargs):
27 self.maps = maps or []
28 SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
29
30 def end_headers(self):
31 self.send_my_headers()
32 SimpleHTTPRequestHandler.end_headers(self)
33
34 def send_my_headers(self):
35 self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
36 self.send_header("Pragma", "no-cache")
37 self.send_header("Expires", "0")
38
39 def translate_path(self, path):
40 for map_path, map_prefix in self.maps:
41 if path.startswith(map_prefix):
42 res = os.path.join(map_path, path.removeprefix(map_prefix).lstrip("/"))
43 break
44 else:
45 res = super().translate_path(path)
46 return res
47
48
49def serve_forever(port: int, ServerClass):
50 handler = MyHTTPRequestHandler
51
52 addr = ("0.0.0.0", port)
53 with ServerClass(addr, handler) as httpd:
54 host, port = httpd.socket.getsockname()[:2]
55 url_host = f"[{host}]" if ":" in host else host
56 print(f"Serving HTTP on {host} port {port} (http://{url_host}:{port}/) ...")
57 try:
58 httpd.serve_forever()
59 except KeyboardInterrupt:
60 print("\nKeyboard interrupt received, exiting.")
61 return 0
62
63
64def main():
65 parser = ArgumentParser(allow_abbrev=False)
66 parser.add_argument("port", nargs="?", type=int, default=8080)
67 parser.add_argument("-d", dest="directory", type=str, default=None)
68 parser.add_argument("--map", dest="maps", nargs="+", type=str, help="Mappings, used as e.g. \"$HOME/projects/SDL:/sdl\"")
69 args = parser.parse_args()
70
71 maps = []
72 for m in args.maps:
73 try:
74 path, uri = m.split(":", 1)
75 except ValueError:
76 parser.error(f"Invalid mapping: \"{m}\"")
77 maps.append((path, uri))
78
79 class DualStackServer(ThreadingHTTPServer):
80 def server_bind(self):
81 # suppress exception when protocol is IPv4
82 with contextlib.suppress(Exception):
83 self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
84 return super().server_bind()
85
86 def finish_request(self, request, client_address):
87 self.RequestHandlerClass(
88 request,
89 client_address,
90 self,
91 directory=args.directory,
92 maps=maps,
93 )
94
95 return serve_forever(
96 port=args.port,
97 ServerClass=DualStackServer,
98 )
99
100
101if __name__ == "__main__":
102 raise SystemExit(main())