From 161761653f9f97bf186bd1dff0a0404bdca49043 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Thu, 30 May 2024 21:08:30 +0200 Subject: [PATCH] test: use Selenium to run Emscripten tests --- .github/workflows/create-test-plan.py | 27 +++- .github/workflows/generic.yml | 19 ++- test/CMakeLists.txt | 94 ++++++++++---- test/emscripten/driver.py | 179 ++++++++++++++++++++++++++ test/emscripten/pre.js | 54 ++++++++ 5 files changed, 341 insertions(+), 32 deletions(-) create mode 100755 test/emscripten/driver.py create mode 100644 test/emscripten/pre.js diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 99b3a7db96..340514c3bc 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -175,6 +175,7 @@ class JobDetails: test_pkg_config: bool = True cc_from_cmake: bool = False source_cmd: str = "" + pretest_cmd: str = "" java: bool = False android_apks: list[str] = dataclasses.field(default_factory=list) android_ndk: bool = False @@ -224,6 +225,7 @@ class JobDetails: "no-cmake": self.no_cmake, "build-tests": self.build_tests, "source-cmd": self.source_cmd, + "pretest-cmd": self.pretest_cmd, "cmake-config-emulator": self.cmake_config_emulator, "cc": self.cc, "cxx": self.cxx, @@ -484,11 +486,30 @@ def spec_to_job(spec: JobSpec) -> JobDetails: "testsprite-apk", ] case SdlPlatform.Emscripten: - job.run_tests = False + job.clang_tidy = False # clang-tidy does not understand -gsource-map job.shared = False job.cmake_config_emulator = "emcmake" job.cmake_build_type = "Debug" job.test_pkg_config = False + job.apt_packages.append("python3-selenium") + job.cmake_arguments.extend(( + "-DSDLTEST_BROWSER=chrome", + "-DSDLTEST_TIMEOUT_MULTIPLIER=4", + "-DSDLTEST_CHROME_BINARY=${CHROME_BINARY}", + )) + job.cflags.extend(( + "-gsource-map", + "-ffile-prefix-map=${PWD}=/SDL", + )) + job.ldflags.extend(( + "--source-map-base", "/", + )) + job.pretest_cmd = "\n".join([ + "# Start local HTTP server", + "cmake --build build --target serve-sdl-tests --verbose &", + "chrome --version", + "chromedriver --version", + ]) case SdlPlatform.Ps2: build_parallel = False job.shared = False @@ -623,9 +644,9 @@ def spec_to_job(spec: JobSpec) -> JobDetails: if not build_parallel: job.cmake_build_arguments.append("-j1") if job.cflags: - job.cmake_arguments.append(f"-DCMAKE_C_FLAGS={my_shlex_join(job.cflags)}") + job.cmake_arguments.append(f"-DCMAKE_C_FLAGS=\"{my_shlex_join(job.cflags)}\"") if job.cxxflags: - job.cmake_arguments.append(f"-DCMAKE_CXX_FLAGS={my_shlex_join(job.cxxflags)}") + job.cmake_arguments.append(f"-DCMAKE_CXX_FLAGS=\"{my_shlex_join(job.cxxflags)}\"") if job.ldflags: job.cmake_arguments.append(f"-DCMAKE_SHARED_LINKER_FLAGS=\"{my_shlex_join(job.ldflags)}\"") job.cmake_arguments.append(f"-DCMAKE_EXE_LINKER_FLAGS=\"{my_shlex_join(job.ldflags)}\"") diff --git a/.github/workflows/generic.yml b/.github/workflows/generic.yml index 2798ec7cda..da916b3d0d 100644 --- a/.github/workflows/generic.yml +++ b/.github/workflows/generic.yml @@ -47,6 +47,22 @@ jobs: if: ${{ matrix.platform.platform == 'emscripten' }} with: version: 3.1.35 + - uses: browser-actions/setup-chrome@v1 + id: setup-chrome + if: ${{ matrix.platform.platform == 'emscripten' }} + with: + install-chromedriver: true + - name: 'Add chrome to PATH' + if: ${{ matrix.platform.platform == 'emscripten' }} + run: | + chrome_dir="$(dirname "${{ steps.setup-chrome.outputs.chrome-path }}")" + chromedriver_dir="$(dirname "${{ steps.setup-chrome.outputs.chromedriver-path }}")" + echo "CHROME_BINARY=${{ steps.setup-chrome.outputs.chrome-path }}" >>$GITHUB_ENV + echo "CHROMEDRIVER_BINARY=${{ steps.setup-chrome.outputs.chromedriver-path }}" >>$GITHUB_ENV + echo "chrome_dir=${chrome_dir}" + echo "chromedriver_dir=${chromedriver_dir}" + echo "${chrome_dir}" >>${GITHUB_PATH} + echo "${chromedriver_dir}" >>${GITHUB_PATH} - uses: nttld/setup-ndk@v1 if: ${{ matrix.platform.android-ndk }} id: setup-ndk @@ -115,7 +131,7 @@ jobs: with: type: ${{ matrix.platform.setup-vita-gles-type }} - - name: 'Pollute toolchain with "bad SDL headers' + - name: 'Pollute toolchain with "bad" SDL headers' if: ${{ matrix.platform.pollute-directories != '' }} #shell: ${{ matrix.platform.shell }} run: | @@ -169,6 +185,7 @@ jobs: # shell: ${{ matrix.platform.shell }} run: | ${{ matrix.platform.source-cmd }} + ${{ matrix.platform.pretest-cmd }} set -eu export SDL_TESTS_QUICK=1 ctest -VV --test-dir build/ -j2 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 12314f5c78..4252988534 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,6 +52,20 @@ if(WIN32 AND NOT WINDOWS_STORE) endif() endif() +if(EMSCRIPTEN) + set(SDLTEST_BROWSER "firefox" CACHE STRING "Browser in which to run SDL unit tests (chrome or firefox)") + set(SDLTEST_PORT "8080" CACHE STRING "Port on which to serve the tests") + set(SDLTEST_CHROME_BINARY "" CACHE STRING "Chrome/Chromium browser binary (optional)") + find_package(Python3 COMPONENTS Interpreter) + if(TARGET Python3::Interpreter) + add_custom_target(serve-sdl-tests + COMMAND Python3::Interpreter "${CMAKE_CURRENT_SOURCE_DIR}/emscripten/server.py" + "${SDLTEST_PORT}" + -d "${CMAKE_CURRENT_BINARY_DIR}" + --map "${CMAKE_CURRENT_SOURCE_DIR}/..:/SDL") + endif() +endif() + if(CMAKE_RUNTIME_OUTPUT_DIRECTORY) set(test_bin_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") if(NOT IS_ABSOLUTE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") @@ -99,7 +113,7 @@ if(WINDOWS_STORE) endif() macro(add_sdl_test_executable TARGET) - cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;NO_C90;MAIN_CALLBACKS;NOTRACKMEM" "" "NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;SOURCES" ${ARGN}) + cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;THREADS;NO_C90;MAIN_CALLBACKS;NOTRACKMEM" "" "DISABLE_THREADS_ARGS;NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;SOURCES" ${ARGN}) if(AST_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown argument(s): ${AST_UNPARSED_ARGUMENTS}") endif() @@ -157,6 +171,8 @@ macro(add_sdl_test_executable TARGET) if(AST_NONINTERACTIVE) set_property(TARGET ${TARGET} PROPERTY SDL_NONINTERACTIVE 1) endif() + set_property(TARGET ${TARGET} PROPERTY SDL_DISABLE_THREADS_ARGS "${AST_DISABLE_THREADS_ARGS}") + set_property(TARGET ${TARGET} PROPERTY SDL_THREADS "${AST_THREADS}") if(AST_NONINTERACTIVE_ARGS) set_property(TARGET ${TARGET} PROPERTY SDL_NONINTERACTIVE_ARGUMENTS "${AST_NONINTERACTIVE_ARGS}") endif() @@ -221,6 +237,9 @@ macro(add_sdl_test_executable TARGET) if(EMSCRIPTEN) set_property(TARGET ${TARGET} PROPERTY SUFFIX ".html") + target_link_options(${TARGET} PRIVATE "SHELL:--pre-js ${CMAKE_CURRENT_SOURCE_DIR}/emscripten/pre.js") + target_link_options(${TARGET} PRIVATE "-sEXIT_RUNTIME=1") + set_property(TARGET ${TARGET} APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/emscripten/pre.js") endif() if(OPENGL_FOUND) @@ -279,17 +298,20 @@ add_sdl_test_executable(testaudiostreamdynamicresample NEEDS_RESOURCES TESTUTILS file(GLOB TESTAUTOMATION_SOURCE_FILES testautomation*.c) add_sdl_test_executable(testautomation NONINTERACTIVE NONINTERACTIVE_TIMEOUT 120 NEEDS_RESOURCES NO_C90 SOURCES ${TESTAUTOMATION_SOURCE_FILES}) +if(EMSCRIPTEN) + target_link_options(testautomation PRIVATE -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=1gb) +endif() add_sdl_test_executable(testmultiaudio NEEDS_RESOURCES TESTUTILS SOURCES testmultiaudio.c) add_sdl_test_executable(testaudiohotplug NEEDS_RESOURCES TESTUTILS SOURCES testaudiohotplug.c) add_sdl_test_executable(testaudiorecording MAIN_CALLBACKS SOURCES testaudiorecording.c) -add_sdl_test_executable(testatomic NONINTERACTIVE SOURCES testatomic.c) +add_sdl_test_executable(testatomic NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" SOURCES testatomic.c) add_sdl_test_executable(testintersections SOURCES testintersections.c) add_sdl_test_executable(testrelative SOURCES testrelative.c) add_sdl_test_executable(testhittesting SOURCES testhittesting.c) add_sdl_test_executable(testdraw SOURCES testdraw.c) add_sdl_test_executable(testdrawchessboard SOURCES testdrawchessboard.c) add_sdl_test_executable(testdropfile MAIN_CALLBACKS SOURCES testdropfile.c) -add_sdl_test_executable(testerror NONINTERACTIVE SOURCES testerror.c) +add_sdl_test_executable(testerror NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" SOURCES testerror.c) set(build_options_dependent_tests ) @@ -320,7 +342,7 @@ elseif(HAVE_X11 OR HAVE_WAYLAND) endif () endif() -find_package(Python3) +find_package(Python3 COMPONENTS Interpreter) function(files2headers OUTPUT) set(xxd "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/xxd.py") set(inputs ${ARGN}) @@ -335,7 +357,7 @@ function(files2headers OUTPUT) # Don't add the 'output' header to the output, to avoid marking them as GENERATED # (generated files are removed when running the CLEAN target) add_custom_command(OUTPUT "${intermediate}" - COMMAND "${Python3_EXECUTABLE}" "${xxd}" -i "${CMAKE_CURRENT_SOURCE_DIR}/${input}" "-o" "${intermediate}" + COMMAND Python3::Interpreter "${xxd}" -i "${CMAKE_CURRENT_SOURCE_DIR}/${input}" "-o" "${intermediate}" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${intermediate}" "${output}" DEPENDS "${xxd}" "${bmp}" ) @@ -384,7 +406,7 @@ add_sdl_test_executable(testhaptic SOURCES testhaptic.c) add_sdl_test_executable(testhotplug SOURCES testhotplug.c) add_sdl_test_executable(testpen SOURCES testpen.c) add_sdl_test_executable(testrumble SOURCES testrumble.c) -add_sdl_test_executable(testthread NONINTERACTIVE NONINTERACTIVE_TIMEOUT 40 SOURCES testthread.c) +add_sdl_test_executable(testthread NONINTERACTIVE THREADS NONINTERACTIVE_TIMEOUT 40 SOURCES testthread.c) add_sdl_test_executable(testiconv NEEDS_RESOURCES TESTUTILS SOURCES testiconv.c) add_sdl_test_executable(testime NEEDS_RESOURCES TESTUTILS SOURCES testime.c) add_sdl_test_executable(testkeys SOURCES testkeys.c) @@ -403,7 +425,7 @@ if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4) endif() add_sdl_test_executable(testrendertarget NEEDS_RESOURCES TESTUTILS SOURCES testrendertarget.c) add_sdl_test_executable(testscale NEEDS_RESOURCES TESTUTILS SOURCES testscale.c) -add_sdl_test_executable(testsem NONINTERACTIVE NONINTERACTIVE_ARGS 10 NONINTERACTIVE_TIMEOUT 30 SOURCES testsem.c) +add_sdl_test_executable(testsem NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" NONINTERACTIVE_ARGS 10 NONINTERACTIVE_TIMEOUT 30 SOURCES testsem.c) add_sdl_test_executable(testsensor SOURCES testsensor.c) add_sdl_test_executable(testshader NEEDS_RESOURCES TESTUTILS SOURCES testshader.c) if(EMSCRIPTEN) @@ -421,7 +443,7 @@ add_sdl_test_executable(testcamera MAIN_CALLBACKS SOURCES testcamera.c) add_sdl_test_executable(testviewport NEEDS_RESOURCES TESTUTILS SOURCES testviewport.c) add_sdl_test_executable(testwm SOURCES testwm.c) add_sdl_test_executable(testyuv NONINTERACTIVE NONINTERACTIVE_ARGS "--automated" NEEDS_RESOURCES TESTUTILS SOURCES testyuv.c testyuv_cvt.c) -add_sdl_test_executable(torturethread NONINTERACTIVE NONINTERACTIVE_TIMEOUT 30 SOURCES torturethread.c) +add_sdl_test_executable(torturethread NONINTERACTIVE THREADS NONINTERACTIVE_TIMEOUT 30 SOURCES torturethread.c) add_sdl_test_executable(testrendercopyex NEEDS_RESOURCES TESTUTILS SOURCES testrendercopyex.c) add_sdl_test_executable(testmessage SOURCES testmessage.c) add_sdl_test_executable(testdisplayinfo SOURCES testdisplayinfo.c) @@ -489,15 +511,6 @@ if(OPENGL_FOUND) target_link_libraries(testgl PRIVATE ${OPENGL_gl_LIBRARY}) endif() endif() -if(EMSCRIPTEN) - set_property(TARGET testshader APPEND_STRING PROPERTY LINK_FLAGS " -sLEGACY_GL_EMULATION") - - find_package(Python3 COMPONENTS Interpreter) - if(TARGET Python3::Interpreter) - add_custom_target(serve-sdl-tests - COMMAND Python3::Interpreter "${CMAKE_CURRENT_SOURCE_DIR}/emscripten/server.py" -d "${CMAKE_CURRENT_BINARY_DIR}") - endif() -endif() if(MACOS) target_link_options(testnative PRIVATE "-Wl,-framework,Cocoa") endif() @@ -605,15 +618,29 @@ endif() set(TESTS_ENVIRONMENT SDL_AUDIO_DRIVER=dummy SDL_VIDEO_DRIVER=dummy - PATH=$ ) +set(SDLTEST_TIMEOUT_MULTIPLIER "1" CACHE STRING "SDL test time-out multiplier") + function(add_sdl_test TEST TARGET) cmake_parse_arguments(ast "INSTALL" "" "" ${ARGN}) get_property(noninteractive TARGET ${TARGET} PROPERTY SDL_NONINTERACTIVE) if(noninteractive) - set(command ${TARGET}) + if(EMSCRIPTEN) + if(NOT PYTHON3_EXECUTABLE) + set(PYTHON3_EXECUTABLE "python3") + endif() + set(command "${PYTHON3_EXECUTABLE};${CMAKE_CURRENT_SOURCE_DIR}/emscripten/driver.py;--server;http://localhost:${SDLTEST_PORT};--browser;${SDLTEST_BROWSER}") + if(SDLTEST_CHROME_BINARY) + list(APPEND command "--chrome-binary;${SDLTEST_CHROME_BINARY}") + endif() + list(APPEND command "--;${TARGET}") + else() + set(command ${TARGET}) + endif() get_property(noninteractive_arguments TARGET ${TARGET} PROPERTY SDL_NONINTERACTIVE_ARGUMENTS) + get_property(disable_threads_args TARGET ${TARGET} PROPERTY SDL_DISABLE_THREADS_ARGS) + get_property(uses_threads TARGET ${TARGET} PROPERTY SDL_THREADS) if(noninteractive_arguments) list(APPEND command ${noninteractive_arguments}) endif() @@ -623,20 +650,29 @@ function(add_sdl_test TEST TARGET) list(APPEND command --trackmem) endif() endif() + if(EMSCRIPTEN) + list(APPEND command ${disable_threads_args}) + endif() add_test( - NAME ${TEST} - COMMAND ${command} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + NAME ${TEST} + COMMAND ${command} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + if(WIN32 AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_property(TEST ${TEST} APPEND PROPERTY ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() if(NOT notrackmem) set_property(TEST ${TEST} PROPERTY FAIL_REGULAR_EXPRESSION "Total: [0-9]+\\.[0-9]+ Kb in [1-9][0-9]* allocations") endif() set_tests_properties(${TEST} PROPERTIES ENVIRONMENT "${TESTS_ENVIRONMENT}") + if(EMSCRIPTEN AND uses_threads) + set_tests_properties(${TEST} PROPERTIES DISABLED 1) + endif() get_property(noninteractive_timeout TARGET ${TARGET} PROPERTY SDL_NONINTERACTIVE_TIMEOUT) if(NOT noninteractive_timeout) set(noninteractive_timeout 10) endif() - math(EXPR noninteractive_timeout "${noninteractive_timeout}*${SDL_TESTS_TIMEOUT_MULTIPLIER}") + math(EXPR noninteractive_timeout "${noninteractive_timeout}*${SDLTEST_TIMEOUT_MULTIPLIER}") set_tests_properties(${TEST} PROPERTIES TIMEOUT "${noninteractive_timeout}") if(ast_INSTALL AND SDL_INSTALL_TESTS) set(exe ${TARGET}) @@ -657,12 +693,14 @@ foreach(TARGET ${SDL_TEST_EXECUTABLES}) add_sdl_test(${TARGET} ${TARGET} INSTALL) endforeach() -add_sdl_test(testautomation-no-simd testautomation) -add_sdl_test(testplatform-no-simd testplatform) -set_property(TEST testautomation-no-simd testplatform-no-simd APPEND PROPERTY ENVIRONMENT "SDL_CPU_FEATURE_MASK=-all") +if(NOT EMSCRIPTEN) + add_sdl_test(testautomation-no-simd testautomation) + add_sdl_test(testplatform-no-simd testplatform) + set_property(TEST testautomation-no-simd testplatform-no-simd APPEND PROPERTY ENVIRONMENT "SDL_CPU_FEATURE_MASK=-all") -# testautomation creates temporary files which might conflict -set_property(TEST testautomation-no-simd testautomation PROPERTY RUN_SERIAL TRUE) + # testautomation creates temporary files which might conflict + set_property(TEST testautomation-no-simd testautomation PROPERTY RUN_SERIAL TRUE) +endif() if(SDL_INSTALL_TESTS) if(RISCOS) diff --git a/test/emscripten/driver.py b/test/emscripten/driver.py new file mode 100755 index 0000000000..78b7a49e05 --- /dev/null +++ b/test/emscripten/driver.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python + +import argparse +import contextlib +import logging +import urllib.parse +import shlex +import sys +import time +import pathlib +from typing import Optional + +from selenium import webdriver +import selenium.common.exceptions +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait + + +logger = logging.getLogger(__name__) + + +class SDLSeleniumTestDriver: + def __init__(self, server: str, test: str, arguments: list[str], browser: str, firefox_binary: Optional[str]=None, chrome_binary: Optional[str]=None): + self. server = server + self.test = test + self.arguments = arguments + self.chrome_binary = chrome_binary + self.firefox_binary = firefox_binary + self.driver = None + self.stdout_printed = False + self.failed_messages: list[str] = [] + self.return_code = None + + driver_contructor = None + match browser: + case "firefox": + driver_contructor = webdriver.Firefox + driver_options = webdriver.FirefoxOptions() + if self.firefox_binary: + driver_options.binary_location = self.firefox_binary + case "chrome": + driver_contructor = webdriver.Chrome + driver_options = webdriver.ChromeOptions() + if self.chrome_binary: + driver_options.binary_location = self.chrome_binary + if driver_contructor is None: + raise ValueError(f"Invalid {browser=}") + + options = [ + "--headless", + ] + for o in options: + driver_options.add_argument(o) + logger.debug("About to create driver") + self.driver = driver_contructor(options=driver_options) + + @property + def finished(self): + return len(self.failed_messages) > 0 or self.return_code is not None + + def __del__(self): + if self.driver: + self.driver.quit() + + @property + def url(self): + req = { + "loghtml": "1", + "SDL_ASSERT": "abort", + } + req.update({f"arg_{i}": a for i, a in enumerate(self.arguments, 1) }) + req_str = urllib.parse.urlencode(req) + return f"{self.server}/{self.test}.html?{req_str}" + + @contextlib.contextmanager + def _selenium_catcher(self): + try: + yield + success = True + except selenium.common.exceptions.UnexpectedAlertPresentException as e: + # FIXME: switch context, verify text of dialog and answer "a" for abort + wait = WebDriverWait(self.driver, timeout=2) + try: + alert = wait.until(lambda d: d.switch_to.alert) + except selenium.common.exceptions.NoAlertPresentException: + self.failed_messages.append(e.msg) + return False + self.failed_messages.append(alert) + if "Assertion failure" in e.msg and "[ariA]" in e.msg: + alert.send_keys("a") + alert.accept() + else: + self.failed_messages.append(e.msg) + success = False + return success + + def get_stdout_and_print(self): + if self.stdout_printed: + return + with self._selenium_catcher(): + div_terminal = self.driver.find_element(by=By.ID, value="terminal") + assert div_terminal + text = div_terminal.text + print(text) + self.stdout_printed = True + + def update_return_code(self): + with self._selenium_catcher(): + div_process_quit = self.driver.find_element(by=By.ID, value="process-quit") + if not div_process_quit: + return + if div_process_quit.text != "": + try: + self.return_code = int(div_process_quit.text) + except ValueError: + raise ValueError(f"process-quit element contains invalid data: {div_process_quit.text:r}") + + def loop(self): + print(f"Connecting to \"{self.url}\"", file=sys.stderr) + self.driver.get(url=self.url) + self.driver.implicitly_wait(0.2) + + while True: + self.update_return_code() + if self.finished: + break + time.sleep(0.1) + + self.get_stdout_and_print() + if not self.stdout_printed: + self.failed_messages.append("Failed to get stdout/stderr") + + + +def main() -> int: + parser = argparse.ArgumentParser(allow_abbrev=False, description="Selenium SDL test driver") + parser.add_argument("--browser", default="firefox", choices=["firefox", "chrome"], help="browser") + parser.add_argument("--server", default="http://localhost:8080", help="Server where SDL tests live") + parser.add_argument("--verbose", action="store_true", help="Verbose logging") + parser.add_argument("--chrome-binary", help="Chrome binary") + parser.add_argument("--firefox-binary", help="Firefox binary") + + index_double_dash = sys.argv.index("--") + if index_double_dash < 0: + parser.error("Missing test arguments. Need -- ") + driver_arguments = sys.argv[1:index_double_dash] + test = pathlib.Path(sys.argv[index_double_dash+1]).name + test_arguments = sys.argv[index_double_dash+2:] + + args = parser.parse_args(args=driver_arguments) + + logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) + + logger.debug("driver_arguments=%r test=%r test_arguments=%r", driver_arguments, test, test_arguments) + + sdl_test_driver = SDLSeleniumTestDriver( + server=args.server, + test=test, + arguments=test_arguments, + browser=args.browser, + chrome_binary=args.chrome_binary, + firefox_binary=args.firefox_binary, + ) + sdl_test_driver.loop() + + rc = sdl_test_driver.return_code + if sdl_test_driver.failed_messages: + for msg in sdl_test_driver.failed_messages: + print(f"FAILURE MESSAGE: {msg}", file=sys.stderr) + if rc == 0: + print(f"Test signaled success (rc=0) but a failure happened", file=sys.stderr) + rc = 1 + sys.stdout.flush() + logger.info("Exit code = %d", rc) + return rc + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/test/emscripten/pre.js b/test/emscripten/pre.js new file mode 100644 index 0000000000..74ebd1c3c0 --- /dev/null +++ b/test/emscripten/pre.js @@ -0,0 +1,54 @@ +const searchParams = new URLSearchParams(window.location.search); + +Module.preRun = () => { +}; + +const arguments = []; +for (let i = 1; true; i++) { + const arg_i = searchParams.get(`arg_${i}`); + if (arg_i == null) { + break; + } + arguments.push(arg_i); +} + +Module.arguments = arguments; + +if (searchParams.get("loghtml") === "1") { + const divTerm = document.createElement("div"); + divTerm.id = "terminal"; + document.body.append(divTerm); + + function printToStdOut(msg, id) { + const divMsg = document.createElement("div", {class: "stdout"}); + divMsg.id = id; + divMsg.append(document.createTextNode(msg)); + divTerm.append(divMsg); + return divMsg; + } + + Module.print = (msg) => { + console.log(msg); + printToStdOut(msg, "stdout"); + } + + Module.printErr = (msg) => { + console.error(msg); + const e = printToStdOut(msg, "stderr"); + e.style = "color:red"; + } + + const divQuit = document.createElement("div"); + divQuit.id = "process-quit"; + document.body.append(divQuit); + + Module.quit = (msg) => { + divQuit.innerText = msg; + console.log(`QUIT: ${msg}`) + } + + Module.onabort = (msg) => { + printToStdOut(`ABORT: ${msg}`, "stderr"); + console.log(`ABORT: ${msg}`); + } +}