[IMP] ci: test the ISOLATE addons in their own database
Some checks failed
CI / lint (pull_request) Successful in 31s
CI / test (pull_request) Failing after 11m29s
CI / review (pull_request) Has been skipped

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 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-10-04 15:04:56 +02:00
parent ca526d55cc
commit 87c2d1d1d3
Signed by: snt
GPG key ID: A9FD34930EADBE71
3 changed files with 73 additions and 36 deletions

View file

@ -50,6 +50,11 @@ jobs:
# este repo (ecocentral depende de él): se prueba el checkout del PR, # este repo (ecocentral depende de él): se prueba el checkout del PR,
# no ese clon. # no ese clon.
ODOO_SRC_SKIP: addons-cm 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. # 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. # Está en la caché que el runner monta en todos los jobs.
BOT_POT_DIR: /root/.cache/eco-pr-bot/pot BOT_POT_DIR: /root/.cache/eco-pr-bot/pot

View file

@ -8,7 +8,12 @@ los del core ni los de la OCA.
tools/ci/run_tests.py [addon ...] tools/ci/run_tests.py [addon ...]
Sin argumentos prueba todos los addons instalables del repo menos los de 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 (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 tools/issue_bot/README.md), menos las carpetas de ODOO_SRC_SKIP, y la conexión
a PostgreSQL, de las variables PG*. a PostgreSQL, de las variables PG*.
@ -33,7 +38,6 @@ from pathlib import Path
REPO = Path(__file__).resolve().parents[2] REPO = Path(__file__).resolve().parents[2]
ODOO_SRC = Path(os.environ.get("ODOO_SRC", "/odoo-src/18.0")) 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") DB = os.environ.get("PGDATABASE", "test")
POT_DIR = os.environ.get("BOT_POT_DIR") POT_DIR = os.environ.get("BOT_POT_DIR")
POT_KEEP_DAYS = 14 POT_KEEP_DAYS = 14
@ -54,8 +58,9 @@ _logger = logging.getLogger("ci")
def find_sources(): def find_sources():
"""Devuelve odoo-bin y el addons_path: el repo y lo que haya en ODOO_SRC.""" """Devuelve odoo-bin y el addons_path: el repo y lo que haya en ODOO_SRC."""
odoo_bin, paths = None, [REPO] odoo_bin, paths = None, [REPO]
skip = env_list("ODOO_SRC_SKIP")
for src in sorted(ODOO_SRC.iterdir()): 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) _logger.info("Se omite %s (ODOO_SRC_SKIP)", src)
elif (src / "odoo-bin").is_file(): elif (src / "odoo-bin").is_file():
odoo_bin = src / "odoo-bin" odoo_bin = src / "odoo-bin"
@ -67,6 +72,10 @@ def find_sources():
return odoo_bin, paths return odoo_bin, paths
def env_list(name):
return set(os.environ.get(name, "").replace(",", " ").split())
def read_manifests(paths): def read_manifests(paths):
manifests = {} manifests = {}
for path in paths: for path in paths:
@ -101,12 +110,12 @@ def wait_for_postgres(timeout=60):
time.sleep(1) time.sleep(1)
def odoo(odoo_bin, paths, *args): def odoo(odoo_bin, paths, db, *args):
cmd = [ cmd = [
sys.executable, sys.executable,
str(odoo_bin), str(odoo_bin),
f"--addons-path={','.join(map(str, paths))}", 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 # 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 # 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 # 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 return subprocess.run(cmd, check=False).returncode
def export_pots(odoo_bin, paths, addons): def export_pots(odoo_bin, paths, runs):
"""Deja en BOT_POT_DIR/<commit> el .pot de cada addon ya instalado.""" """Deja en BOT_POT_DIR/<commit> el .pot de cada addon ya instalado.
`runs` es una lista de (base de datos, addons instalados en ella).
"""
pot_dir = Path(POT_DIR) pot_dir = Path(POT_DIR)
head = subprocess.run( head = subprocess.run(
["git", "rev-parse", "HEAD"], ["git", "rev-parse", "HEAD"],
@ -136,21 +148,22 @@ def export_pots(odoo_bin, paths, addons):
for stale in (out, tmp): for stale in (out, tmp):
shutil.rmtree(stale, ignore_errors=True) shutil.rmtree(stale, ignore_errors=True)
tmp.mkdir(parents=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) _logger.info("Exportando los .pot a %s", out)
script = EXPORT_POTS.format(addons=addons, out=str(tmp)) for db, addons in runs:
if subprocess.run(cmd, input=script, text=True, check=False).returncode: cmd = [
_logger.warning("No se han podido exportar los .pot") sys.executable,
shutil.rmtree(tmp, ignore_errors=True) str(odoo_bin),
return "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) tmp.rename(out)
limit = time.time() - POT_KEEP_DAYS * 86400 limit = time.time() - POT_KEEP_DAYS * 86400
for old in pot_dir.iterdir(): for old in pot_dir.iterdir():
@ -158,12 +171,27 @@ def export_pots(odoo_bin, paths, addons):
shutil.rmtree(old, ignore_errors=True) 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(): def main():
logging.basicConfig(level=logging.INFO, format="%(message)s") logging.basicConfig(level=logging.INFO, format="%(message)s")
odoo_bin, paths = find_sources() odoo_bin, paths = find_sources()
manifests = read_manifests(paths) manifests = read_manifests(paths)
repo_addons = sorted(m.parent.name for m in REPO.glob("*/__manifest__.py")) 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 [ addons = sys.argv[1:] or [
addon addon
for addon in repo_addons for addon in repo_addons
@ -189,27 +217,25 @@ def main():
"(tools/issue_bot/runner/ en el repo de ecocentral) o el addon a " "(tools/issue_bot/runner/ en el repo de ecocentral) o el addon a "
"EXCLUDE." "EXCLUDE."
) )
dependencies = sorted( isolate = env_list("ISOLATE")
{dep for addon in addons for dep in manifests[addon].get("depends", [])} together = [addon for addon in addons if addon not in isolate]
- set(addons) runs = [(DB, together)] if together else []
) runs += [(f"{DB}_{addon}", [addon]) for addon in addons if addon in isolate]
wait_for_postgres() wait_for_postgres()
subprocess.run(["dropdb", "--if-exists", DB], check=True) failed = []
_logger.info("Instalando las dependencias: %s", ", ".join(dependencies)) for db, run_addons in runs:
init = ["--init", ",".join(dependencies), "--log-level=warn"] if run_tests(odoo_bin, paths, manifests, db, run_addons):
if odoo(odoo_bin, paths, *init): failed.append(db)
sys.exit("No se ha podido crear la base de datos con las dependencias") if failed:
_logger.info("Probando: %s", ", ".join(addons)) sys.exit(f"Fallan los tests en: {', '.join(failed)}")
result = odoo(odoo_bin, paths, "--init", ",".join(addons), "--test-enable") if POT_DIR:
if not result and POT_DIR:
# Que falte la caché no es un fallo de los tests: el bot de revisión # Que falte la caché no es un fallo de los tests: el bot de revisión
# se queda sin las traducciones y lo dice. # se queda sin las traducciones y lo dice.
try: try:
export_pots(odoo_bin, paths, addons) export_pots(odoo_bin, paths, runs)
except OSError as e: except OSError as e:
_logger.warning("No se han podido exportar los .pot: %s", e) _logger.warning("No se han podido exportar los .pot: %s", e)
sys.exit(result)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -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. 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`. 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: 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 - Todo repo del que dependa un addon de aquí tiene que estar en la parte