import os import sys import tempfile import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from wflib import config as C from test_claims import FOUR, git from test_cli import TOML, Cli SETUP = TOML + 'worktree_setup = ["mkdir -p out && ln -sfn \\"$WF_MAIN/out/data\\" out/data", "echo ran >> log.txt"]\n' class SetupConfigTest(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.root = Path(self.tmp.name).resolve() def tearDown(self): self.tmp.cleanup() def load(self, extra): (self.root / "workflow.toml").write_text('format = 1\ntasks = "T.md"\narchive = "a.md"\n' + extra) return C.load(self.root) def test_default_empty(self): self.assertEqual(self.load("").worktree_setup, []) def test_list_of_commands(self): self.assertEqual(self.load('worktree_setup = ["a", "b c"]\n').worktree_setup, ["a", "b c"]) def test_wrong_type(self): with self.assertRaisesRegex(C.ConfigError, "'worktree_setup' must be a list of strings"): self.load('worktree_setup = "a"\n') def test_unknown_key_still_errors(self): with self.assertRaisesRegex(C.ConfigError, "unknown key 'worktree_setp'"): self.load('worktree_setp = []\n') class GitCli(Cli): tasks_text = FOUR toml = SETUP def setUp(self): super().setUp() git(self.root, "init", "-q", "-b", "master") (self.root / ".gitignore").write_text(".worktrees/\n.wf/\nout/\n") git(self.root, "add", "-A") git(self.root, "commit", "-qm", "init") class WorktreeCli(GitCli): def setUp(self): super().setUp() self.wt = self.root / ".worktrees" / "opus" git(self.root, "worktree", "add", "-q", str(self.wt), "-b", "opus/t-three") (self.root / "out" / "data").mkdir(parents=True) (self.root / "out" / "data" / "x.bin").write_text("main data\n") def setup_(self, *args, cwd=None): return self.wf("setup", *args, project=False, cwd=cwd or self.wt) class SetupCliTest(WorktreeCli): def test_runs_commands_in_worktree_with_wf_main(self): code, out, err = self.setup_() self.assertEqual((code, err), (0, ""), out) self.assertEqual((self.wt / "out" / "data" / "x.bin").read_text(), "main data\n") self.assertEqual((self.wt / "log.txt").read_text(), "ran\n") self.assertFalse((self.root / "log.txt").exists()) self.assertIn("$ echo ran >> log.txt\n", out) self.assertTrue(out.endswith("worktree_setup: 2 commands ok in .worktrees/opus\n"), out) def test_idempotent_rerun(self): self.assertEqual(self.setup_()[0], 0) self.assertEqual(self.setup_()[0], 0) self.assertEqual((self.wt / "out" / "data" / "x.bin").read_text(), "main data\n") def test_from_subfolder_runs_at_worktree_project_root(self): code, out, err = self.setup_(cwd=self.wt / "docs") self.assertEqual((code, err), (0, ""), out) self.assertTrue((self.wt / "log.txt").is_file()) def test_nonzero_exit_reported_and_rest_skipped(self): (self.wt / "workflow.toml").write_text(TOML + 'worktree_setup = ["echo a > a.txt", "exit 3", "echo c > c.txt"]\n') code, out, err = self.setup_() self.assertEqual(code, 1, out + err) self.assertEqual(err, "wf: worktree_setup 'exit 3' failed (exit 3): later commands skipped\n") self.assertTrue((self.wt / "a.txt").is_file()) self.assertFalse((self.wt / "c.txt").exists()) def test_outside_worktree_fails(self): code, out, err = self.wf("setup", project=False, cwd=self.root) self.assertEqual((code, err), (1, "wf: setup runs inside a linked git worktree (lane worktree)\n")) def test_none_configured(self): (self.wt / "workflow.toml").write_text(TOML) code, out, err = self.setup_() self.assertEqual((code, out, err), (0, "no worktree_setup in workflow.toml: nothing to do\n", "")) class WorktreeConfigTest(WorktreeCli): """A lane worktree's own workflow.toml / area file: setup, gate, areas read it; --mark writes it.""" def edit_wt_toml(self, extra): (self.wt / "workflow.toml").write_text(TOML + extra) def test_setup_and_gate_read_worktree_toml(self): self.edit_wt_toml('worktree_setup = ["echo branch > b.txt"]\nquick_gate = ["echo g > g.txt"]\n') code, out, err = self.setup_() self.assertEqual((code, err), (0, ""), out) self.assertEqual((self.wt / "b.txt").read_text(), "branch\n") self.assertFalse((self.wt / "log.txt").exists()) code, out, err = self.wf("gate", project=False, cwd=self.wt) self.assertEqual((code, err), (0, ""), out) self.assertTrue((self.wt / "g.txt").is_file()) def test_books_stay_main_tree(self): self.edit_wt_toml("") (self.wt / "TASKS.md").write_text("# other\n") out = self.ok("show", "t-three", project=False, cwd=self.wt) self.assertIn("t-three", out) cfg = C.load_at(self.wt) self.assertEqual((cfg.root, cfg.local, cfg.tasks), (self.root, self.wt, self.root / "TASKS.md")) self.assertEqual(cfg.areas_file, self.wt / "CLAUDE.md") def test_areas_read_and_mark_worktree_copy(self): notes = "## Areas\n### Core\n- Code map: `nothing_here`\n" (self.root / "CLAUDE.md").write_text(notes.replace("Core", "Main")) (self.wt / "CLAUDE.md").write_text(notes) self.assertIn("Core: ", self.ok("areas", project=False, cwd=self.wt)) self.ok("areas", "--mark", "Core", project=False, cwd=self.wt) self.assertIn("- Checked: ", (self.wt / "CLAUDE.md").read_text()) self.assertEqual((self.root / "CLAUDE.md").read_text(), notes.replace("Core", "Main")) self.assertIn("Main: ", self.ok("areas", project=False, cwd=self.root)) def test_main_tree_ignores_worktree(self): self.edit_wt_toml('quick_gate = ["exit 9"]\n') self.assertIsNone(C.find_local(self.root)) code, out, err = self.wf("gate", project=False, cwd=self.root) self.assertEqual((code, out), (0, "no quick_gate in workflow.toml: nothing to do\n")) class SetupHintTest(GitCli): def env(self, name, pid): sock = self.root / f"{name}.sock" sock.write_text("") return {"CLAUDE_CODE_MESSAGING_SOCKET": str(sock), "CLAUDE_PID": str(pid), "CLAUDE_CODE_SESSION_ID": name} def test_multi_session_hint_adds_setup(self): son, me = self.env("s", os.getppid()), self.env("o", os.getpid()) self.ok("next", "--lane", "slow", "--as", "sonnet", "--brief", env=son) out = self.ok("next", "--lane", "fast", "--as", "opus", env=me) self.assertIn(" git worktree add .worktrees/fast -b fast/ master && cd .worktrees/fast && wf setup" " (wf there writes this TASKS.md)\n", out) (self.root / ".worktrees" / "fast").mkdir(parents=True) out = self.ok("next", "--lane", "fast", "--as", "opus", env=me) self.assertIn(" cd .worktrees/fast && git switch -c fast/ master && wf setup" " (wf there writes this TASKS.md)\n", out) if __name__ == "__main__": unittest.main()