231 lines
11 KiB
Python
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()
|