[ADD] make: une cible pour les tests unitaires, dépendance mobile déclarée
Ces 382 tests ne tournaient que lancés à la main, donc jamais. « make test » demande une base de données et plusieurs minutes ; ceux-ci lisent le code et exécutent les fragments de shell générés, sudo, pgrep et pkill bouchonnés, en une dizaine de secondes. Deux cibles : test_unit, et test_unit_file pour la boucle d'écriture. La dépendance à mobile/erplibre_home_mobile est DITE plutôt que supposée : le lanceur l'annonce présente ou absente, et les tests du vrai transfert se déclarent ignorés — avec la commande qui manque — au lieu de passer en silence. Vérifié dans les trois états : dépôt absent, présent non compilé, compilé. Au passage, compile_and_run.sh vérifie le transfert des dépôts, comme l'installation d'une VM et par le même script. --- EN --- These 382 tests only ran when invoked by hand, so never. "make test" wants a database and several minutes; these read the code and run the generated shell fragments with sudo, pgrep and pkill stubbed, in about ten seconds. Two targets: test_unit, and test_unit_file for the writing loop. The dependency on mobile/erplibre_home_mobile is STATED rather than assumed: the runner announces it present or absent, and the real-transfer tests declare themselves skipped — naming the missing command — instead of passing quietly. Checked in all three states: repo absent, present but unbuilt, built. Along the way, compile_and_run.sh verifies the repo transfer, like a VM install and through the same script. Assisted-by: Claude Opus 5
This commit is contained in:
parent
deae7d16c7
commit
5b748e347e
6 changed files with 208 additions and 7 deletions
|
|
@ -34,7 +34,17 @@ make db_clone_test_to_test2 # Cloner test -> test2
|
|||
```
|
||||
|
||||
## Tests
|
||||
### Suite complète
|
||||
### Unitaires python (rapides, sans base de données)
|
||||
```bash
|
||||
make test_unit # tout ce qui tourne sans DB ni Odoo
|
||||
make test_unit_file F=test/test_qemu_motd.py # un seul fichier
|
||||
```
|
||||
Ils lisent le code et exécutent les fragments de shell générés par `todo.py`,
|
||||
avec `sudo`, `pgrep` et `pkill` bouchonnés — quelques secondes. Le lanceur
|
||||
annonce sa dépendance à `mobile/erplibre_home_mobile` : absent, les tests du
|
||||
transfert mobile se déclarent ignorés au lieu de passer en silence.
|
||||
|
||||
### Suite complète (base de données requise)
|
||||
```bash
|
||||
make test # Tests de base + format
|
||||
make test_full_fast # Tests complets en parallèle
|
||||
|
|
|
|||
|
|
@ -4,6 +4,31 @@
|
|||
|
||||
# TODO load specific test file : ./run.sh -d test_file --log-level=test --test-enable --stop-after-init --test-file ./.venv.erplibre/test.py
|
||||
|
||||
###########################
|
||||
# Tests unitaires python #
|
||||
###########################
|
||||
# Ni base de données, ni Odoo, ni VM : ils lisent le code et exécutent les
|
||||
# fragments de shell que todo.py génère, « sudo », « pgrep » et « pkill »
|
||||
# bouchonnés. Une dizaine de secondes, là où « make test » demande une base et
|
||||
# plusieurs minutes — d'où une cible à part, faite pour être lancée souvent.
|
||||
#
|
||||
# DÉPENDANCE : les tests du transfert mobile lisent
|
||||
# mobile/erplibre_home_mobile. Le lanceur l'annonce présent ou absent avant de
|
||||
# commencer, et les tests concernés se déclarent ignorés plutôt que de passer
|
||||
# en silence — un test vert sans son dépôt ne prouve rien.
|
||||
#
|
||||
# « test_todo.py » n'en fait PAS partie : il demande une base de données. Son
|
||||
# nom sans souligné le laisse hors du motif, et c'est voulu.
|
||||
.PHONY: test_unit
|
||||
test_unit:
|
||||
./script/test/run_unit_test.sh
|
||||
|
||||
# Un seul fichier, pour la boucle d'écriture :
|
||||
# make test_unit_file F=test/test_qemu_forgejo.py
|
||||
.PHONY: test_unit_file
|
||||
test_unit_file:
|
||||
./script/test/run_unit_test.sh $(F)
|
||||
|
||||
.PHONY: open_test_coverage
|
||||
open_test_coverage:
|
||||
-$(BROWSER) htmlcov/index.html
|
||||
|
|
|
|||
|
|
@ -5,10 +5,26 @@ if [[ ! -d "./mobile/erplibre_home_mobile" ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
cd mobile/erplibre_home_mobile
|
||||
WORKSPACE="$(pwd)"
|
||||
|
||||
cd mobile/erplibre_home_mobile || exit 1
|
||||
|
||||
npm install
|
||||
npm run build && npx cap sync
|
||||
npm run build || exit 1
|
||||
|
||||
# Le transfert des dépôts du manifeste DANS l'application est ce qui fait
|
||||
# l'intérêt de son navigateur de code hors ligne, et il peut être vide sans que
|
||||
# la compilation le dise. Ces dépôts entrent en packs — un APK est un ZIP borné
|
||||
# à 65535 entrées, quand un fichier par source en réclamait 123 678 — et le
|
||||
# vérificateur relit un échantillon depuis les tranches pour le comparer à la
|
||||
# source. Trois pannes qu'un « build OK » passe sous silence : transfert vide,
|
||||
# tranche absente, index qui promet des octets que sa tranche n'a pas.
|
||||
#
|
||||
# Même vérification que l'installation d'une VM, même script : une seule
|
||||
# autorité.
|
||||
"${WORKSPACE}/script/mobile/check_bundle_transfer.py" . --workspace "${WORKSPACE}" || exit 1
|
||||
|
||||
npx cap sync || exit 1
|
||||
npx cap run android
|
||||
|
||||
cd -
|
||||
cd - || exit 1
|
||||
|
|
|
|||
72
script/test/run_unit_test.sh
Executable file
72
script/test/run_unit_test.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env bash
|
||||
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
#
|
||||
# Tests unitaires python du dépôt : ni base de données, ni Odoo, ni VM.
|
||||
#
|
||||
# Ils lisent le code et exécutent les fragments de shell que todo.py génère,
|
||||
# avec « sudo », « pgrep » et « pkill » bouchonnés — c'est ce qui les rend
|
||||
# lançables partout et en quelques secondes, là où « make test » demande une
|
||||
# base et plusieurs minutes.
|
||||
#
|
||||
# DÉPENDANCE DÉCLARÉE : les tests du transfert mobile lisent
|
||||
# mobile/erplibre_home_mobile. Absent, ils se disent ignorés plutôt que de
|
||||
# passer en silence — un test vert sans son dépôt ne prouve rien. Ce script
|
||||
# l'annonce donc avant de commencer.
|
||||
#
|
||||
# ./script/test/run_unit_test.sh [fichiers...]
|
||||
set -uo pipefail
|
||||
|
||||
Red='\033[0;31m'
|
||||
Green='\033[0;32m'
|
||||
Yellow='\033[0;33m'
|
||||
Color_Off='\033[0m'
|
||||
|
||||
cd "$(dirname "$0")/../.." || exit 1
|
||||
|
||||
PY=./.venv.erplibre/bin/python
|
||||
if [[ ! -x "${PY}" ]]; then
|
||||
echo -e "${Red}✗ ${PY} absent : lancer l'installation ERPLibre d'abord.${Color_Off}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MOBILE=mobile/erplibre_home_mobile
|
||||
if [[ -d "${MOBILE}" ]]; then
|
||||
echo -e " dépendance ${MOBILE} : ${Green}présente${Color_Off}"
|
||||
else
|
||||
echo -e " dépendance ${MOBILE} : ${Yellow}absente${Color_Off}"
|
||||
echo " (les tests du transfert mobile s'en passeront et le diront)"
|
||||
fi
|
||||
|
||||
FILES=("$@")
|
||||
if [[ ${#FILES[@]} -eq 0 ]]; then
|
||||
# Aucun argument : tout ce que le dépôt sait tester sans base de données.
|
||||
mapfile -t FILES < <(ls test/test_qemu_*.py test/test_mobile_*.py \
|
||||
test/test_todo_*.py 2>/dev/null)
|
||||
fi
|
||||
|
||||
fail=0
|
||||
total=0
|
||||
for f in "${FILES[@]}"; do
|
||||
out=$(PYTHONPATH=. "${PY}" "${f}" 2>&1)
|
||||
ran=$(echo "${out}" | grep -oE 'Ran [0-9]+' | grep -oE '[0-9]+' | tail -1)
|
||||
skipped=$(echo "${out}" | grep -oE 'skipped=[0-9]+' | tail -1)
|
||||
if echo "${out}" | grep -qE '^OK'; then
|
||||
state="${Green}OK${Color_Off}"
|
||||
else
|
||||
state="${Red}ÉCHEC${Color_Off}"
|
||||
fail=1
|
||||
fi
|
||||
total=$((total + ${ran:-0}))
|
||||
printf " %-42s %5s tests %-14s %b\n" \
|
||||
"$(basename "${f}")" "${ran:-?}" "${skipped:-}" "${state}"
|
||||
[[ "${state}" == *"ÉCHEC"* ]] && echo "${out}" | tail -12
|
||||
done
|
||||
|
||||
echo " ─────"
|
||||
if [[ ${fail} -eq 0 ]]; then
|
||||
echo -e " ${Green}${total} tests, tout vert${Color_Off}"
|
||||
else
|
||||
echo -e " ${Red}des échecs ci-dessus${Color_Off}"
|
||||
fi
|
||||
exit ${fail}
|
||||
|
|
@ -2058,13 +2058,17 @@ class TODO:
|
|||
host, from_ssh = self._qemu_self_address()
|
||||
user = os.environ.get("USER", "user")
|
||||
print(f"\n {t('No display here; run this on YOUR workstation:')}")
|
||||
print(f"\n virt-viewer -c qemu+ssh://{user}@{host}/system {domain}\n")
|
||||
print(
|
||||
f"\n virt-viewer -c qemu+ssh://{user}@{host}/system {domain}\n"
|
||||
)
|
||||
if not from_ssh:
|
||||
print(f" ⚠ {t('Not in an SSH session: check the host address.')}")
|
||||
print(f" {t('A ~/.ssh/config alias works there too.')}")
|
||||
print(f" {t('It builds its own tunnel; no ssh -L to keep open.')}")
|
||||
print(f" {t('Missing? Install virt-viewer:')} apt / dnf / pacman"
|
||||
" / zypper")
|
||||
print(
|
||||
f" {t('Missing? Install virt-viewer:')} apt / dnf / pacman"
|
||||
" / zypper"
|
||||
)
|
||||
|
||||
def _qemu_console_tunnel(self, name, src):
|
||||
"""Tunnel vers l'ÉCRAN QEMU d'une VM, pas vers un serveur de l'invité.
|
||||
|
|
|
|||
|
|
@ -263,6 +263,80 @@ class TestTheCommandLine(unittest.TestCase):
|
|||
self.assertNotIn("Traceback", buf.getvalue())
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
MOBILE = REPO / "mobile" / "erplibre_home_mobile"
|
||||
|
||||
|
||||
class TestTheRealBundle(unittest.TestCase):
|
||||
"""Le VRAI transfert, quand le dépôt mobile est installé et compilé.
|
||||
|
||||
C'est ici que ces tests DÉCLARENT leur dépendance : sans
|
||||
mobile/erplibre_home_mobile, ils se disent ignorés plutôt que de passer en
|
||||
silence — un test vert sans son dépôt ne prouve rien. Le lanceur
|
||||
(script/test/run_unit_test.sh) annonce la même dépendance avant de
|
||||
commencer.
|
||||
|
||||
Ce qu'ils gardent : qu'une compilation réelle produise bien des PACKS. Un
|
||||
retour au fichier-par-source ferait disparaître le champ « chunk » des
|
||||
index, et la limite du ZIP reviendrait — 123 678 entrées pour un plafond de
|
||||
65 535, silencieusement, jusqu'à l'APK.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if not MOBILE.is_dir():
|
||||
# Pas de « relative_to » : il lève quand le chemin sort du
|
||||
# dépôt, et une erreur n'est pas un « ignoré » — mesuré en
|
||||
# simulant l'absence.
|
||||
raise unittest.SkipTest(
|
||||
"mobile/erplibre_home_mobile absent :"
|
||||
" ./mobile/install_mobile_dev.sh"
|
||||
)
|
||||
cls.repos = MOBILE / "dist" / "repos"
|
||||
if not (cls.repos / "manifest.json").is_file():
|
||||
raise unittest.SkipTest(
|
||||
"dépôt mobile présent mais pas compilé :"
|
||||
" ./mobile/compile_and_run.sh (ou npm run build)"
|
||||
)
|
||||
|
||||
def test_the_transfer_is_coherent(self):
|
||||
rep = cbt.check(MOBILE, REPO)
|
||||
self.assertGreater(rep["repos"], 1)
|
||||
self.assertGreater(rep["files"], cbt.MIN_FILES)
|
||||
self.assertGreater(rep["packs"], 0)
|
||||
|
||||
def test_a_sample_matches_the_source(self):
|
||||
"""La seule vérification qui prouve un transfert FIDÈLE."""
|
||||
rep = cbt.check(MOBILE, REPO)
|
||||
self.assertGreater(rep["compared"], 0)
|
||||
|
||||
def test_the_indexes_are_packed_not_file_per_source(self):
|
||||
"""Le garde-fou de la limite du ZIP : chaque fichier doit porter sa
|
||||
tranche. Sans « chunk », c'est un fichier par source, et l'APK sera
|
||||
refusé — mais bien plus tard, et sans dire pourquoi."""
|
||||
man = json.loads((self.repos / "manifest.json").read_text())
|
||||
checked = 0
|
||||
for proj in man[:5]:
|
||||
index = self.repos / proj["slug"] / "index.json"
|
||||
entries = json.loads(index.read_text())
|
||||
files = [e for e in entries if e.get("type") == "file"]
|
||||
if not files:
|
||||
continue
|
||||
self.assertTrue(
|
||||
all("chunk" in e for e in files),
|
||||
f"{proj['slug']} : des fichiers sans tranche",
|
||||
)
|
||||
checked += 1
|
||||
self.assertGreater(checked, 0, "aucun dépôt à vérifier")
|
||||
|
||||
def test_no_bundled_test_file_lingers_as_a_source(self):
|
||||
"""Effet de bord mesuré, et il compte : empaquetés, les 1 599 fichiers
|
||||
de test des dépôts Odoo ne sont plus ramassés par vitest — 1 423
|
||||
fichiers de test ramenés à 75, 35 s ramenées à 3 s."""
|
||||
stray = list(self.repos.glob("*/**/*.test.ts"))
|
||||
self.assertEqual([], stray)
|
||||
|
||||
|
||||
import contextlib as _contextlib # noqa: E402
|
||||
import unittest.mock # noqa: E402
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue