Dotfiles_ArchAP/tests/test_screenshot_annotation.py

231 lines
11 KiB
Python

"""Parcours annotation isolé : aucun vrai écran ni presse-papiers n'est lu."""
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
from PIL import Image
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "sway/.config/sway/scripts/screenshot-region"
class ScreenshotAnnotationTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix="annotation test ")
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
self.home = self.root / "home with spaces"
self.bin = self.root / "fake tools"
self.runtime = self.root / "runtime with spaces"
self.bin.mkdir()
self.runtime.mkdir()
(self.home / ".local/libexec").mkdir(parents=True)
(self.home / ".config").mkdir()
self.pictures = self.root / "Images de test"
(self.home / ".config/user-dirs.dirs").write_text(
f'XDG_PICTURES_DIR="{self.pictures}"\n')
self.png = self.root / "synthetic.png"
Image.new("RGB", (4, 3), (7, 13, 29)).save(self.png)
self.log = self.root / "calls.log"
self.make_tool("swaymsg", "#!/bin/sh\necho '[{\"focused\":true,\"name\":\"eDP-1\"}]'\n")
self.make_tool("quickshell", """#!/bin/sh
if [ "${TEST_PANELS:-}" = yes ]; then
marker="$TEST_PANEL_DIR/$5"
if [ "$6" = toggle ]; then
touch "$marker"
echo true
elif [ -e "$marker" ]; then
echo '{"loaded":false,"requested":false}'
else
echo '{"loaded":true,"requested":true}'
fi
else
echo '{"loaded":false}'
fi
""")
self.make_tool("grim", "#!/bin/sh\necho \"$0\" >> \"$TEST_LOG\"\n[ \"$TEST_CASE\" = grim_fail ] && exit 1\n[ \"$TEST_CASE\" = bad_png ] && { printf 'not a png' > \"$3\"; exit 0; }\ncp \"$TEST_PNG\" \"$3\"\n")
self.make_tool("wl-copy", "#!/bin/sh\necho \"$0\" >> \"$TEST_LOG\"\n")
self.make_tool("swappy", """#!/usr/bin/python3
import os, pathlib, sys
p = pathlib.Path(os.environ['TEST_LOG'])
with p.open('a') as out: out.write(sys.argv[0] + '\\n')
assert sys.argv[1] == '--file'
assert pathlib.Path(sys.argv[2]).read_bytes() == pathlib.Path(os.environ['TEST_PNG']).read_bytes()
config_root = pathlib.Path(os.environ['XDG_CONFIG_HOME'])
source_root = pathlib.Path(os.environ['TEST_SOURCE_CONFIG'])
assert config_root != source_root
for shared in ['dconf', 'gtk-3.0']:
link = config_root / shared
assert link.is_symlink()
assert os.readlink(link) == str(source_root / shared)
for source in (source_root / shared).rglob('*'):
if source.is_file():
assert (link / source.relative_to(source_root / shared)).read_bytes() == source.read_bytes()
assert os.environ.get('GTK_THEME') == os.environ.get('TEST_EXPECT_GTK_THEME')
config = config_root / 'swappy/config'
content = config.read_text()
assert 'show_panel=true' in content and 'early_exit=true' in content
assert 'auto_save=false' in content and 'save_dir="$LABFY_SWAPPY_SAVE_DIR"' in content
assert len(sys.argv) == 3
if os.environ['TEST_CASE'] == 'save':
dest = pathlib.Path(os.environ['LABFY_SWAPPY_SAVE_DIR']) / 'swappy-test.png'
dest.write_bytes(pathlib.Path(os.environ['TEST_PNG']).read_bytes())
if os.environ['TEST_CASE'] == 'save_slow':
import time
time.sleep(0.25)
dest = pathlib.Path(os.environ['LABFY_SWAPPY_SAVE_DIR']) / 'swappy-test.png'
dest.write_bytes(pathlib.Path(os.environ['TEST_PNG']).read_bytes())
""")
selector = self.home / ".local/libexec/labfy-slurp"
selector.write_text("#!/bin/sh\necho \"$0\" >> \"$TEST_LOG\"\nif [ \"${TEST_PANELS:-}\" = yes ]; then\n [ -e \"$TEST_PANEL_DIR/clipboardUi-eDP-1\" ] && [ -e \"$TEST_PANEL_DIR/applicationsUi-eDP-1\" ] && [ -e \"$TEST_PANEL_DIR/overviewUi-eDP-1\" ] || exit 17\nfi\ncase \"$TEST_CASE\" in cancel) exit 1;; empty) exit 0;; invalid) echo bogus;; *) echo '10,20 4x3';; esac\n")
selector.chmod(0o755)
def make_tool(self, name, body):
path = self.bin / name
path.write_text(body)
path.chmod(0o755)
def case_env(self, name):
env = dict(os.environ, HOME=str(self.home), XDG_CONFIG_HOME=str(self.home / ".config"),
XDG_RUNTIME_DIR=str(self.runtime),
PATH=f"{self.bin}:{os.environ['PATH']}", TEST_CASE=name,
TEST_LOG=str(self.log), TEST_PNG=str(self.png),
TEST_SOURCE_CONFIG=str(self.home / ".config"))
env.pop("GTK_THEME", None)
env.pop("TEST_EXPECT_GTK_THEME", None)
env.pop("XDG_PICTURES_DIR", None)
return env
def run_case(self, name, annotate=True):
env = self.case_env(name)
return subprocess.run([str(SCRIPT)] + (["--annotate"] if annotate else []), env=env,
capture_output=True, text=True, timeout=10)
def calls(self):
return self.log.read_text().splitlines() if self.log.exists() else []
def expected(self, *names):
return [str(self.home / ".local/libexec/labfy-slurp") if name == "slurp"
else str(self.bin / name) for name in names]
def assert_clean(self):
self.assertEqual(list(self.runtime.iterdir()), [])
def test_cancel_and_invalid_geometry(self):
for case, expected in (("cancel", 0), ("empty", 0), ("invalid", 1)):
with self.subTest(case=case):
self.log.unlink(missing_ok=True)
self.assertEqual(self.run_case(case).returncode, expected)
self.assertEqual(self.calls(), self.expected("slurp"))
self.assert_clean()
def test_grim_failure(self):
self.assertNotEqual(self.run_case("grim_fail").returncode, 0)
self.assertEqual(self.calls(), self.expected("slurp", "grim"))
self.assert_clean()
def test_invalid_png_never_opens_editor(self):
self.assertNotEqual(self.run_case("bad_png").returncode, 0)
self.assertEqual(self.calls(), self.expected("slurp", "grim"))
self.assert_clean()
def test_exclusive_panels_close_before_selector(self):
panel_dir = self.root / "panels"
panel_dir.mkdir()
env = self.case_env("close")
env.update(TEST_PANELS="yes", TEST_PANEL_DIR=str(panel_dir))
result = subprocess.run([str(SCRIPT), "--annotate"], env=env,
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(len(list(panel_dir.iterdir())), 3)
self.assertEqual(self.calls(), self.expected("slurp", "grim", "swappy"))
self.assert_clean()
def test_close_does_not_publish_or_copy(self):
self.assertEqual(self.run_case("close").returncode, 0)
self.assertEqual(self.calls(), self.expected("slurp", "grim", "swappy"))
self.assertFalse(self.pictures.exists())
self.assert_clean()
def test_explicit_save_and_two_invocations(self):
for _ in range(2):
self.assertEqual(self.run_case("save").returncode, 0)
self.assert_clean()
images = list((self.pictures / "Screenshots").glob("Screenshot-*.png"))
self.assertEqual(len(images), 2)
self.assertEqual(len({p.name for p in images}), 2)
self.assertTrue(all(p.read_bytes() == self.png.read_bytes() for p in images))
self.assertNotIn(str(self.bin / "wl-copy"), self.calls())
def test_overlapping_invocations_have_distinct_outputs(self):
env = self.case_env("save_slow")
processes = [subprocess.Popen([str(SCRIPT), "--annotate"], env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for _ in range(2)]
for process in processes:
_, stderr = process.communicate(timeout=10)
self.assertEqual(process.returncode, 0, stderr.decode())
images = list((self.pictures / "Screenshots").glob("Screenshot-*.png"))
self.assertEqual(len(images), 2)
self.assertEqual(len({p.name for p in images}), 2)
self.assert_clean()
def test_system_theme_changes_are_shared_without_copy_or_override(self):
config = self.home / ".config"
for shared in ("dconf", "gtk-3.0"):
(config / shared).mkdir()
for theme in ("Fixture-Dark", "Fixture-Light"):
with self.subTest(theme=theme):
# Données factices : aucune base dconf personnelle n'est lue.
database = config / "dconf/user"
settings = config / "gtk-3.0/settings.ini"
css = config / "gtk-3.0/gtk.css"
database.write_bytes(("fixture:" + theme).encode())
settings.write_text("[Settings]\ngtk-theme-name=" + theme + "\n")
css.write_text("/* CSS système factice " + theme + " */\n")
before = {p: p.read_bytes() for p in (database, settings, css)}
result = self.run_case("close")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual({p: p.read_bytes() for p in before}, before)
self.assert_clean()
def test_custom_xdg_config_and_inherited_theme_are_preserved(self):
config = self.root / "custom config with spaces"
(config / "dconf").mkdir(parents=True)
(config / "gtk-3.0").mkdir()
settings = config / "gtk-3.0/settings.ini"
settings.write_text("[Settings]\ngtk-theme-name=Fixture-User\n")
env = self.case_env("close")
env.update(XDG_CONFIG_HOME=str(config), TEST_SOURCE_CONFIG=str(config),
GTK_THEME="Fixture-Session", TEST_EXPECT_GTK_THEME="Fixture-Session")
result = subprocess.run([str(SCRIPT), "--annotate"], env=env,
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(settings.read_text(), "[Settings]\ngtk-theme-name=Fixture-User\n")
self.assert_clean()
def test_unset_xdg_config_and_missing_gtk_settings_are_supported(self):
env = self.case_env("close")
env.pop("XDG_CONFIG_HOME")
result = subprocess.run([str(SCRIPT), "--annotate"], env=env,
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertFalse((self.home / ".config/gtk-3.0").exists())
self.assertFalse((self.home / ".config/dconf").exists())
self.assert_clean()
def test_print_default_and_shift_binding_remain(self):
self.assertEqual(self.run_case("close", annotate=False).returncode, 0)
self.assertEqual(self.calls(), self.expected("slurp", "grim", "wl-copy"))
self.assert_clean()
binds = (ROOT / "sway/.config/sway/bind").read_text()
self.assertIn("bindsym Shift+Print exec grim - | wl-copy -t image/png", binds)
self.assertIn("bindsym Print exec ~/.config/sway/scripts/screenshot-region\n", binds)
if __name__ == "__main__":
unittest.main()