mirror of
https://github.com/Gathin23/cleanyourtv.git
synced 2026-10-27 19:54:45 +00:00
docs: the reboot that comes back, in the four chains that reboot (#995)
`tools/tv.py reboot` is a shell command, and adbd stops before it sends the shell's last CLSE, so the client waits out its timeout (#995, TCL 65C935, G05_4K_GB, Google TV 11, AR2101). #1005 added `--reboot`, which uses ADB's own service and returns once the request is accepted, and documented it in the docstring only: four pages still handed a reader the shell form, and the README Tools row did not name the mode. Each chain now uses it and says the TV is still restarting; TROUBLESHOOTING gains the symptom bullet with the cause and the device. tests/test_reboot_docs.py reads the promise out of tools/tv.py, so the pages fail rather than go stale if the mode changes.
This commit is contained in:
1 parent
c5d75e8e80
commit
30039b0a2a
9 files changed
+154
-9
No files matched your search
@@ -389,7 +389,7 @@ tools/theme.sh themes/aurora # push a theme; --r
|
||||
|
||||
| Tool | What it does |
|
||||
|---|---|
|
||||
| [`tools/tv.py`](tools/tv.py) | Pure-Python ADB client: shell, batch, push, pull, and screenshots, optionally pressing remote keys first. Works where the stock `adb` binary is blocked. |
|
||||
| [`tools/tv.py`](tools/tv.py) | Pure-Python ADB client: shell, batch, push, pull, reboot, and screenshots, optionally pressing remote keys first. Works where the stock `adb` binary is blocked. |
|
||||
| [`tools/b64push.py`](tools/b64push.py) | Uploads large files such as APKs in base64 chunks and verifies the checksum. |
|
||||
| [`tools/relay.py`](tools/relay.py) | Forwards a local port to the TV's ADB port, for assistants whose sandbox can't reach your network. Listens on this computer only. [Guide](tools/relay.md). |
|
||||
| [`tools/disable.sh`](tools/disable.sh), [`restore.sh`](tools/restore.sh) | Apply or undo a debloat list, the undo from the state recorded before the change. |
|
||||
|
||||
@@ -67,6 +67,13 @@ Things that bit us or contributors, so they don't bite you. Commands use `tools/
|
||||
- **Chained `pm` commands** (`pm disable a; pm disable b`) may silently stop after the first. Run
|
||||
one per shell call; `--batch` does exactly that.
|
||||
- **Large pushes hang** through the Python client. Use `tools/b64push.py` for APKs.
|
||||
- **`tools/tv.py reboot` never comes back.** `reboot` is a shell command, and adbd stops before it
|
||||
sends the shell's last `CLSE`, so the client waits for a reply that cannot arrive: two minutes,
|
||||
then an error, on a TCL 65C935 (`G05_4K_GB`, Google TV 11, AR2101,
|
||||
[#995](https://github.com/Gathin23/cleanyourtv/issues/995)). A chain that reboots and then checks
|
||||
something stalls there. Use `tools/tv.py --reboot`: it sends the reboot through ADB's own service
|
||||
and returns once the request is accepted. The TV is still restarting at that point, so let it
|
||||
answer again (`tools/tv.py --probe`) before the next command.
|
||||
- **A `logcat` without the line you expected is not proof that nothing happened.** On a TCL 55C728
|
||||
(`G05_4K_GB`, Google TV 11) the main buffer is 1 MiB (`persist.logd.size.main`), and the TV's own
|
||||
HDMI, ARC and media logging fills it in well under a minute; around power-on it held about 30
|
||||
@@ -101,7 +108,7 @@ Things that bit us or contributors, so they don't bite you. Commands use `tools/
|
||||
```sh
|
||||
tools/tv.py 'am start -n com.cvte.tv.hkcfac/.MainActivity'
|
||||
tools/tv.py 'input keyevent 218; input keyevent 218'
|
||||
tools/tv.py reboot
|
||||
tools/tv.py --reboot # returns once ADB accepts it; wait for the TV to come back
|
||||
tools/tv.py 'settings put global hdmi_control_enabled 1'
|
||||
```
|
||||
Press the real Input button afterwards and check the packages are back:
|
||||
|
||||
@@ -71,7 +71,7 @@ package name.
|
||||
tools/tv.py 'pm enable com.haiersoft.launcherproject2995hk'
|
||||
# ...once per package that was disabled, or once per package in the backup
|
||||
tools/tv.py 'pm list packages -d' # must print nothing
|
||||
tools/tv.py reboot
|
||||
tools/tv.py --reboot # returns once ADB accepts it; wait for the TV to come back
|
||||
```
|
||||
|
||||
This set showed `INICIALIZANDO` once more on the reboot and then booted normally. Boots take 33–73 s.
|
||||
|
||||
@@ -88,7 +88,7 @@ is in front**, reboot, then turn CEC back on:
|
||||
```sh
|
||||
tools/tv.py 'am start -n com.cvte.tv.hkcfac/.MainActivity'
|
||||
tools/tv.py 'input keyevent 218; input keyevent 218'
|
||||
tools/tv.py reboot
|
||||
tools/tv.py --reboot # returns once ADB accepts it; wait for the TV to come back
|
||||
tools/tv.py 'settings put global hdmi_control_enabled 1'
|
||||
```
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Both commands, in this order, then a real reboot:
|
||||
```sh
|
||||
tools/tv.py 'wm density 320'
|
||||
tools/tv.py 'settings --user 0 put secure display_density_forced 320'
|
||||
tools/tv.py reboot
|
||||
tools/tv.py --reboot # returns once ADB accepts it; wait for the TV to come back
|
||||
tools/tv.py 'wm size; wm density; settings --user 0 get secure display_density_forced'
|
||||
```
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ class CoopNoteTest(unittest.TestCase):
|
||||
self.assertIn("INICIALIZANDO", body)
|
||||
self.assertIn("pm list packages -d", self.text)
|
||||
self.assertIn("pm enable", self.text)
|
||||
self.assertIn("tools/tv.py reboot", self.text)
|
||||
self.assertIn("tools/tv.py --reboot", self.text)
|
||||
self.assertIn("33–73 s", self.text)
|
||||
|
||||
def test_every_disabled_package_is_recorded(self):
|
||||
|
||||
@@ -96,7 +96,7 @@ class DurabrandNoteTest(unittest.TestCase):
|
||||
text = NOTE.read_text(encoding="utf-8")
|
||||
block = text.split("```sh", 1)[1].split("```", 1)[0]
|
||||
steps = ["am start -n %s/.MainActivity" % FACTORY, "input keyevent 218",
|
||||
"tools/tv.py reboot", "hdmi_control_enabled 1"]
|
||||
"tools/tv.py --reboot", "hdmi_control_enabled 1"]
|
||||
positions = [block.find(step) for step in steps]
|
||||
self.assertNotIn(-1, positions, f"missing recovery step in {steps}")
|
||||
self.assertEqual(positions, sorted(positions), "recovery steps out of order")
|
||||
@@ -124,7 +124,7 @@ class TroubleshootingTrapTest(unittest.TestCase):
|
||||
|
||||
def test_trap_has_the_way_out(self):
|
||||
for command in (f"com.cvte.tv.hkcfac/.MainActivity", "input keyevent 218",
|
||||
"tools/tv.py reboot", "hdmi_control_enabled 1"):
|
||||
"tools/tv.py --reboot", "hdmi_control_enabled 1"):
|
||||
with self.subTest(command=command):
|
||||
self.assertIn(command, self.trap)
|
||||
self.assertLess(self.trap.find("am start"), self.trap.find("keyevent 218"))
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
"""Every reboot the docs run is the one that comes back: `tools/tv.py --reboot`.
|
||||
|
||||
`tools/tv.py reboot` is a shell command, and adbd stops before it sends the shell's last `CLSE`, so
|
||||
the client waits for a reply that cannot arrive: the call ends in a timeout two minutes later. That
|
||||
is #995 (a TCL 65C935, `G05_4K_GB`, Google TV 11, AR2101) where a chain that rebooted and then read
|
||||
back the disabled list stalled there. #1005 added `tools/tv.py --reboot`, which goes through ADB's
|
||||
own service and returns once the request is accepted - and four pages still handed a reader the
|
||||
shell form. These tests keep every page on the mode that returns, and check the promise the pages
|
||||
make ("returns once ADB accepts it") against tools/tv.py instead of trusting the prose.
|
||||
|
||||
What counts as a command: a line with no backticks on it. A page that explains the symptom has to
|
||||
name the broken form, and it writes it in backticks (`tools/tv.py reboot`); a line a reader is meant
|
||||
to paste does not.
|
||||
|
||||
Run from the repository root: python3 -m unittest discover -s tests -v
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from test_markdown_links import markdown_files # noqa: E402
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
TV = ROOT / "tools" / "tv.py"
|
||||
TROUBLE = ROOT / "docs" / "TROUBLESHOOTING.md"
|
||||
README = ROOT / "README.md"
|
||||
|
||||
# `tv.py reboot`, not `tv.py --reboot`: the dashes sit between the path and the word.
|
||||
SHELL_REBOOT = re.compile(r"tv\.py\s+reboot\b")
|
||||
|
||||
# Pages that reboot and then keep using the TV, so the next command needs it back.
|
||||
CHAINS = ("docs/TROUBLESHOOTING.md", "docs/devices/durabrand-durat32isdb.md",
|
||||
"docs/devices/coop-cotv-0001.md", "docs/devices/sony-k85xr70-ae2.md")
|
||||
|
||||
|
||||
def shell_reboots(text):
|
||||
"""(line number, line) for every command in one page that reboots through the shell form."""
|
||||
return [(number, line.strip()) for number, line in enumerate(text.split("\n"), 1)
|
||||
if "`" not in line and SHELL_REBOOT.search(line)]
|
||||
|
||||
|
||||
def mode_lines(text):
|
||||
"""(line number, line) for every `tv.py --reboot` command in one page, prose left out."""
|
||||
return [(number, line.strip()) for number, line in enumerate(text.split("\n"), 1)
|
||||
if "`" not in line and "tv.py --reboot" in line]
|
||||
|
||||
|
||||
def page(path):
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
class EveryPageUsesTheModeTest(unittest.TestCase):
|
||||
def test_no_page_runs_the_shell_form(self):
|
||||
found = []
|
||||
for path in markdown_files(ROOT):
|
||||
for number, line in shell_reboots(page(path)):
|
||||
found.append(f"{path.relative_to(ROOT).as_posix()}:{number}: {line}")
|
||||
self.assertEqual([], found, "use `tools/tv.py --reboot`; the shell form hangs (#995):\n"
|
||||
+ "\n".join(found))
|
||||
|
||||
def test_the_reboot_chains_use_the_mode_and_say_what_it_returns(self):
|
||||
for name in CHAINS:
|
||||
text = page(ROOT / name)
|
||||
lines = mode_lines(text)
|
||||
with self.subTest(page=name):
|
||||
self.assertEqual(1, len(lines), f"{name} should reboot once through --reboot: {lines}")
|
||||
line = lines[0][1]
|
||||
self.assertRegex(line, r"#.*\bwait\b",
|
||||
f"{name}: the line after --reboot runs against a TV that is still "
|
||||
f"restarting, so the line has to say so")
|
||||
|
||||
def test_the_symptom_bullet_names_the_cause_the_device_and_the_way_out(self):
|
||||
text = page(TROUBLE)
|
||||
marker = "- **`tools/tv.py reboot` never comes back.**"
|
||||
self.assertTrue(marker in text, "TROUBLESHOOTING has to name the hang a reader will hit")
|
||||
bullet = text.split(marker, 1)[1].split("\n- **", 1)[0]
|
||||
for token in ("adbd", "CLSE", "issues/995", "TCL 65C935", "G05_4K_GB",
|
||||
"Google TV 11", "AR2101", "tools/tv.py --reboot", "tools/tv.py --probe"):
|
||||
with self.subTest(token=token):
|
||||
self.assertIn(token, bullet)
|
||||
|
||||
def test_the_readme_tool_row_names_reboot(self):
|
||||
rows = [line for line in page(README).split("\n") if line.startswith("| [`tools/tv.py`]")]
|
||||
self.assertEqual(1, len(rows), "the README Tools table needs one tools/tv.py row")
|
||||
self.assertIn("reboot", rows[0], "the row is where a reader learns the mode exists")
|
||||
|
||||
|
||||
class TheModeIsWhatThePagesPromiseTest(unittest.TestCase):
|
||||
"""The prose says 'returns once ADB accepts it'; this reads that out of the tool."""
|
||||
|
||||
def setUp(self):
|
||||
self.text = page(TV)
|
||||
|
||||
def test_the_flag_is_a_known_argument(self):
|
||||
self.assertRegex(self.text, r'"--reboot":\s*1', "tv.py's ARITY table has to accept --reboot alone")
|
||||
|
||||
def test_it_reboots_through_adb_and_closes(self):
|
||||
branch = self.text.split('if args == ["--reboot"]:', 1)
|
||||
self.assertEqual(2, len(branch), "tv.py needs the --reboot branch")
|
||||
body = branch[1].split("\n status = 0", 1)[0]
|
||||
self.assertIn("dev.reboot()", body, "the mode sends ADB's own reboot service, not a shell command")
|
||||
self.assertIn("dev.close()", body)
|
||||
self.assertIn("wait for the TV to restart", body, "it returns before the TV is back")
|
||||
|
||||
def test_the_docstring_lists_the_mode(self):
|
||||
docstring = self.text.split('"""', 2)[1]
|
||||
self.assertIn("tv.py --reboot", docstring)
|
||||
self.assertIn("return once the reboot request is accepted", docstring)
|
||||
|
||||
|
||||
class TheCheckerItselfTest(unittest.TestCase):
|
||||
def test_a_bare_command_is_reported_with_its_line(self):
|
||||
text = "intro\n\ntools/tv.py reboot\nmore\n"
|
||||
self.assertEqual([(3, "tools/tv.py reboot")], shell_reboots(text))
|
||||
|
||||
def test_the_mode_is_not_reported(self):
|
||||
self.assertEqual([], shell_reboots("tools/tv.py --reboot\n"))
|
||||
self.assertEqual([], shell_reboots("tools/venv/bin/python -I tools/tv.py --reboot\n"))
|
||||
|
||||
def test_a_path_written_in_prose_is_not_a_command(self):
|
||||
self.assertEqual([], shell_reboots("`tools/tv.py reboot` never comes back.\n"))
|
||||
self.assertEqual(1, len(shell_reboots("python -I tools/tv.py reboot\n")))
|
||||
|
||||
def test_a_walk_finds_the_pages_it_is_given(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
(root / "docs").mkdir()
|
||||
(root / "docs" / "note.md").write_text("tools/tv.py reboot\n", encoding="utf-8")
|
||||
(root / "README.txt").write_text("tools/tv.py reboot\n", encoding="utf-8")
|
||||
found = [path.name for path in markdown_files(root) if shell_reboots(page(path))]
|
||||
self.assertEqual(["note.md"], found)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -95,7 +95,7 @@ class SonyDensityNoteTest(unittest.TestCase):
|
||||
block = self.text.split("```sh", 1)[1].split("```", 1)[0]
|
||||
steps = ["wm density 320",
|
||||
"settings --user 0 put secure display_density_forced 320",
|
||||
"tv.py reboot"]
|
||||
"tv.py --reboot"]
|
||||
positions = [block.find(step) for step in steps]
|
||||
self.assertNotIn(-1, positions, f"missing step in {steps}")
|
||||
self.assertEqual(positions, sorted(positions), "the saved density must come after wm density")
|
||||
|
||||
Reference in new issue
Block a user