[FIX] migration: updating the addons early left step 2 « not started »
A database coming from an old version is updated before the neutralization — the tool offers it at the very start. That work IS step 2, done earlier, but it was only recorded as state_1_update_all while the screen reads state_2_*. So the step stayed « not started » right after running. The costly half was invisible. Step 2 skipped the work through a local variable, which is False again on a resume: with only the old flag written, resuming a migration re-ran update_addons_all on an already-updated database. Hours, for nothing. The check now reads the written trace, not the session. The screen says when it happened rather than just « done », since the moment is exactly what raised the doubt. Logs written before this fix read correctly without being touched — the early flag alone is recognised. Checked on the real log from the VM: step 2 goes from « not started » to « done early, before the neutralization », the four other steps unchanged. 10 tests; removing the fix makes the resume one fail. --- FR --- Une base venant d'une vieille version se met à jour avant la neutralisation — l'outil le propose au tout début. Ce travail EST l'étape 2, faite plus tôt, mais il n'était enregistré que sous state_1_update_all quand l'écran lit state_2_*. L'étape restait donc « non démarrée » juste après avoir tourné. La moitié coûteuse était invisible. L'étape 2 sautait le travail grâce à une variable locale, remise à False à la reprise : avec seulement l'ancien drapeau écrit, reprendre une migration relançait update_addons_all sur une base déjà à jour. Des heures, pour rien. Le contrôle lit maintenant la trace écrite, pas la session. L'écran dit quand cela a eu lieu plutôt que « terminée », le moment étant justement la source du doute. Les journaux écrits avant ce correctif se lisent correctement sans être modifiés — le drapeau précoce seul est reconnu. Vérifié sur le vrai journal de la VM : l'étape 2 passe de « non démarrée » à « faite plus tôt, avant la neutralisation », les quatre autres inchangées. 10 tests ; retirer le correctif fait tomber celui de la reprise. Assisted-by: Claude Opus 5
This commit is contained in:
parent
4b6de55f30
commit
98be22cb77
3 changed files with 149 additions and 4 deletions
|
|
@ -3868,6 +3868,10 @@ TRANSLATIONS = {
|
|||
"fr": "Journal de la migration précédente conservé dans",
|
||||
"en": "Previous migration log kept in",
|
||||
},
|
||||
"done early, before the neutralization": {
|
||||
"fr": "faite plus tôt, avant la neutralisation",
|
||||
"en": "done early, before the neutralization",
|
||||
},
|
||||
# --- script/analyse : vues personnalisées ---
|
||||
"Customised views": {
|
||||
"fr": "Vues personnalisées",
|
||||
|
|
|
|||
|
|
@ -108,6 +108,26 @@ class TodoUpgrade:
|
|||
)
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def needs_update_all(dct_progression, already_done_early=False):
|
||||
"""Faut-il encore mettre à jour tous les modules ?
|
||||
|
||||
Trois façons de l'avoir déjà fait, et il faut les trois. Le drapeau de
|
||||
l'étape ; celui de la mise à jour précoce, posée avant la
|
||||
neutralisation quand la base vient d'une vieille version ; et la
|
||||
variable de la session en cours.
|
||||
|
||||
Le second est ce qui manquait. Il n'est vrai que dans la session qui
|
||||
l'a posé, alors qu'à la reprise seule la trace écrite subsiste : sans
|
||||
le lire ici, reprendre une migration relançait `update_addons_all` sur
|
||||
une base déjà à jour — des heures, pour rien.
|
||||
"""
|
||||
return not (
|
||||
dct_progression.get("state_2_update_all")
|
||||
or dct_progression.get("state_1_update_all")
|
||||
or already_done_early
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def step_status(dct_progression, step):
|
||||
"""Return (icon, detail) telling how far a migration step went.
|
||||
|
|
@ -121,6 +141,15 @@ class TodoUpgrade:
|
|||
for key, value in dct_progression.items()
|
||||
if key.startswith(prefix)
|
||||
}
|
||||
# Un journal écrit avant que l'étape 2 n'enregistre son propre drapeau
|
||||
# ne porte que state_1_update_all. Le travail a bien eu lieu ; le lire
|
||||
# ici évite de dire « non démarrée » d'une étape terminée.
|
||||
if (
|
||||
step == 2
|
||||
and not dct_flag
|
||||
and dct_progression.get("state_1_update_all")
|
||||
):
|
||||
return "✅", t("done early, before the neutralization")
|
||||
if not dct_flag:
|
||||
return "⬜", t("not started")
|
||||
|
||||
|
|
@ -154,6 +183,10 @@ class TodoUpgrade:
|
|||
return ("✅" if total and done == total else "⏳"), detail
|
||||
|
||||
if all(dct_flag.values()):
|
||||
if step == 2 and dct_progression.get("state_2_done_early"):
|
||||
# Le doute vient du moment, pas du résultat : le dire évite de
|
||||
# relancer une mise à jour qui a déjà eu lieu.
|
||||
return "✅", t("done early, before the neutralization")
|
||||
return "✅", t("done")
|
||||
return "⏳", t("partially done")
|
||||
|
||||
|
|
@ -1313,6 +1346,13 @@ class TodoUpgrade:
|
|||
if not status:
|
||||
already_update_state_1 = True
|
||||
self.dct_progression["state_1_update_all"] = True
|
||||
# La mise à jour de tous les modules EST l'étape 2, même
|
||||
# faite plus tôt. Ne l'enregistrer que sous state_1 la
|
||||
# laissait « non démarrée » à l'écran alors qu'elle venait
|
||||
# de tourner, et faisait la refaire entièrement à la
|
||||
# reprise suivante — sur une grosse base, des heures.
|
||||
self.dct_progression["state_2_update_all"] = True
|
||||
self.dct_progression["state_2_done_early"] = True
|
||||
self.write_config()
|
||||
|
||||
print("✅ -> Update database before neutralize by module")
|
||||
|
|
@ -1400,10 +1440,7 @@ class TodoUpgrade:
|
|||
print(f"🔷 {msg}")
|
||||
self.add_comment_progression(msg)
|
||||
|
||||
if (
|
||||
not self.dct_progression.get("state_2_update_all")
|
||||
and not already_update_state_1
|
||||
):
|
||||
if self.needs_update_all(self.dct_progression, already_update_state_1):
|
||||
status, cmd_executed = self.todo_upgrade_execute(
|
||||
f"./script/addons/update_addons_all.sh {database_name}",
|
||||
single_source_odoo=True,
|
||||
|
|
|
|||
104
test/test_todo_upgrade_steps.py
Normal file
104
test/test_todo_upgrade_steps.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env python3
|
||||
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
"""L'écran de reprise doit dire ce qui a eu lieu, pas ce qu'il croit.
|
||||
|
||||
Une base venant d'une vieille version se met à jour AVANT la neutralisation :
|
||||
l'outil le propose au tout début. Ce travail est celui de l'étape 2, fait plus
|
||||
tôt — mais il n'était enregistré que sous `state_1_update_all`, et l'écran
|
||||
cherchait `state_2_*`.
|
||||
|
||||
Deux conséquences, l'une visible et l'autre chère : l'étape restait « non
|
||||
démarrée » alors qu'elle venait de tourner, et la reprise suivante relançait
|
||||
`update_addons_all` sur une base déjà à jour.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from script.todo import todo_i18n
|
||||
from script.todo.todo_upgrade import MIGRATION_STEP, TodoUpgrade
|
||||
|
||||
|
||||
class StatusCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# PAS set_lang() : il persiste la langue dans env_var.sh, suivi par
|
||||
# git. On écrit la mémoïsation, et on la rend.
|
||||
self.addCleanup(
|
||||
setattr, todo_i18n, "_current_lang", todo_i18n._current_lang
|
||||
)
|
||||
todo_i18n._current_lang = "en"
|
||||
|
||||
|
||||
class TestUpdateAllStatus(StatusCase):
|
||||
def status(self, dct):
|
||||
return TodoUpgrade.step_status(dct, 2)
|
||||
|
||||
def test_an_old_log_knows_only_the_early_flag(self):
|
||||
# Le cas signalé : un journal écrit avant le correctif. L'étape a bien
|
||||
# eu lieu, il ne faut pas la dire « non démarrée ».
|
||||
icon, detail = self.status({"state_1_update_all": True})
|
||||
self.assertEqual(icon, "✅")
|
||||
self.assertIn("early", detail)
|
||||
|
||||
def test_a_new_log_says_when_it_happened(self):
|
||||
icon, detail = self.status(
|
||||
{
|
||||
"state_1_update_all": True,
|
||||
"state_2_update_all": True,
|
||||
"state_2_done_early": True,
|
||||
}
|
||||
)
|
||||
self.assertEqual(icon, "✅")
|
||||
self.assertIn("early", detail)
|
||||
|
||||
def test_done_at_its_normal_place_says_only_done(self):
|
||||
icon, detail = self.status({"state_2_update_all": True})
|
||||
self.assertEqual(icon, "✅")
|
||||
self.assertNotIn("early", detail)
|
||||
|
||||
def test_really_not_started(self):
|
||||
icon, detail = self.status({"state_0_install_odoo": True})
|
||||
self.assertEqual(icon, "⬜")
|
||||
self.assertIn("not started", detail)
|
||||
|
||||
def test_the_other_steps_are_untouched(self):
|
||||
# Le correctif ne vaut que pour l'étape 2 : les autres gardent leur
|
||||
# règle, sinon un drapeau d'une étape en éclairerait une autre.
|
||||
dct = {"state_1_update_all": True}
|
||||
for step, _ in MIGRATION_STEP:
|
||||
if step in (1, 2):
|
||||
continue
|
||||
icon, _ = TodoUpgrade.step_status(dct, step)
|
||||
self.assertEqual(icon, "⬜", f"étape {step}")
|
||||
|
||||
|
||||
class TestNeedsUpdateAll(unittest.TestCase):
|
||||
"""Ce qui décide de relancer, ou non, une mise à jour de plusieurs heures."""
|
||||
|
||||
def test_nothing_done_yet(self):
|
||||
self.assertTrue(TodoUpgrade.needs_update_all({}))
|
||||
|
||||
def test_its_own_flag_is_enough(self):
|
||||
self.assertFalse(
|
||||
TodoUpgrade.needs_update_all({"state_2_update_all": True})
|
||||
)
|
||||
|
||||
def test_the_early_flag_survives_a_resume(self):
|
||||
# LE défaut : la variable de session repart à False à la reprise, et
|
||||
# seule la trace écrite subsiste. Sans la lire, on relançait tout.
|
||||
self.assertFalse(
|
||||
TodoUpgrade.needs_update_all({"state_1_update_all": True})
|
||||
)
|
||||
|
||||
def test_the_session_variable_still_counts(self):
|
||||
self.assertFalse(TodoUpgrade.needs_update_all({}, True))
|
||||
|
||||
def test_an_unrelated_flag_does_not_count(self):
|
||||
self.assertTrue(
|
||||
TodoUpgrade.needs_update_all({"state_1_restore_database": True})
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue