[ADD] ci: CI de Forgejo, bots y pre-commit en todo el repo #19
3 changed files with 73 additions and 36 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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__":
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue