diff --git a/tests/main.py b/tests/main.py index beb91d2f..57f84bcb 100644 --- a/tests/main.py +++ b/tests/main.py @@ -1,10 +1,12 @@ import os +import shlex import shutil import ssl import subprocess import sys from enum import Enum from functools import wraps +from textwrap import dedent from time import sleep from typing import Any, Callable, Optional from urllib.error import HTTPError, URLError @@ -63,8 +65,13 @@ def log(text: str): if CI: print(f"::group::{text}") else: - print(colored(text, Color.YELLOW)) + output = ( + f"\n{f' {text} '.center(os.get_terminal_size().columns, '=')}\n" + ) + print(colored(output, Color.YELLOW)) + ret = f(*args, **kwargs) + if CI: print("::endgroup::") return ret @@ -75,7 +82,7 @@ def log(text: str): def run(*cmd: str): - print(colored(f"> {' '.join(cmd)}", Color.GREEN)) + print(colored(f"> {shlex.join(cmd)}", Color.GREEN)) return subprocess.check_call(cmd) @@ -101,7 +108,7 @@ def docker_compose(*cmd: str): return run(*args, *cmd) -def dco_exec(*cmd: str): +def docker_compose_exec(*cmd: str): if TTY: return docker_compose("exec", *cmd) else: @@ -111,25 +118,31 @@ def dco_exec(*cmd: str): @log("Setup .env") def setup_env(): shutil.copy("example.env", "tests/.env") + if not CI: return - for env in ("FRAPPE_VERSION", "ERPNEXT_VERSION"): - if os.environ[env] == "develop": + + for var in ("FRAPPE_VERSION", "ERPNEXT_VERSION"): + if os.environ[var] == "develop": with open(os.environ["GITHUB_ENV"], "a") as f: - f.write(f"\n{env}=latest") - os.environ[env] = "latest" + f.write(f"\n{var}=latest") + os.environ[var] = "latest" + with open("tests/.env", "a") as f: f.write( - f""" -FRAPPE_VERSION={os.environ['FRAPPE_VERSION']} -ERPNEXT_VERSION={os.environ['ERPNEXT_VERSION']} -""" + dedent( + f""" + FRAPPE_VERSION={os.environ['FRAPPE_VERSION']} + ERPNEXT_VERSION={os.environ['ERPNEXT_VERSION']} + """ + ) ) + with open("tests/.env") as f: print(f.read()) -@log("Print compose configuration") +@log("Print configuration") def print_compose_configuration(): docker_compose("config") @@ -139,22 +152,22 @@ def create_containers(): docker_compose("up", "-d", "--quiet-pull") -@log("Check if backend services have connections") +@log("Check if Python services have connections") def ping_links_in_backends(): for service in BACKEND_SERVICES: for _ in range(10): try: - dco_exec(service, "healthcheck.sh") + docker_compose_exec(service, "healthcheck.sh") break except subprocess.CalledProcessError: sleep(1) else: - raise Exception(f"Connections healthcheck failed for service {service}") + raise RuntimeError(f"Connections healthcheck failed for service {service}") -@log("Create test site") +@log("Create site") def create_site(): - dco_exec( + docker_compose_exec( "backend", "bench", "new-site", @@ -176,14 +189,18 @@ _ssl_ctx.verify_mode = ssl.CERT_NONE def ping_and_check_content(url: str, callback: Callable[[str], Optional[str]]): request = Request(url, headers={"Host": SITE_NAME}) print(f"Checking {url}") + for _ in range(100): try: response = urlopen(request, context=_ssl_ctx) + except HTTPError as exc: if exc.code not in (404, 502): raise + except URLError: pass + else: text: str = response.read().decode() ret = callback(text) @@ -192,32 +209,33 @@ def ping_and_check_content(url: str, callback: Callable[[str], Optional[str]]): return sleep(0.1) - raise AssertionError(f"Couldn't ping {url}") + + raise RuntimeError(f"Couldn't ping {url}") -def index_callback(text: str): +def check_index_callback(text: str): if "404 page not found" not in text: return text[:200] @log("Check /") def check_index(): - ping_and_check_content(url="http://127.0.0.1", callback=index_callback) + ping_and_check_content("http://127.0.0.1", check_index_callback) @log("Check /api/method/version") def check_api(): ping_and_check_content( - url="http://127.0.0.1/api/method/version", - callback=lambda text: text if '"message"' in text else None, + "http://127.0.0.1/api/method/version", + lambda text: text if '"message"' in text else None, ) -@log("Check if Frappe can connect to services in backends") +@log("Check if Frappe can connect to services in Python services") def ping_frappe_connections_in_backends(): for service in BACKEND_SERVICES: docker_compose("cp", "tests/_ping_frappe_connections.py", f"{service}:/tmp/") - dco_exec( + docker_compose_exec( service, "/home/frappe/frappe-bench/env/bin/python", f"/tmp/_ping_frappe_connections.py", @@ -227,8 +245,8 @@ def ping_frappe_connections_in_backends(): @log("Check /assets") def check_assets(): ping_and_check_content( - url="http://127.0.0.1/assets/frappe/images/frappe-framework-logo.svg", - callback=lambda text: text[:200] if text is not None else None, + "http://127.0.0.1/assets/frappe/images/frappe-framework-logo.svg", + lambda text: text[:200] if text else None, ) @@ -241,8 +259,8 @@ def check_files(): f"backend:/home/frappe/frappe-bench/sites/{SITE_NAME}/public/files/", ) ping_and_check_content( - url=f"http://127.0.0.1/files/{file_name}", - callback=lambda text: text if text == "lalala\n" else None, + f"http://127.0.0.1/files/{file_name}", + lambda text: text if text == "lalala\n" else None, ) @@ -265,7 +283,7 @@ def prepare_s3_server(): "/data", ) docker_compose("cp", "tests/_create_bucket.py", "backend:/tmp") - dco_exec( + docker_compose_exec( "-e", f"S3_ACCESS_KEY={S3_ACCESS_KEY}", "-e", @@ -278,8 +296,10 @@ def prepare_s3_server(): @log("Push backup to S3") def push_backup_to_s3(): - dco_exec("backend", "bench", "--site", SITE_NAME, "backup", "--with-files") - dco_exec( + docker_compose_exec( + "backend", "bench", "--site", SITE_NAME, "backup", "--with-files" + ) + docker_compose_exec( "backend", "push-backup", "--site", @@ -297,10 +317,10 @@ def push_backup_to_s3(): ) -@log("Check backup in S3") +@log("Check backup files in S3") def check_backup_in_s3(): docker_compose("cp", "tests/_check_backup_files.py", "backend:/tmp") - dco_exec( + docker_compose_exec( "-e", f"S3_ACCESS_KEY={S3_ACCESS_KEY}", "-e", @@ -316,14 +336,14 @@ def stop_s3_container(): run("docker", "rm", "minio", "-f") -@log("Recreate with https override") +@log("Recreate with HTTPS override") def recreate_with_https_override(): docker_compose("-f", "overrides/compose.https.yaml", "up", "-d") -@log("Check / (https)") +@log("Check / (HTTPS)") def check_index_https(): - ping_and_check_content(url="https://127.0.0.1", callback=index_callback) + ping_and_check_content("https://127.0.0.1", check_index_callback) @log("Stop containers") @@ -335,13 +355,14 @@ def stop_containers(): def create_containers_with_erpnext_override(): args = ["-f", "overrides/compose.erpnext.yaml"] if CI: - args.extend(("-f", "tests/compose.ci-erpnext.yaml")) + args += ("-f", "tests/compose.ci-erpnext.yaml") + docker_compose(*args, "up", "-d", "--quiet-pull") @log("Create ERPNext site") def create_erpnext_site(): - dco_exec( + docker_compose_exec( "backend", "bench", "new-site", @@ -356,19 +377,19 @@ def create_erpnext_site(): docker_compose("restart", "backend") -@log("Check /api/method/erpnext.templates.pages.product_search.get_product_list") +@log("Check /api/method") def check_erpnext_api(): ping_and_check_content( - url="http://127.0.0.1/api/method/erpnext.templates.pages.product_search.get_product_list", - callback=lambda text: text if '"message"' in text else None, + "http://127.0.0.1/api/method/erpnext.templates.pages.product_search.get_product_list", + lambda text: text if '"message"' in text else None, ) -@log("Check /assets/erpnext/js/setup_wizard.js") +@log("Check /assets") def check_erpnext_assets(): ping_and_check_content( - url="http://127.0.0.1/assets/erpnext/js/setup_wizard.js", - callback=lambda text: text[:200] if text is not None else None, + "http://127.0.0.1/assets/erpnext/js/setup_wizard.js", + lambda text: text[:200] if text else None, ) @@ -379,9 +400,11 @@ def create_containers_with_postgres_override(): @log("Create Postgres site") def create_postgres_site(): - dco_exec("backend", "bench", "set-config", "-g", "root_login", "postgres") - dco_exec("backend", "bench", "set-config", "-g", "root_password", "123") - dco_exec( + docker_compose_exec( + "backend", "bench", "set-config", "-g", "root_login", "postgres" + ) + docker_compose_exec("backend", "bench", "set-config", "-g", "root_password", "123") + docker_compose_exec( "backend", "bench", "new-site", @@ -399,52 +422,76 @@ def delete_env(): os.remove("tests/.env") -@log("Show docker compose logs") +@log("Show logs") def show_docker_compose_logs(): docker_compose("logs") +def start(): + setup_env() + print_compose_configuration() + create_containers() + ping_links_in_backends() + + +def create_frappe_site_and_check_availability(): + create_site() + check_index() + check_api() + ping_frappe_connections_in_backends() + check_assets() + check_files() + + +def check_s3(): + prepare_s3_server() + + try: + push_backup_to_s3() + check_backup_in_s3() + finally: + stop_s3_container() + + +def check_https(): + print_compose_configuration() + recreate_with_https_override() + check_index_https() + stop_containers() + + +def check_erpnext(): + print_compose_configuration() + create_containers_with_erpnext_override() + create_erpnext_site() + check_erpnext_api() + check_erpnext_assets() + stop_containers() + + +def check_postgres(): + print_compose_configuration() + create_containers_with_postgres_override() + create_postgres_site() + ping_links_in_backends() + + def main() -> int: try: patch_print() - - setup_env() - print_compose_configuration() - create_containers() - - ping_links_in_backends() - create_site() - check_index() - check_api() - ping_frappe_connections_in_backends() - check_assets() - check_files() - - prepare_s3_server() - push_backup_to_s3() - check_backup_in_s3() - stop_s3_container() - - recreate_with_https_override() - check_index_https() - stop_containers() - - create_containers_with_erpnext_override() - create_erpnext_site() - check_erpnext_api() - check_erpnext_assets() - stop_containers() - - create_containers_with_postgres_override() - create_postgres_site() - ping_links_in_backends() + start() + create_frappe_site_and_check_availability() + check_s3() + check_https() + check_erpnext() + check_postgres() finally: delete_env() show_docker_compose_logs() stop_containers() - print(colored("Tests successfully passed!", Color.YELLOW)) + print(colored("\nTests successfully passed!", Color.YELLOW)) return 0