mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 18:07:06 +01:00
phone_focus_mode is built for a rooted phone: the hosts layer is a read-only
bind mount over /system/etc/hosts, chosen so that even root-in-a-terminal
cannot overwrite it. None of that ports to a stock locked device. This splits
out the parts that are device-independent so one policy can drive both.
python_pkg/focus_policy parses config.sh rather than copying it, so the shell
config stays the source of truth and the two cannot drift. It rejects the
shipped REDACTED_* coordinate placeholder instead of defaulting to (0,0),
which would move "home" to the Atlantic and quietly disable the location gate.
blocklist.py converts the hosts file to domains for a DoT resolver or a local
VpnService. Three properties of the real 194k-line file make the obvious parser
wrong, and all three are handled: generate_hosts_file.sh *comments out*
deliberate unblocks rather than deleting them, so stripping '#' re-blocks
facebook/messenger/linkedin/4chan; the StevenBlack header carries loopback and
IPv6 housekeeping that would blackhole localhost; and ~1060 upstream annotation
tokens ('#[adware.zeno]', '"spam') render as address=/"spam/, which makes
dnsmasq refuse to start -- taking DNS down rather than blocking anything.
Verified with dnsmasq --test over all 185681 generated domains.
strip_workout_hosts.py had the same class of bug on the live rooted phone:
exact set membership meant the allowlisted googlevideo.com never matched the
dynamic r*---sn-*.googlevideo.com hosts YouTube actually streams from, so the
workout unblock was only partly working. Now suffix-matched (17 lines stripped,
was 14). The three new tests were checked against the original code in a
throwaway worktree; two of them fail there, so they pin the fix rather than
passing vacuously.
focus_owner is a device-owner app that deliberately does nothing yet. It ships
clearDeviceOwnerApp() implemented and tested first, because dpm
remove-active-admin does not work on a device owner: an app that locks the
device down before proving it can let go leaves a soft-brick recoverable only
through fastboot on a locked bootloader. Built, installed and launched on the
Pixel 6a; dumpsys afterwards confirms Device Owner Type -1 and no user
restrictions, i.e. the phone is unchanged.
Measured while scoping this, and not yet reflected in any enforcement code:
on Android 16 both pm suspend and pm disable-user are cleared by a reboot, so
shell-level app blocking is per-session only. setApplicationHidden is assumed
to persist under Device Owner but is untested.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwepyL5Lxs5QKbKkMpxxxL
140 lines
5.0 KiB
Python
140 lines
5.0 KiB
Python
"""Tests for phone_focus_mode/strip_workout_hosts.py (hosts workout variant)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import strip_workout_hosts
|
|
from strip_workout_hosts import _strip, main
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_HOSTS = """\
|
|
# header
|
|
127.0.0.1 localhost
|
|
0.0.0.0 youtube.com www.youtube.com
|
|
0.0.0.0 ads.example.com
|
|
0.0.0.0 m.youtube.com alias.youtube.com
|
|
0.0.0.0 keepme.com
|
|
"""
|
|
|
|
|
|
def _src(tmp_path: Path) -> Path:
|
|
"""Write the sample hosts file and return its path."""
|
|
path = tmp_path / "hosts"
|
|
path.write_text(_HOSTS, encoding="utf-8")
|
|
return path
|
|
|
|
|
|
class TestStrip:
|
|
"""``_strip`` removes lines mapping unblocked domains."""
|
|
|
|
def test_removes_matching_and_alias_lines(self, tmp_path: Path) -> None:
|
|
"""Entries whose name or any alias is unblocked are dropped."""
|
|
dest = tmp_path / "out"
|
|
_strip(
|
|
_src(tmp_path),
|
|
dest,
|
|
frozenset({"youtube.com", "www.youtube.com", "m.youtube.com"}),
|
|
)
|
|
result = dest.read_text(encoding="utf-8")
|
|
assert "youtube.com" not in result # both youtube entries gone
|
|
assert "ads.example.com" in result
|
|
assert "keepme.com" in result
|
|
assert "localhost" in result
|
|
assert "# header" in result
|
|
|
|
def test_empty_unblock_keeps_everything(self, tmp_path: Path) -> None:
|
|
"""An empty unblock set copies the file verbatim."""
|
|
dest = tmp_path / "out"
|
|
_strip(_src(tmp_path), dest, frozenset())
|
|
assert dest.read_text(encoding="utf-8") == _HOSTS
|
|
|
|
def test_comment_and_blank_lines_preserved(self, tmp_path: Path) -> None:
|
|
"""Comment and blank lines are never stripped."""
|
|
src = tmp_path / "hosts"
|
|
src.write_text("# c\n\n0.0.0.0 block.me\n", encoding="utf-8")
|
|
dest = tmp_path / "out"
|
|
_strip(src, dest, frozenset({"block.me"}))
|
|
assert dest.read_text(encoding="utf-8") == "# c\n\n"
|
|
|
|
|
|
class TestMain:
|
|
"""The CLI reads paths from argv and domains from the environment."""
|
|
|
|
def test_bad_arg_count_returns_2(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""A wrong number of path arguments is a usage error (rc 2)."""
|
|
monkeypatch.setattr(
|
|
strip_workout_hosts.sys, "argv", ["strip_workout_hosts", "only-one"]
|
|
)
|
|
assert main() == 2
|
|
|
|
def test_strips_via_env(
|
|
self,
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Domains from the env var drive the stripping."""
|
|
src = _src(tmp_path)
|
|
dest = tmp_path / "out"
|
|
monkeypatch.setenv("WORKOUT_UNBLOCK_DOMAINS", "youtube.com m.youtube.com")
|
|
monkeypatch.setattr(
|
|
strip_workout_hosts.sys,
|
|
"argv",
|
|
["strip_workout_hosts", str(src), str(dest)],
|
|
)
|
|
assert main() == 0
|
|
result = dest.read_text(encoding="utf-8")
|
|
assert "www.youtube.com" not in result
|
|
assert "keepme.com" in result
|
|
|
|
|
|
class TestSubdomainMatching:
|
|
"""Regression tests for the exact-match bug fixed on 2026-08-05.
|
|
|
|
The original implementation tested ``name.lower() in unblock``, i.e. exact
|
|
set membership. YouTube streams video from dynamically named CDN hosts, so
|
|
an allowlist entry of ``googlevideo.com`` released nothing that actually
|
|
carried video and playback stayed blocked during workouts.
|
|
"""
|
|
|
|
def test_releases_dynamic_cdn_subdomains(self, tmp_path: Path) -> None:
|
|
"""A parent domain releases the CDN hosts beneath it."""
|
|
src = tmp_path / "hosts"
|
|
src.write_text(
|
|
"0.0.0.0 googlevideo.com\n"
|
|
"0.0.0.0 r1---sn-4g5e6nls.googlevideo.com\n"
|
|
"0.0.0.0 r1---sn-4g5lne7s.googlevideo.com\n"
|
|
"0.0.0.0 keepme.com\n",
|
|
encoding="utf-8",
|
|
)
|
|
dest = tmp_path / "out"
|
|
_strip(src, dest, frozenset({"googlevideo.com"}))
|
|
result = dest.read_text(encoding="utf-8")
|
|
assert "googlevideo.com" not in result
|
|
assert "keepme.com" in result
|
|
|
|
def test_does_not_release_lookalike_domains(self, tmp_path: Path) -> None:
|
|
"""Suffix matching must not be fooled by substring similarity."""
|
|
src = tmp_path / "hosts"
|
|
src.write_text(
|
|
"0.0.0.0 notgooglevideo.com\n0.0.0.0 googlevideo.com.evil.net\n",
|
|
encoding="utf-8",
|
|
)
|
|
dest = tmp_path / "out"
|
|
_strip(src, dest, frozenset({"googlevideo.com"}))
|
|
result = dest.read_text(encoding="utf-8")
|
|
assert "notgooglevideo.com" in result
|
|
assert "googlevideo.com.evil.net" in result
|
|
|
|
def test_case_and_trailing_dot_are_normalised(self, tmp_path: Path) -> None:
|
|
"""Host names are compared case-insensitively and without the root dot."""
|
|
src = tmp_path / "hosts"
|
|
src.write_text("0.0.0.0 WWW.YouTube.COM.\n", encoding="utf-8")
|
|
dest = tmp_path / "out"
|
|
_strip(src, dest, frozenset({"youtube.com"}))
|
|
assert dest.read_text(encoding="utf-8") == ""
|