erplibre/test/test_reset_stale_cow_tui.py
Mathieu Benoit 95ffbe6f13 [ADD] migration: browse the drifted COW copies full screen
The text report puts a thousand lines between the two things that decide:
what the copy holds that the module view does not — all a reset gives up
— and which child no longer finds its anchor, which is why anything
breaks. Space switches between them, « c » copies the reset command.

Offered as [4] on the error prompt, next to check and reset, and run on
the real terminal: a full screen started through the capturing executor
falls back to the text report with nothing to tell the two apart.

The diff is rendered in one place, shared with the command line. Two
renderings would drift without anything saying so.

--- FR ---

[ADD] migration : parcourir les copies COW dérivées en plein écran

Le rapport texte met mille lignes entre les deux choses qui décident :
ce que la copie porte et que la vue module n'a pas — tout ce qu'une
réinitialisation abandonne — et quel enfant ne trouve plus son ancrage,
la raison pour laquelle quoi que ce soit casse. L'espace bascule, « c »
copie la commande de réparation.

Proposé en [4] à l'invite d'erreur, à côté de vérifier et réinitialiser,
et lancé sur le vrai terminal : un plein écran passé par l'exécuteur qui
capture retombe sur le rapport texte, sans rien qui les distingue.

Le diff est rendu à un seul endroit, partagé avec la ligne de commande.
Deux rendus dériveraient sans que rien ne le signale.

Assisted-by: Claude Opus 5
2026-08-22 07:23:59 -04:00

163 lines
5.5 KiB
Python
Executable file

#!/usr/bin/env python3
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""Lire une copie COW avant d'accepter de la perdre, en plein écran.
Le rapport texte met à mille lignes d'écart les deux choses qui décident :
ce que la copie porte en propre — la seule chose qu'une réinitialisation
abandonne — et quel enfant ne trouve plus son ancrage, qui est la raison
pour laquelle quoi que ce soit casse. L'espace bascule entre les deux.
La commande de réparation prend une CLÉ. La recopier à la main depuis un
diff défilé est l'endroit où un caractère se perd, et une clé sans
correspondance n'est pas une erreur pour l'outil : il tourne et ne fait
rien. D'où « c ».
"""
import os
import sys
import unittest
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(REPO, "script", "odoo", "migration"))
import reset_stale_cow_tui as tui # noqa: E402
import reset_stale_cow_views as reset # noqa: E402
def finding(**override):
cow = {
"id": 2841,
"key": "website_blog.blog_post_complete",
"website_id": 1,
"arch": "<t>\n <div/>\n <span>vieux</span>\n</t>",
}
module = {
"id": 1826,
"key": "website_blog.blog_post_complete",
"website_id": None,
"arch": "<t>\n <div/>\n <section id='o_wblog_post_footer'/>\n</t>",
}
broken = [(3288, "//section[@id='o_wblog_post_footer']")]
if "cow" in override:
cow.update(override["cow"])
return cow, override.get("module", module), override.get("broken", broken)
class TestBothScreens(unittest.TestCase):
def setUp(self):
from script.todo import todo_i18n
self.addCleanup(
setattr, todo_i18n, "_current_lang", todo_i18n._current_lang
)
todo_i18n._current_lang = "en"
def test_the_diff_shows_what_the_copy_holds(self):
cow, module, _broken = finding()
text = reset.render_diff(module, cow, indent=" ")
self.assertIn("vieux", text)
self.assertIn("o_wblog_post_footer", text)
def test_the_why_screen_names_the_failing_child(self):
# Sans lui, on lit un diff sans savoir pourquoi il compte.
text = tui.render_broken(*finding())
self.assertIn("3288", text)
self.assertIn("//section[@id='o_wblog_post_footer']", text)
def test_the_why_screen_says_what_a_reset_costs(self):
text = tui.render_broken(*finding())
self.assertIn("INHERITING", text)
def test_a_copy_without_broken_child_is_said_so(self):
# Elle a dérivé sans rien casser ENCORE : le taire ferait croire à
# une erreur de détection.
cow, module, _ = finding()
text = tui.render_broken(cow, module, [])
self.assertIn("without", text)
def test_the_two_screens_use_the_same_diff_as_the_cli(self):
# Deux rendus séparés dériveraient sans que rien ne le signale.
import inspect
source = inspect.getsource(tui)
self.assertIn("from reset_stale_cow_views import", source)
self.assertIn("render_diff", source)
class TestTheTriageColumn(unittest.TestCase):
def test_the_weight_counts_both_directions(self):
cow, module, _ = finding()
self.assertEqual(tui.weight(cow, module), "+1/-1")
def test_no_module_twin_has_no_weight(self):
cow, _module, _ = finding()
self.assertEqual(tui.weight(cow, None), "—")
class TestTheRefusalIsNeverSilent(unittest.TestCase):
"""Un refus muet ferait réafficher le rapport texte sans le dire."""
def test_a_pipe_is_explained(self):
import io
from contextlib import redirect_stdout
out = io.StringIO()
with redirect_stdout(out):
result = tui.run_tui([finding()], "db")
self.assertFalse(result)
self.assertTrue(out.getvalue().strip(), "refus muet")
def test_nothing_to_show_stays_silent(self):
import io
from contextlib import redirect_stdout
out = io.StringIO()
with redirect_stdout(out):
self.assertFalse(tui.run_tui([], "db"))
self.assertEqual(out.getvalue(), "")
class TestTheToolAndTheMigrationOfferIt(unittest.TestCase):
def test_the_tool_has_a_tui_flag(self):
import subprocess
done = subprocess.run(
[
sys.executable,
"./script/odoo/migration/reset_stale_cow_views.py",
"--help",
],
capture_output=True,
text=True,
cwd=REPO,
)
self.assertEqual(done.returncode, 0)
self.assertIn("--tui", done.stdout)
def test_the_error_prompt_offers_it_as_option_four(self):
import inspect
from script.todo.todo_upgrade import TodoUpgrade
source = inspect.getsource(TodoUpgrade.todo_upgrade_execute)
self.assertIn("Browse the differences full screen", source)
self.assertIn('wait_status == "4"', source)
def test_it_gets_a_real_terminal(self):
# Un plein écran lancé par l'exécuteur qui capture retomberait sur
# le rapport texte, sans que rien ne distingue les deux.
import inspect
from script.todo.todo_upgrade import TodoUpgrade
source = inspect.getsource(TodoUpgrade.todo_upgrade_execute)
start = source.index('wait_status == "4"')
window = source[start : start + 620]
self.assertIn("run_on_terminal", window)
self.assertIn("--tui", window)
if __name__ == "__main__":
unittest.main()