mirror of
https://github.com/libsdl-org/SDL.git
synced 2024-11-27 05:43:29 +08:00
test: use Selenium to run Emscripten tests
This commit is contained in:
parent
46bafda7ab
commit
161761653f
27
.github/workflows/create-test-plan.py
vendored
27
.github/workflows/create-test-plan.py
vendored
@ -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)}\"")
|
||||
|
19
.github/workflows/generic.yml
vendored
19
.github/workflows/generic.yml
vendored
@ -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
|
||||
|
@ -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=$<TARGET_FILE_DIR:SDL3::${sdl_name_component}>
|
||||
)
|
||||
|
||||
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:$<TARGET_RUNTIME_DLL_DIRS:${TEST}>")
|
||||
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)
|
||||
|
179
test/emscripten/driver.py
Executable file
179
test/emscripten/driver.py
Executable file
@ -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 -- <FILENAME> <ARGUMENTS>")
|
||||
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())
|
54
test/emscripten/pre.js
Normal file
54
test/emscripten/pre.js
Normal file
@ -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}`);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user