diff --git a/script/todo/migration_status.py b/script/todo/migration_status.py index f846dd2..5b00471 100755 --- a/script/todo/migration_status.py +++ b/script/todo/migration_status.py @@ -288,9 +288,28 @@ def failures(dct): return list(reversed(events(dct, kind="command"))) +def elapsed(dct): + """Combien de temps la migration a duré, ou dure encore. + + Du premier écrit à la DERNIÈRE écriture du journal : la progression est + réécrite après chaque geste, donc sa date de mise à jour EST la fin — + ou l'instant présent si la migration tourne toujours. + + On délègue à `migration_stats.fmt_delay`, qui porte déjà ce calcul pour + l'écran de statistiques. Deux formules donneraient deux durées pour la + même migration selon l'écran qu'on ouvre. + """ + try: + from script.todo.migration_stats import fmt_delay + except Exception: + return "?" + return fmt_delay(dct.get("date_create"), dct.get("date_update")) + + def overview(dct): """L'en-tête : de quelle migration parle-t-on.""" return { + "elapsed": elapsed(dct), "file": os.path.basename(dct.get("migration_file") or "?"), "database": dct.get("config_database_name") or "?", "target": dct.get("target_odoo_version") or "?", @@ -340,7 +359,8 @@ def render_text(dct, limit_cmd=12, colour=None): f" {t('database')} : {info['database']}", f" {t('image')} : {info['file']}", f" {t('started')} : {info['started']}", - f" {t('last written')} : {info['updated']}", + f" {t('finished')} : {info['updated']}", + f" {t('duration')} : {info['elapsed']}", f" {t('current step')} : {info['step']}", ] diff --git a/script/todo/migration_status_tui.py b/script/todo/migration_status_tui.py index 6711bf1..02acece 100644 --- a/script/todo/migration_status_tui.py +++ b/script/todo/migration_status_tui.py @@ -48,7 +48,8 @@ def head_text(dct): return ( f"{info['database']} · {info['file']}\n" f"{t('current step')} : {info['step']}\n" - f"{t('last written')} : {info['updated']}" + f"{t('finished')} : {info['updated']}" + f" · {t('duration')} {info['elapsed']}" f" · {len(status.failures(dct))} {t('failed')}" f" · {len(casse)}/{len(lst_test)} {t('Test results')}" ) diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 6724817..9ba8a24 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -5667,6 +5667,14 @@ TRANSLATIONS = { "fr": "Rétrécir", "en": "Narrower", }, + "finished": { + "fr": "terminée", + "en": "finished", + }, + "duration": { + "fr": "durée", + "en": "duration", + }, "Clean the database before testing the pages?": { "fr": "Nettoyer la base avant de tester les pages ?", "en": "Clean the database before testing the pages?", diff --git a/test/test_migration_status.py b/test/test_migration_status.py index d534b42..b27ed2b 100644 --- a/test/test_migration_status.py +++ b/test/test_migration_status.py @@ -958,6 +958,62 @@ class TestTheFullScreenColoursToo(Base): self.assertIn("colour=True", source) +class TestHowLongItTook(Base): + """Du premier écrit à la dernière écriture du journal. + + La progression est réécrite après chaque geste, donc sa date de mise à + jour EST la fin — ou l'instant présent si la migration tourne encore. + """ + + def dct(self, debut, fin): + return progression(date_create=debut, date_update=fin) + + def test_it_counts_from_the_start_to_the_last_write(self): + info = status.overview( + self.dct("2026-08-19 09:06:27", "2026-08-19 10:19:18") + ) + self.assertEqual(info["elapsed"], "1 h 12 min") + + def test_a_short_run_is_given_in_minutes(self): + info = status.overview( + self.dct("2026-08-19 09:06:00", "2026-08-19 09:41:00") + ) + self.assertEqual(info["elapsed"], "35 min") + + def test_a_long_run_is_given_in_days(self): + info = status.overview( + self.dct("2026-08-17 09:00:00", "2026-08-19 11:30:00") + ) + self.assertIn("j", info["elapsed"]) + + def test_a_missing_date_never_invents_a_duration(self): + self.assertEqual(status.overview(self.dct(None, None))["elapsed"], "?") + + def test_the_report_shows_it(self): + texte = status.render_text( + self.dct("2026-08-19 09:06:27", "2026-08-19 10:19:18"), + colour=False, + ) + self.assertIn("1 h 12 min", texte) + + def test_the_full_screen_shows_it_too(self): + texte = tui.head_text( + self.dct("2026-08-19 09:06:27", "2026-08-19 10:19:18") + ) + self.assertIn("1 h 12 min", texte) + + def test_it_is_computed_in_ONE_place(self): + # L'écran de statistiques porte déjà ce calcul. Deux formules + # donneraient deux durées pour la même migration selon l'écran + # qu'on ouvre. + import inspect + + source = inspect.getsource(status.elapsed) + self.assertIn( + "from script.todo.migration_stats import fmt_delay", source + ) + + class TestSeparatingCommandsFromLogs(Base): """Les deux mélangés dans un même panneau se confondent.