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
104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
#!/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()
|