Simple Directmedia Layer
at main 6.4 kB view raw
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 driver_contructor = None 36 match browser: 37 case "firefox": 38 driver_contructor = webdriver.Firefox 39 driver_options = webdriver.FirefoxOptions() 40 if self.firefox_binary: 41 driver_options.binary_location = self.firefox_binary 42 case "chrome": 43 driver_contructor = webdriver.Chrome 44 driver_options = webdriver.ChromeOptions() 45 if self.chrome_binary: 46 driver_options.binary_location = self.chrome_binary 47 if driver_contructor is None: 48 raise ValueError(f"Invalid {browser=}") 49 50 options = [ 51 "--headless", 52 ] 53 for o in options: 54 driver_options.add_argument(o) 55 logger.debug("About to create driver") 56 self.driver = driver_contructor(options=driver_options) 57 58 @property 59 def finished(self): 60 return len(self.failed_messages) > 0 or self.return_code is not None 61 62 def __del__(self): 63 if self.driver: 64 self.driver.quit() 65 66 @property 67 def url(self): 68 req = { 69 "loghtml": "1", 70 "SDL_ASSERT": "abort", 71 } 72 for key, value in os.environ.items(): 73 if key.startswith("SDL_"): 74 req[key] = value 75 req.update({f"arg_{i}": a for i, a in enumerate(self.arguments, 1) }) 76 req_str = urllib.parse.urlencode(req) 77 return f"{self.server}/{self.test}.html?{req_str}" 78 79 @contextlib.contextmanager 80 def _selenium_catcher(self): 81 try: 82 yield 83 success = True 84 except selenium.common.exceptions.UnexpectedAlertPresentException as e: 85 # FIXME: switch context, verify text of dialog and answer "a" for abort 86 wait = WebDriverWait(self.driver, timeout=2) 87 try: 88 alert = wait.until(lambda d: d.switch_to.alert) 89 except selenium.common.exceptions.NoAlertPresentException: 90 self.failed_messages.append(e.msg) 91 return False 92 self.failed_messages.append(alert) 93 if "Assertion failure" in e.msg and "[ariA]" in e.msg: 94 alert.send_keys("a") 95 alert.accept() 96 else: 97 self.failed_messages.append(e.msg) 98 success = False 99 return success 100 101 def get_stdout_and_print(self): 102 if self.stdout_printed: 103 return 104 with self._selenium_catcher(): 105 div_terminal = self.driver.find_element(by=By.ID, value="terminal") 106 assert div_terminal 107 text = div_terminal.text 108 print(text) 109 self.stdout_printed = True 110 111 def update_return_code(self): 112 with self._selenium_catcher(): 113 div_process_quit = self.driver.find_element(by=By.ID, value="process-quit") 114 if not div_process_quit: 115 return 116 if div_process_quit.text != "": 117 try: 118 self.return_code = int(div_process_quit.text) 119 except ValueError: 120 raise ValueError(f"process-quit element contains invalid data: {div_process_quit.text:r}") 121 122 def loop(self): 123 print(f"Connecting to \"{self.url}\"", file=sys.stderr) 124 self.driver.get(url=self.url) 125 self.driver.implicitly_wait(0.2) 126 127 while True: 128 self.update_return_code() 129 if self.finished: 130 break 131 time.sleep(0.1) 132 133 self.get_stdout_and_print() 134 if not self.stdout_printed: 135 self.failed_messages.append("Failed to get stdout/stderr") 136 137 138 139def main() -> int: 140 parser = argparse.ArgumentParser(allow_abbrev=False, description="Selenium SDL test driver") 141 parser.add_argument("--browser", default="firefox", choices=["firefox", "chrome"], help="browser") 142 parser.add_argument("--server", default="http://localhost:8080", help="Server where SDL tests live") 143 parser.add_argument("--verbose", action="store_true", help="Verbose logging") 144 parser.add_argument("--chrome-binary", help="Chrome binary") 145 parser.add_argument("--firefox-binary", help="Firefox binary") 146 147 index_double_dash = sys.argv.index("--") 148 if index_double_dash < 0: 149 parser.error("Missing test arguments. Need -- <FILENAME> <ARGUMENTS>") 150 driver_arguments = sys.argv[1:index_double_dash] 151 test = pathlib.Path(sys.argv[index_double_dash+1]).name 152 test_arguments = sys.argv[index_double_dash+2:] 153 154 args = parser.parse_args(args=driver_arguments) 155 156 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) 157 158 logger.debug("driver_arguments=%r test=%r test_arguments=%r", driver_arguments, test, test_arguments) 159 160 sdl_test_driver = SDLSeleniumTestDriver( 161 server=args.server, 162 test=test, 163 arguments=test_arguments, 164 browser=args.browser, 165 chrome_binary=args.chrome_binary, 166 firefox_binary=args.firefox_binary, 167 ) 168 sdl_test_driver.loop() 169 170 rc = sdl_test_driver.return_code 171 if sdl_test_driver.failed_messages: 172 for msg in sdl_test_driver.failed_messages: 173 print(f"FAILURE MESSAGE: {msg}", file=sys.stderr) 174 if rc == 0: 175 print(f"Test signaled success (rc=0) but a failure happened", file=sys.stderr) 176 rc = 1 177 sys.stdout.flush() 178 logger.info("Exit code = %d", rc) 179 return rc 180 181 182if __name__ == "__main__": 183 raise SystemExit(main())