From 87c2d1d1d366f6aa9b44c4b3b51d16f754a0d0ef Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Sun, 4 Oct 2026 15:04:56 +0200 Subject: [PATCH] [IMP] ci: test the ISOLATE addons in their own database website_sale_disable_cart removes the cart from every website once installed, which breaks website_membership_signup_required when CI installs everything together. The repo is shared by clients that never install them together: run_tests.py now tests each ISOLATE addon in its own database, after the rest, and ci.yml isolates website_sale_disable_cart. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/ci.yml | 5 ++ tools/ci/run_tests.py | 98 +++++++++++++++++++++++++-------------- tools/issue_bot/README.md | 6 +++ 3 files changed, 73 insertions(+), 36 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index c992aaa..a2b7ab5 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -50,6 +50,11 @@ jobs: # este repo (ecocentral depende de él): se prueba el checkout del PR, # no ese clon. ODOO_SRC_SKIP: addons-cm + # Se prueban cada uno en su propia base de datos: instalados junto + # al resto les cambian el comportamiento. website_sale_disable_cart + # quita el carrito a todos los sitios web y rompe, por ejemplo, el + # alta con cuota de website_membership_signup_required. + ISOLATE: website_sale_disable_cart # Si los tests pasan, deja aquí el .pot de cada addon para el job review. # Está en la caché que el runner monta en todos los jobs. BOT_POT_DIR: /root/.cache/eco-pr-bot/pot diff --git a/tools/ci/run_tests.py b/tools/ci/run_tests.py index 638f78c..a52a647 100755 --- a/tools/ci/run_tests.py +++ b/tools/ci/run_tests.py @@ -8,7 +8,12 @@ los del core ni los de la OCA. tools/ci/run_tests.py [addon ...] Sin argumentos prueba todos los addons instalables del repo menos los de -EXCLUDE. Odoo y los repos de los que dependen los addons se toman de ODOO_SRC +EXCLUDE. Los de ISOLATE se prueban cada uno en su propia base de datos, después +del resto: son los que cambian el comportamiento de otros con solo estar +instalados (website_sale_disable_cart quita el carrito a todos los sitios web), +y el repo lo comparten varios clientes que no los instalan juntos. + +Odoo y los repos de los que dependen los addons se toman de ODOO_SRC (un checkout por carpeta; en el runner es un montaje, ver tools/issue_bot/README.md), menos las carpetas de ODOO_SRC_SKIP, y la conexión a PostgreSQL, de las variables PG*. @@ -33,7 +38,6 @@ from pathlib import Path REPO = Path(__file__).resolve().parents[2] ODOO_SRC = Path(os.environ.get("ODOO_SRC", "/odoo-src/18.0")) -SKIP = set(os.environ.get("ODOO_SRC_SKIP", "").replace(",", " ").split()) DB = os.environ.get("PGDATABASE", "test") POT_DIR = os.environ.get("BOT_POT_DIR") POT_KEEP_DAYS = 14 @@ -54,8 +58,9 @@ _logger = logging.getLogger("ci") def find_sources(): """Devuelve odoo-bin y el addons_path: el repo y lo que haya en ODOO_SRC.""" odoo_bin, paths = None, [REPO] + skip = env_list("ODOO_SRC_SKIP") for src in sorted(ODOO_SRC.iterdir()): - if src.name in SKIP: + if src.name in skip: _logger.info("Se omite %s (ODOO_SRC_SKIP)", src) elif (src / "odoo-bin").is_file(): odoo_bin = src / "odoo-bin" @@ -67,6 +72,10 @@ def find_sources(): return odoo_bin, paths +def env_list(name): + return set(os.environ.get(name, "").replace(",", " ").split()) + + def read_manifests(paths): manifests = {} for path in paths: @@ -101,12 +110,12 @@ def wait_for_postgres(timeout=60): time.sleep(1) -def odoo(odoo_bin, paths, *args): +def odoo(odoo_bin, paths, db, *args): cmd = [ sys.executable, str(odoo_bin), f"--addons-path={','.join(map(str, paths))}", - f"--database={DB}", + f"--database={db}", # Con --test-enable Odoo levanta su servidor HTTP para los tours, y # el runner lanza el job en la red del host: sin esto escucharía en # todas las interfaces de la máquina. Los tests lo buscan en @@ -119,8 +128,11 @@ def odoo(odoo_bin, paths, *args): return subprocess.run(cmd, check=False).returncode -def export_pots(odoo_bin, paths, addons): - """Deja en BOT_POT_DIR/ el .pot de cada addon ya instalado.""" +def export_pots(odoo_bin, paths, runs): + """Deja en BOT_POT_DIR/ el .pot de cada addon ya instalado. + + `runs` es una lista de (base de datos, addons instalados en ella). + """ pot_dir = Path(POT_DIR) head = subprocess.run( ["git", "rev-parse", "HEAD"], @@ -136,21 +148,22 @@ def export_pots(odoo_bin, paths, addons): for stale in (out, tmp): shutil.rmtree(stale, ignore_errors=True) tmp.mkdir(parents=True) - cmd = [ - sys.executable, - str(odoo_bin), - "shell", - f"--addons-path={','.join(map(str, paths))}", - f"--database={DB}", - "--no-http", - "--log-level=warn", - ] _logger.info("Exportando los .pot a %s", out) - script = EXPORT_POTS.format(addons=addons, out=str(tmp)) - if subprocess.run(cmd, input=script, text=True, check=False).returncode: - _logger.warning("No se han podido exportar los .pot") - shutil.rmtree(tmp, ignore_errors=True) - return + for db, addons in runs: + cmd = [ + sys.executable, + str(odoo_bin), + "shell", + f"--addons-path={','.join(map(str, paths))}", + f"--database={db}", + "--no-http", + "--log-level=warn", + ] + script = EXPORT_POTS.format(addons=addons, out=str(tmp)) + if subprocess.run(cmd, input=script, text=True, check=False).returncode: + _logger.warning("No se han podido exportar los .pot") + shutil.rmtree(tmp, ignore_errors=True) + return tmp.rename(out) limit = time.time() - POT_KEEP_DAYS * 86400 for old in pot_dir.iterdir(): @@ -158,12 +171,27 @@ def export_pots(odoo_bin, paths, addons): shutil.rmtree(old, ignore_errors=True) +def run_tests(odoo_bin, paths, manifests, db, addons): + """Crea `db` con las dependencias de `addons` y los instala con sus tests.""" + dependencies = sorted( + {dep for addon in addons for dep in manifests[addon].get("depends", [])} + - set(addons) + ) + subprocess.run(["dropdb", "--if-exists", db], check=True) + _logger.info("Instalando las dependencias: %s", ", ".join(dependencies)) + init = ["--init", ",".join(dependencies), "--log-level=warn"] + if odoo(odoo_bin, paths, db, *init): + sys.exit("No se ha podido crear la base de datos con las dependencias") + _logger.info("Probando: %s", ", ".join(addons)) + return odoo(odoo_bin, paths, db, "--init", ",".join(addons), "--test-enable") + + def main(): logging.basicConfig(level=logging.INFO, format="%(message)s") odoo_bin, paths = find_sources() manifests = read_manifests(paths) repo_addons = sorted(m.parent.name for m in REPO.glob("*/__manifest__.py")) - exclude = set(os.environ.get("EXCLUDE", "").replace(",", " ").split()) + exclude = env_list("EXCLUDE") addons = sys.argv[1:] or [ addon for addon in repo_addons @@ -189,27 +217,25 @@ def main(): "(tools/issue_bot/runner/ en el repo de ecocentral) o el addon a " "EXCLUDE." ) - dependencies = sorted( - {dep for addon in addons for dep in manifests[addon].get("depends", [])} - - set(addons) - ) + isolate = env_list("ISOLATE") + together = [addon for addon in addons if addon not in isolate] + runs = [(DB, together)] if together else [] + runs += [(f"{DB}_{addon}", [addon]) for addon in addons if addon in isolate] wait_for_postgres() - subprocess.run(["dropdb", "--if-exists", DB], check=True) - _logger.info("Instalando las dependencias: %s", ", ".join(dependencies)) - init = ["--init", ",".join(dependencies), "--log-level=warn"] - if odoo(odoo_bin, paths, *init): - sys.exit("No se ha podido crear la base de datos con las dependencias") - _logger.info("Probando: %s", ", ".join(addons)) - result = odoo(odoo_bin, paths, "--init", ",".join(addons), "--test-enable") - if not result and POT_DIR: + failed = [] + for db, run_addons in runs: + if run_tests(odoo_bin, paths, manifests, db, run_addons): + failed.append(db) + if failed: + sys.exit(f"Fallan los tests en: {', '.join(failed)}") + if POT_DIR: # Que falte la caché no es un fallo de los tests: el bot de revisión # se queda sin las traducciones y lo dice. try: - export_pots(odoo_bin, paths, addons) + export_pots(odoo_bin, paths, runs) except OSError as e: _logger.warning("No se han podido exportar los .pot: %s", e) - sys.exit(result) if __name__ == "__main__": diff --git a/tools/issue_bot/README.md b/tools/issue_bot/README.md index 03104f9..5667a73 100644 --- a/tools/issue_bot/README.md +++ b/tools/issue_bot/README.md @@ -101,6 +101,12 @@ una base de datos con las dependencias de los addons y después instala los addons con `--test-enable`, de modo que solo corren los tests del repo. PostgreSQL es un servicio del job, en el puerto 54330 de `127.0.0.1`. +Los addons de `ISOLATE` (en `ci.yml`) se prueban cada uno en su propia base de +datos, después del resto: son los que con solo estar instalados cambian el +comportamiento de otros addons del repo, que es compartido por clientes que no +los instalan juntos. Por ejemplo, `website_sale_disable_cart` quita el carrito +a todos los sitios web. + Odoo arranca desde las fuentes montadas en `/odoo-src/18.0`, así que: - Todo repo del que dependa un addon de aquí tiene que estar en la parte