2026-03-11 22:50:02 -04:00
|
|
|
#!/usr/bin/env python3
|
2026-03-11 23:15:07 -04:00
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
2026-03-11 22:50:02 -04:00
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import tempfile
|
|
|
|
|
import unittest
|
[REF] format : passer l'outillage et les tests sous ruff
Le formateur de ce dépôt est ruff depuis qu'il remplace black, qui ne connaît
aucune cible au-delà de py313 ; ce passage applique sa norme à l'arbre entier,
d'un coup, pour qu'aucun commit de fond n'ait à porter du style. L'écart tient
presque entièrement aux chaînes coupées à la main que ruff recolle quand elles
tiennent sur une ligne, et aux « with » multiples qu'il regroupe : aucune
valeur ne change, et les clés de traduction non plus.
Vérifié : la suite unitaire reste verte après le passage, et le contrôle de
syntaxe ne signale rien.
--- EN ---
This repository's formatter is ruff since it replaced black, which knows no
target beyond py313; this pass applies its standard to the whole tree at once,
so that no substantive commit has to carry style. The difference is almost
entirely the hand-split strings ruff joins back when they fit on one line, and
the multiple "with" it merges: no value changes, nor do the translation keys.
Checked: the unit suite stays green after the pass, and the syntax check
reports nothing.
Assisted-by: Claude Opus 5
2026-09-24 13:30:31 -04:00
|
|
|
from unittest.mock import MagicMock, patch
|
2026-03-11 22:50:02 -04:00
|
|
|
|
|
|
|
|
from script.version.update_env_version import (
|
[REF] format : passer l'outillage et les tests sous ruff
Le formateur de ce dépôt est ruff depuis qu'il remplace black, qui ne connaît
aucune cible au-delà de py313 ; ce passage applique sa norme à l'arbre entier,
d'un coup, pour qu'aucun commit de fond n'ait à porter du style. L'écart tient
presque entièrement aux chaînes coupées à la main que ruff recolle quand elles
tiennent sur une ligne, et aux « with » multiples qu'il regroupe : aucune
valeur ne change, et les clés de traduction non plus.
Vérifié : la suite unitaire reste verte après le passage, et le contrôle de
syntaxe ne signale rien.
--- EN ---
This repository's formatter is ruff since it replaced black, which knows no
target beyond py313; this pass applies its standard to the whole tree at once,
so that no substantive commit has to carry style. The difference is almost
entirely the hand-split strings ruff joins back when they fit on one line, and
the multiple "with" it merges: no value changes, nor do the translation keys.
Checked: the unit suite stays green after the pass, and the syntax check
reports nothing.
Assisted-by: Claude Opus 5
2026-09-24 13:30:31 -04:00
|
|
|
ADDONS_TEMPLATE_FILE,
|
2026-03-11 22:50:02 -04:00
|
|
|
ERPLIBRE_TEMPLATE_VERSION,
|
|
|
|
|
MANIFEST_TEMPLATE_FILE,
|
|
|
|
|
ODOO_TEMPLATE_FILE,
|
[REF] format : passer l'outillage et les tests sous ruff
Le formateur de ce dépôt est ruff depuis qu'il remplace black, qui ne connaît
aucune cible au-delà de py313 ; ce passage applique sa norme à l'arbre entier,
d'un coup, pour qu'aucun commit de fond n'ait à porter du style. L'écart tient
presque entièrement aux chaînes coupées à la main que ruff recolle quand elles
tiennent sur une ligne, et aux « with » multiples qu'il regroupe : aucune
valeur ne change, et les clés de traduction non plus.
Vérifié : la suite unitaire reste verte après le passage, et le contrôle de
syntaxe ne signale rien.
--- EN ---
This repository's formatter is ruff since it replaced black, which knows no
target beyond py313; this pass applies its standard to the whole tree at once,
so that no substantive commit has to carry style. The difference is almost
entirely the hand-split strings ruff joins back when they fit on one line, and
the multiple "with" it merges: no value changes, nor do the translation keys.
Checked: the unit suite stays green after the pass, and the syntax check
reports nothing.
Assisted-by: Claude Opus 5
2026-09-24 13:30:31 -04:00
|
|
|
POETRY_LOCK_TEMPLATE_FILE,
|
|
|
|
|
PYPROJECT_TEMPLATE_FILE,
|
|
|
|
|
VENV_TEMPLATE_FILE,
|
|
|
|
|
Update,
|
|
|
|
|
die,
|
|
|
|
|
remove_dot_path,
|
2026-03-11 22:50:02 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestRemoveDotPath(unittest.TestCase):
|
|
|
|
|
def test_removes_dot_slash(self):
|
|
|
|
|
self.assertEqual(remove_dot_path("./path/to/file"), "path/to/file")
|
|
|
|
|
|
|
|
|
|
def test_no_dot_slash(self):
|
|
|
|
|
self.assertEqual(remove_dot_path("path/to/file"), "path/to/file")
|
|
|
|
|
|
|
|
|
|
def test_just_dot_slash(self):
|
|
|
|
|
self.assertEqual(remove_dot_path("./"), "")
|
|
|
|
|
|
|
|
|
|
def test_nested_dot_slash(self):
|
2026-09-03 23:42:34 -04:00
|
|
|
self.assertEqual(remove_dot_path("./a/./b"), "a/./b")
|
2026-03-11 22:50:02 -04:00
|
|
|
|
|
|
|
|
def test_empty_string(self):
|
|
|
|
|
self.assertEqual(remove_dot_path(""), "")
|
|
|
|
|
|
|
|
|
|
def test_single_file(self):
|
|
|
|
|
self.assertEqual(remove_dot_path("file.txt"), "file.txt")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestDie(unittest.TestCase):
|
|
|
|
|
def test_die_true_exits(self):
|
|
|
|
|
with self.assertRaises(SystemExit) as cm:
|
|
|
|
|
die(True, "error message")
|
|
|
|
|
self.assertEqual(cm.exception.code, 1)
|
|
|
|
|
|
|
|
|
|
def test_die_false_no_exit(self):
|
|
|
|
|
die(False, "no error")
|
|
|
|
|
|
|
|
|
|
def test_die_custom_code(self):
|
|
|
|
|
with self.assertRaises(SystemExit) as cm:
|
|
|
|
|
die(True, "error", code=42)
|
|
|
|
|
self.assertEqual(cm.exception.code, 42)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestConstants(unittest.TestCase):
|
|
|
|
|
def test_erplibre_template_version(self):
|
|
|
|
|
result = ERPLIBRE_TEMPLATE_VERSION % ("18.0", "3.12.10")
|
|
|
|
|
self.assertEqual(result, "odoo18.0_python3.12.10")
|
|
|
|
|
|
|
|
|
|
def test_venv_template(self):
|
|
|
|
|
result = VENV_TEMPLATE_FILE % "odoo18.0_python3.12.10"
|
|
|
|
|
self.assertEqual(result, ".venv.odoo18.0_python3.12.10")
|
|
|
|
|
|
|
|
|
|
def test_manifest_template(self):
|
|
|
|
|
result = MANIFEST_TEMPLATE_FILE % "18.0"
|
|
|
|
|
self.assertEqual(result, "default.dev.odoo18.0.xml")
|
|
|
|
|
|
|
|
|
|
def test_pyproject_template(self):
|
|
|
|
|
result = PYPROJECT_TEMPLATE_FILE % "odoo18.0_python3.12.10"
|
2026-09-03 23:42:34 -04:00
|
|
|
self.assertEqual(result, "pyproject.odoo18.0_python3.12.10.toml")
|
2026-03-11 22:50:02 -04:00
|
|
|
|
|
|
|
|
def test_poetry_lock_template(self):
|
|
|
|
|
result = POETRY_LOCK_TEMPLATE_FILE % "odoo18.0_python3.12.10"
|
2026-09-03 23:42:34 -04:00
|
|
|
self.assertEqual(result, "poetry.odoo18.0_python3.12.10.lock")
|
2026-03-11 22:50:02 -04:00
|
|
|
|
|
|
|
|
def test_addons_template(self):
|
|
|
|
|
result = ADDONS_TEMPLATE_FILE % "18.0"
|
|
|
|
|
self.assertEqual(result, "odoo18.0/addons")
|
|
|
|
|
|
|
|
|
|
def test_odoo_template(self):
|
|
|
|
|
result = ODOO_TEMPLATE_FILE % "18.0"
|
|
|
|
|
self.assertEqual(result, "odoo18.0")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestUpdateValidateVersion(unittest.TestCase):
|
|
|
|
|
"""Test validate_version() sets expected paths based on version data."""
|
|
|
|
|
|
|
|
|
|
def _make_update(self, data_version, config_args=None):
|
|
|
|
|
with patch("sys.argv", ["prog"]):
|
|
|
|
|
update = Update()
|
|
|
|
|
update.data_version = data_version
|
|
|
|
|
if config_args:
|
|
|
|
|
for k, v in config_args.items():
|
|
|
|
|
setattr(update.config, k, v)
|
|
|
|
|
return update
|
|
|
|
|
|
|
|
|
|
def test_explicit_erplibre_version(self):
|
|
|
|
|
data = {
|
|
|
|
|
"odoo18.0_python3.12.10": {
|
|
|
|
|
"odoo_version": "18.0",
|
|
|
|
|
"python_version": "3.12.10",
|
|
|
|
|
"poetry_version": "2.1.3",
|
|
|
|
|
"default": True,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
update = self._make_update(
|
|
|
|
|
data,
|
|
|
|
|
{"erplibre_version": "odoo18.0_python3.12.10"},
|
|
|
|
|
)
|
|
|
|
|
update.validate_version()
|
|
|
|
|
self.assertEqual(update.new_version_odoo, "18.0")
|
|
|
|
|
self.assertEqual(update.new_version_python, "3.12.10")
|
|
|
|
|
self.assertEqual(update.new_version_poetry, "2.1.3")
|
2026-09-03 23:42:34 -04:00
|
|
|
self.assertEqual(update.new_version_erplibre, "odoo18.0_python3.12.10")
|
2026-03-11 22:50:02 -04:00
|
|
|
|
|
|
|
|
def test_explicit_odoo_version(self):
|
|
|
|
|
data = {}
|
|
|
|
|
update = self._make_update(data, {"odoo_version": "17.0"})
|
|
|
|
|
update.new_version_python = "3.10.18"
|
|
|
|
|
update.config.python_version = "3.10.18"
|
|
|
|
|
update.validate_version()
|
|
|
|
|
self.assertEqual(update.new_version_odoo, "17.0")
|
|
|
|
|
|
|
|
|
|
def test_default_version_fallback(self):
|
|
|
|
|
data = {
|
|
|
|
|
"odoo18.0_python3.12.10": {
|
|
|
|
|
"odoo_version": "18.0",
|
|
|
|
|
"python_version": "3.12.10",
|
|
|
|
|
"poetry_version": "2.1.3",
|
|
|
|
|
"default": True,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
update = self._make_update(data)
|
|
|
|
|
update.detected_version_erplibre = None
|
|
|
|
|
update.validate_version()
|
|
|
|
|
self.assertEqual(update.new_version_odoo, "18.0")
|
|
|
|
|
|
|
|
|
|
def test_detected_version_used(self):
|
|
|
|
|
data = {
|
|
|
|
|
"odoo17.0_python3.10.18": {
|
|
|
|
|
"odoo_version": "17.0",
|
|
|
|
|
"python_version": "3.10.18",
|
|
|
|
|
"poetry_version": "1.8.3",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
update = self._make_update(data)
|
|
|
|
|
update.detected_version_erplibre = "odoo17.0_python3.10.18"
|
|
|
|
|
update.validate_version()
|
|
|
|
|
self.assertEqual(update.new_version_odoo, "17.0")
|
|
|
|
|
self.assertEqual(update.new_version_python, "3.10.18")
|
|
|
|
|
|
|
|
|
|
def test_expected_paths_set(self):
|
|
|
|
|
data = {
|
|
|
|
|
"odoo18.0_python3.12.10": {
|
|
|
|
|
"odoo_version": "18.0",
|
|
|
|
|
"python_version": "3.12.10",
|
|
|
|
|
"poetry_version": "2.1.3",
|
|
|
|
|
"default": True,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
update = self._make_update(
|
|
|
|
|
data,
|
|
|
|
|
{"erplibre_version": "odoo18.0_python3.12.10"},
|
|
|
|
|
)
|
|
|
|
|
update.validate_version()
|
2026-09-03 23:42:34 -04:00
|
|
|
self.assertIn(
|
|
|
|
|
"default.dev.odoo18.0.xml", update.expected_manifest_name
|
|
|
|
|
)
|
2026-03-11 22:50:02 -04:00
|
|
|
self.assertIn("requirement", update.expected_pyproject_path)
|
|
|
|
|
self.assertIn("requirement", update.expected_poetry_lock_path)
|
|
|
|
|
self.assertEqual(update.expected_odoo_name, "odoo18.0")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestUpdateDetectVersion(unittest.TestCase):
|
|
|
|
|
def test_detect_version_no_files(self):
|
|
|
|
|
with patch("sys.argv", ["prog"]):
|
|
|
|
|
update = Update()
|
|
|
|
|
with patch("os.path.exists", return_value=False):
|
|
|
|
|
result = update.detect_version()
|
|
|
|
|
self.assertFalse(result)
|
|
|
|
|
|
|
|
|
|
def test_detect_version_matching(self):
|
|
|
|
|
with patch("sys.argv", ["prog"]):
|
|
|
|
|
update = Update()
|
|
|
|
|
update.data_version = {
|
|
|
|
|
"odoo18.0_python3.12.10": {
|
|
|
|
|
"odoo_version": "18.0",
|
|
|
|
|
"python_version": "3.12.10",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
|
py_file = os.path.join(tmpdir, "python_ver")
|
|
|
|
|
odoo_file = os.path.join(tmpdir, "odoo_ver")
|
|
|
|
|
poetry_file = os.path.join(tmpdir, "poetry_ver")
|
|
|
|
|
with open(py_file, "w") as f:
|
|
|
|
|
f.write("3.12.10")
|
|
|
|
|
with open(odoo_file, "w") as f:
|
|
|
|
|
f.write("18.0")
|
|
|
|
|
with open(poetry_file, "w") as f:
|
|
|
|
|
f.write("2.1.3")
|
[REF] format : passer l'outillage et les tests sous ruff
Le formateur de ce dépôt est ruff depuis qu'il remplace black, qui ne connaît
aucune cible au-delà de py313 ; ce passage applique sa norme à l'arbre entier,
d'un coup, pour qu'aucun commit de fond n'ait à porter du style. L'écart tient
presque entièrement aux chaînes coupées à la main que ruff recolle quand elles
tiennent sur une ligne, et aux « with » multiples qu'il regroupe : aucune
valeur ne change, et les clés de traduction non plus.
Vérifié : la suite unitaire reste verte après le passage, et le contrôle de
syntaxe ne signale rien.
--- EN ---
This repository's formatter is ruff since it replaced black, which knows no
target beyond py313; this pass applies its standard to the whole tree at once,
so that no substantive commit has to carry style. The difference is almost
entirely the hand-split strings ruff joins back when they fit on one line, and
the multiple "with" it merges: no value changes, nor do the translation keys.
Checked: the unit suite stays green after the pass, and the syntax check
reports nothing.
Assisted-by: Claude Opus 5
2026-09-24 13:30:31 -04:00
|
|
|
with (
|
|
|
|
|
patch(
|
|
|
|
|
"script.version.update_env_version.VERSION_PYTHON_FILE",
|
|
|
|
|
py_file,
|
|
|
|
|
),
|
|
|
|
|
patch(
|
|
|
|
|
"script.version.update_env_version.VERSION_ODOO_FILE",
|
|
|
|
|
odoo_file,
|
|
|
|
|
),
|
|
|
|
|
patch(
|
|
|
|
|
"script.version.update_env_version.VERSION_POETRY_FILE",
|
|
|
|
|
poetry_file,
|
|
|
|
|
),
|
|
|
|
|
patch(
|
|
|
|
|
"script.version.update_env_version.INSTALLED_ODOO_VERSION_FILE",
|
|
|
|
|
os.path.join(tmpdir, "nonexist"),
|
|
|
|
|
),
|
2026-03-11 22:50:02 -04:00
|
|
|
):
|
|
|
|
|
result = update.detect_version()
|
|
|
|
|
self.assertTrue(result)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
update.detected_version_erplibre, "odoo18.0_python3.12.10"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestUpdatePrintLog(unittest.TestCase):
|
|
|
|
|
def test_empty_log(self):
|
|
|
|
|
with patch("sys.argv", ["prog"]):
|
|
|
|
|
update = Update()
|
|
|
|
|
update.print_log()
|
|
|
|
|
|
|
|
|
|
def test_with_entries(self):
|
|
|
|
|
with patch("sys.argv", ["prog"]):
|
|
|
|
|
update = Update()
|
|
|
|
|
update.execute_log = ["entry1", "entry2"]
|
|
|
|
|
update.print_log()
|
[FIX] tests : balayer test/test_*.py et refuser un fichier muet
Le lanceur ne prenait que sept préfixes de noms, « le reste demandant une
base de données » : les 3703 tests du répertoire passent avec PostgreSQL
injoignable, et la suite n'en exécutait que 1131. Il balaie test/test_*.py.
Deux formes rendent un fichier muet sans erreur : unittest.main() posé en
plein milieu, qui sort avant que la seconde moitié soit définie (quatre
fichiers, 87 tests), et l'absence de bloc __main__, qui compte zéro (huit
fichiers, 174 tests). Une garde refuse ces deux formes et le retour d'une
liste de préfixes.
--- EN ---
The runner took seven filename prefixes only, "the rest needing a
database": all 3703 tests in the directory pass with PostgreSQL
unreachable, and the suite ran 1131 of them. It now globs test/test_*.py.
Two shapes make a file silent without error: unittest.main() placed
mid-file, which exits before the second half is defined (four files, 87
tests), and no __main__ block at all, which counts zero (eight files, 174
tests). A guard now refuses both shapes and the return of a prefix list.
Assisted-by: Claude Opus 5
(cherry picked from commit db71b333cc08ae0fce4ddaaf037357648b10236b)
2026-08-26 07:46:14 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|