okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauffeat(api)!: migrate daemon and clients to protocol 361c43ca

main
11.4 KiB304 linesraw
1"""Workspace directory and preview resources preserve real filesystem and VCS facts.
2
3What the picker shows comes from real directories, git and jj run by the
4daemon with its own environment, so the checks are end to end: a plain tree,
5a git repository (branch, changes, detached HEAD), a colocated jj repository
6(jj wins, bookmarks, and browsing never writes a jj operation), linguist
7shares, and path validation. The jj cases skip when jj is not installed.
8"""
9
10from datetime import datetime
11import json
12import os
13from pathlib import Path
14import shutil
15import subprocess
16import tempfile
17import unittest
18import urllib.error
19import urllib.parse
20
21from harness import Albedo, exclusive
22
23GIT_ENV = {
24    "GIT_AUTHOR_NAME": "fixture",
25    "GIT_AUTHOR_EMAIL": "fixture@example.com",
26    "GIT_COMMITTER_NAME": "fixture",
27    "GIT_COMMITTER_EMAIL": "fixture@example.com",
28    "JJ_USER": "fixture",
29    "JJ_EMAIL": "fixture@example.com",
30}
31
32
33def sh(cwd, *args):
34    return subprocess.run(
35        args,
36        cwd=cwd,
37        env=dict(os.environ, **GIT_ENV),
38        check=True,
39        capture_output=True,
40        text=True,
41    ).stdout
42
43
44def write(path, text):
45    path.parent.mkdir(parents=True, exist_ok=True)
46    path.write_text(text)
47
48
49class FoldersTest(unittest.TestCase):
50    def setUp(self):
51        self.app = Albedo().__enter__()
52        self.addCleanup(self.app.__exit__, None, None, None)
53        directory = (
54            Path("/var/tmp")
55            if self._testMethodName
56            == "test_plain_tree_lists_directories_and_previews_two_levels"
57            else self.app.root
58        )
59        self.root = Path(tempfile.mkdtemp(prefix="albedo-folders-", dir=directory))
60        self.addCleanup(shutil.rmtree, self.root, True)
61
62    def get(self, location, *, preview=False):
63        query = {"location": str(location)}
64        if preview:
65            query["include"] = "preview"
66        with self.app.api("/workspaces?" + urllib.parse.urlencode(query)) as response:
67            return json.load(response)
68
69    def failure(self, location, *, preview=False):
70        with self.assertRaises(urllib.error.HTTPError) as caught:
71            self.get(location, preview=preview)
72        return caught.exception.code, json.load(caught.exception)
73
74    @exclusive
75    def test_plain_tree_lists_directories_and_previews_two_levels(self):
76        for name in ("beta", "Alpha", ".hidden", "gamma/one", "gamma/two"):
77            (self.root / name).mkdir(parents=True)
78        for name in "abcdef":
79            (self.root / "gamma" / f"{name}.py").write_text("x = 1\n")
80        (self.root / "notes.txt").write_text("not a directory\n")
81        (self.root / "zeta").symlink_to(self.root / "beta")
82
83        listed = self.get(f"{self.root}/./gamma/../")
84        self.assertEqual(listed["directory"], str(self.root))
85        self.assertIsNone(listed["next"])
86        self.assertEqual(
87            [(e["name"], e["hidden"], e["vcs"]) for e in listed["items"]],
88            [
89                (".hidden", True, None),
90                ("Alpha", False, None),
91                ("beta", False, None),
92                ("gamma", False, None),
93                ("zeta", False, None),
94            ],
95        )
96        modified = datetime.fromisoformat(
97            listed["items"][1]["modified_at"].replace("Z", "+00:00")
98        )
99        self.assertAlmostEqual(
100            modified.timestamp(), (self.root / "Alpha").stat().st_mtime, delta=1
101        )
102
103        preview = self.get(self.root, preview=True)["preview"]
104        self.assertIsNone(preview["repository"])
105        self.assertEqual(preview["languages"], [])
106        self.assertEqual(preview["more"], 0)
107        self.assertEqual(
108            [(e["name"], e["kind"]) for e in preview["tree"]],
109            [
110                ("Alpha", "directory"),
111                ("beta", "directory"),
112                ("gamma", "directory"),
113                ("zeta", "symlink"),
114                ("notes.txt", "file"),
115            ],
116        )
117        gamma = preview["tree"][2]
118        self.assertEqual(
119            [c["name"] for c in gamma["children"]], ["one", "two", "a.py", "b.py"]
120        )
121        self.assertEqual(gamma["more"], 4)
122        self.assertNotIn("children", gamma["children"][0])
123        self.assertEqual(gamma["children"][2]["language"], "Python")
124        self.assertEqual(preview["tree"][4]["language"], "Text")
125
126    def test_git_repository_reports_branch_changes_and_detached_head(self):
127        repo = self.root / "project"
128        write(repo / "src" / "main.go", "package main\n" * 30)
129        write(repo / "src" / "util.go", "package main\n" * 30)
130        write(repo / "script.py", "print(1)\n" * 10)
131        write(repo / "data.json", "{}\n" * 500)
132        write(repo / ".gitignore", "build/\n")
133        sh(repo, "git", "init", "-q", "-b", "trunk")
134        sh(repo, "git", "add", ".")
135        sh(repo, "git", "commit", "-q", "-m", "one")
136        write(repo / "src" / "main.go", "package main // edited\n")
137        write(repo / "src" / "new.go", "package main\n")
138        write(repo / "build" / "out.o", "ignored\n")
139        commit = sh(repo, "git", "rev-parse", "--short", "HEAD").strip()
140
141        self.assertEqual(self.get(self.root)["items"][0]["vcs"], "git")
142        found = self.get(repo / "src", preview=True)["preview"]["repository"]
143        self.assertEqual(
144            {
145                key: found[key]
146                for key in ("kind", "root", "branch", "revision", "changed")
147            },
148            {
149                "kind": "git",
150                "root": str(repo),
151                "branch": "trunk",
152                "revision": commit,
153                "changed": 2,
154            },
155        )
156
157        preview = self.get(repo, preview=True)["preview"]
158        self.assertEqual(preview["repository"], found)
159        # JSON is data, so only Go and Python count, largest first.
160        self.assertEqual(
161            [language["name"] for language in preview["languages"]], ["Go", "Python"]
162        )
163        self.assertAlmostEqual(
164            sum(language["share"] for language in preview["languages"]), 1.0
165        )
166        tree = {e["name"]: e for e in preview["tree"]}
167        # Inside a repository only tracked or changed paths show: the
168        # ignored build/ stays out, the untracked src/new.go is there.
169        self.assertEqual(list(tree), ["src", "data.json", "script.py"])
170        self.assertEqual(tree["src"]["changed"], 2)
171        self.assertEqual(
172            {c["name"]: c["changed"] for c in tree["src"]["children"]},
173            {"main.go": 1, "new.go": 1, "util.go": 0},
174        )
175        self.assertEqual(tree["script.py"]["changed"], 0)
176
177        sh(repo, "git", "checkout", "-q", "--detach")
178        self.assertIsNone(
179            self.get(repo, preview=True)["preview"]["repository"]["branch"]
180        )
181
182    def test_unborn_git_repository_has_no_commit(self):
183        repo = self.root / "fresh"
184        repo.mkdir()
185        sh(repo, "git", "init", "-q", "-b", "main")
186        (repo / "draft.md").write_text("# draft\n")
187        found = self.get(repo, preview=True)["preview"]["repository"]
188        self.assertEqual(
189            {
190                key: found[key]
191                for key in (
192                    "kind",
193                    "root",
194                    "branch",
195                    "revision",
196                    "changed",
197                    "touched_at",
198                )
199            },
200            {
201                "kind": "git",
202                "root": str(repo),
203                "branch": "main",
204                "revision": None,
205                "changed": 1,
206                "touched_at": None,
207            },
208        )
209
210    @unittest.skipUnless(shutil.which("jj"), "jj is not installed")
211    def test_colocated_jj_repository_wins_without_writing_an_operation(self):
212        repo = self.root / "colocated"
213        repo.mkdir()
214        sh(repo, "jj", "git", "init", "--colocate")
215        write(repo / "lib" / "core.gleam", "pub fn main() { Nil }\n")
216        sh(repo, "jj", "commit", "-m", "one")
217        sh(repo, "jj", "bookmark", "create", "main", "-r", "@-")
218        write(repo / "lib" / "more.gleam", "pub fn more() { Nil }\n")
219        sh(repo, "jj", "commit", "-m", "two")
220        write(repo / "lib" / "core.gleam", "pub fn main() { 1 }\n")
221        sh(repo, "jj", "status")
222        change = sh(
223            repo, "jj", "log", "-r", "@", "--no-graph", "-T", "change_id.shortest(4)"
224        ).strip()
225        # Not yet snapshotted, so jj does not know it and the tree leaves it
226        # out; a browse that snapshotted would record an operation.
227        write(repo / "lib" / "later.gleam", "pub fn later() { Nil }\n")
228        operations = self.operations(repo)
229
230        self.assertEqual(self.get(self.root)["items"][0]["vcs"], "jj")
231        found = self.get(repo / "lib", preview=True)["preview"]["repository"]
232        self.assertEqual(found["kind"], "jj")
233        self.assertEqual(found["root"], str(repo))
234        self.assertEqual(found["change_id"], change)
235        self.assertEqual(found["bookmark"], {"name": "main", "ahead": 2})
236        self.assertEqual(found["changed"], 1)
237        self.assertIsInstance(found["touched_at"], str)
238        datetime.fromisoformat(found["touched_at"].replace("Z", "+00:00"))
239
240        preview = self.get(repo, preview=True)["preview"]
241        self.assertEqual(preview["repository"], found)
242        self.assertEqual(
243            [
244                (language["name"], language["share"])
245                for language in preview["languages"]
246            ],
247            [("Gleam", 1.0)],
248        )
249        lib = preview["tree"][0]
250        self.assertEqual((lib["name"], lib["changed"]), ("lib", 1))
251        self.assertEqual(
252            {c["name"]: c["changed"] for c in lib["children"]},
253            {"core.gleam": 1, "more.gleam": 0},
254        )
255
256        self.assertEqual(self.operations(repo), operations)
257
258        # A bookmark that moved on without @ still names @'s line of work,
259        # rather than an older backup behind @.
260        def log(rev):
261            return sh(
262                repo, "jj", "log", "-r", rev, "--no-graph", "-T", "change_id"
263            ).strip()
264
265        two, one = log("@-"), log("@--")
266        sh(repo, "jj", "bookmark", "create", "backup/old", "-r", one)
267        sh(repo, "jj", "new", two, "-m", "side")
268        sh(repo, "jj", "bookmark", "set", "main", "-r", "@")
269        sh(repo, "jj", "edit", change)
270        self.assertEqual(
271            self.get(repo, preview=True)["preview"]["repository"]["bookmark"],
272            {"name": "main", "ahead": 1},
273        )
274
275    @staticmethod
276    def operations(repo):
277        return sh(
278            repo,
279            "jj",
280            "op",
281            "log",
282            "--no-graph",
283            "--ignore-working-copy",
284            "-T",
285            'id ++ "\\n"',
286        )
287
288    def test_paths_expand_home_and_refuse_the_rest(self):
289        home = self.app.root / "user-home"
290        listed = self.get("~")
291        self.assertEqual((listed["directory"], listed["home"]), (str(home), str(home)))
292        self.assertEqual(self.get("~/")["directory"], str(home))
293
294        (self.root / "file.txt").write_text("x\n")
295        for preview in (False, True):
296            self.assertEqual(self.failure("relative/dir", preview=preview)[0], 400)
297            self.assertEqual(self.failure("~someone", preview=preview)[0], 400)
298            self.assertEqual(self.failure("", preview=preview)[0], 400)
299            code, body = self.failure(self.root / "missing", preview=preview)
300            self.assertEqual(code, 404)
301            self.assertEqual(body["status"], 404)
302            self.assertEqual(
303                self.failure(self.root / "file.txt", preview=preview)[0], 404
304            )