l10n_CM/run_l10n.py (142 lines of code) (raw):
import logging
import os
import subprocess
import sys
import threading
from contextlib import contextmanager
from http.server import HTTPServer, SimpleHTTPRequestHandler
from json import load
import requests
current_dir = os.path.dirname(__file__)
valid_flags = {"--run-headless", "-n", "--reruns"}
flag_with_parameter = {"-n", "--reruns"}
valid_region = {"US", "CA", "DE", "FR"}
valid_sites = {"demo", "amazon", "walmart"}
live_sites = []
LOCALHOST = "127.0.0.1"
PORT = 8080
class MyHttpRequestHandler(SimpleHTTPRequestHandler):
live_site = None
region = None
def translate_path(self, path):
"""switch the default directory where the html files are served from."""
base_dir = os.path.join(current_dir, "sites", self.live_site, self.region)
return os.path.join(base_dir, path.lstrip("/"))
def log_message(self, format, *args):
"""Remove logs from the server."""
pass
def start_server(live_site, current_region):
# set live site attribute
MyHttpRequestHandler.live_site = live_site
MyHttpRequestHandler.region = current_region
# start web server on a separate thread to avoid blocking calls.
http = HTTPServer((LOCALHOST, PORT), MyHttpRequestHandler)
thread = threading.Thread(target=lambda: http.serve_forever())
thread.start()
return http, thread
@contextmanager
def running_server(live_site, test_region):
"""Context manager to run a server and clean it up automatically."""
html_path = os.path.join(current_dir, "sites", live_site, test_region)
if not os.path.exists(html_path):
raise FileNotFoundError(
f"Expected HTML directory not found at path: {html_path}"
)
httpd, server_thread = start_server(live_site, test_region)
try:
yield # control goes to the caller, server runs in the background
finally:
try:
# Send a dummy request to unblock the server if necessary
requests.get(f"http://{LOCALHOST}:{PORT}")
except Exception:
pass
httpd.shutdown()
server_thread.join()
logging.info(f"{live_site} server shutdown.")
def run_tests(reg, site, flg, all_tests):
"""
Execute the test suite for a given region with specified flags.
Args:
reg (str): The test region identifier.
site (str): Page being tested.
flg (list[str]): The list of pytest flags to be used.
all_tests (list[str]): The list of test file paths to execute.
"""
try:
if len(all_tests) > 0:
logging.info(f"Tests for {reg} region on {site} page.")
os.environ["CM_SITE"] = site
os.environ["FX_REGION"] = reg
subprocess.run(["pytest", *flg, *all_tests], check=True, text=True)
else:
logging.info(f"{reg} region on {site} site has no tests.")
except subprocess.CalledProcessError as e:
logging.warning(f"Test run failed. {e}")
def get_region_tests(test_region: str) -> list[str]:
"""
Retrieve the list of test file paths for a specified region.
Args:
test_region (str): The region identifier for which tests are retrieved.
Returns:
list[str]: A list of test file paths for the given region.
"""
path_to_region = current_dir + "/region/"
with open(path_to_region + test_region + ".json", "r") as fp:
region_data = load(fp)
raw_tests = region_data.get("tests", [])
return (
list(map(lambda test: current_dir + "/Unified/" + test, raw_tests))
if len(raw_tests) > 0
else raw_tests
)
def get_flags_and_sanitize(flags_arguments: list[str]) -> list[str]:
"""
Extract and validate pytest flags from command-line arguments.
Args:
flags_arguments (list[str]): List of command-line arguments passed to the script.
Returns:
list[str]: A sanitized list of valid pytest flags.
Raises:
IndexError: If a flag that is supposed to have an option doesn't
ValueError: If an arg or flag is not valid.
"""
# add workers and rerun flaky failed tests.
flg = []
for arg in flags_arguments[:]:
if arg in valid_flags:
if arg in flag_with_parameter:
try:
i = flags_arguments.index(arg)
val = int(flags_arguments[i + 1])
flg.extend((arg, str(val)))
del flags_arguments[i : i + 2]
except IndexError:
logging.warning(f"Argument {arg} doesn't have proper value.")
raise IndexError(f"Argument {arg} doesn't have proper value.")
except ValueError:
logging.warning(f"Value for Argument {arg} must be an int.")
raise IndexError(f"Value for Argument {arg} must be an int.")
else:
flags_arguments.remove(arg)
flg.append(arg)
elif arg in valid_region or arg.isdigit():
continue
elif arg in valid_sites:
live_sites.append(arg)
flags_arguments.remove(arg)
else:
logging.warning(f"Invalid Argument: {arg}.")
raise ValueError(f"Invalid Argument: {arg}.")
return flg
def run_unified(regions, unified_flags):
"""
Execute unified tests for multiple regions.
Args:
regions (list[str]): A set of region identifiers.
unified_flags (list[str]): A list of pytest flags to be used.
"""
unified_tests = get_region_tests("Unified")
logging.info(f"Testing {live_sites} Sites.")
for live_site in live_sites:
# If the live_site is 'demo', skip starting the server
if live_site == "demo":
for unified_region in regions:
run_tests(unified_region, live_site, unified_flags, unified_tests)
else:
for unified_region in regions:
unified_json_path = os.path.join(
current_dir, "constants", live_site, unified_region
)
if os.path.exists(unified_json_path):
with running_server(live_site, unified_region):
run_tests(
unified_region, live_site, unified_flags, unified_tests
)
else:
logging.info(
f"No mapping json file for {unified_region} region and {live_site} site."
)
if __name__ == "__main__":
arguments = sys.argv[1:]
flags = get_flags_and_sanitize(arguments)
if len(live_sites) == 0:
# Run on all live sites.
live_sites = valid_sites
logging.info(f"Running Against all available live sites ({live_sites}).")
if len(arguments) == 0:
## Run on all Regions.
logging.info(f"Running Unified Tests for {valid_region} Regions.")
run_unified(list(valid_region), flags)
else:
# run on given region and sites.
logging.info(f"Running Unified Tests for {arguments} Regions.")
run_unified(arguments, flags)
for site in live_sites:
# for a given site, run all region specific tests.
for region in arguments:
tests = get_region_tests(region)
# Check if field mapping json file is present, pass test region if it isn't
json_path = os.path.join(current_dir, "constants", site, region)
logging.info(f"Running Specific Tests for {region}.")
# If the live_site is 'demo', skip starting the server
if site == "demo":
run_tests(region, site, flags, tests)
elif os.path.exists(json_path):
with running_server(site, region):
run_tests(region, site, flags, tests)