mirror of
				https://github.com/libsdl-org/SDL.git
				synced 2025-11-04 09:44:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			185 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python
 | 
						|
 | 
						|
import argparse
 | 
						|
import contextlib
 | 
						|
import logging
 | 
						|
import os
 | 
						|
import pathlib
 | 
						|
import shlex
 | 
						|
import sys
 | 
						|
import time
 | 
						|
from typing import Optional
 | 
						|
import urllib.parse
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
        options = [
 | 
						|
            "--headless",
 | 
						|
        ]
 | 
						|
 | 
						|
        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
 | 
						|
                options.append("--no-sandbox")
 | 
						|
        if driver_contructor is None:
 | 
						|
            raise ValueError(f"Invalid {browser=}")
 | 
						|
        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",
 | 
						|
        }
 | 
						|
        for key, value in os.environ.items():
 | 
						|
            if key.startswith("SDL_"):
 | 
						|
                req[key] = value
 | 
						|
        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())
 |