collab-runtime 0.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- collab/__init__.py +77 -0
- collab/__main__.py +11 -0
- collab_runtime-0.2.9.dist-info/METADATA +218 -0
- collab_runtime-0.2.9.dist-info/RECORD +82 -0
- collab_runtime-0.2.9.dist-info/WHEEL +5 -0
- collab_runtime-0.2.9.dist-info/entry_points.txt +3 -0
- collab_runtime-0.2.9.dist-info/licenses/LICENSE +21 -0
- collab_runtime-0.2.9.dist-info/top_level.txt +10 -0
- scripts/cleanup.py +395 -0
- scripts/collab_git_hook.py +190 -0
- scripts/format_code.py +594 -0
- scripts/generate_tests.py +560 -0
- scripts/validate_code.py +1397 -0
- src/__init__.py +4 -0
- src/dashboard/index.html +1131 -0
- src/live_locks_watcher.py +1982 -0
- src/lock_client.py +4268 -0
- src/logging_config.py +259 -0
- src/main.py +436 -0
- tests/backend/__init__.py +0 -0
- tests/backend/functional/__init__.py +0 -0
- tests/backend/functional/test_package_imports.py +43 -0
- tests/backend/integration/__init__.py +0 -0
- tests/backend/integration/test_cli_contract_parity.py +220 -0
- tests/backend/performance/__init__.py +0 -0
- tests/backend/reliability/__init__.py +0 -0
- tests/backend/security/__init__.py +0 -0
- tests/backend/unit/live_locks_watcher/__init__.py +5 -0
- tests/backend/unit/live_locks_watcher/_helpers.py +123 -0
- tests/backend/unit/live_locks_watcher/conftest.py +18 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_dashboard.py +188 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_developer.py +56 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_graceful_shutdown.py +459 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_main.py +1925 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_module.py +187 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_multi_session.py +320 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_notify.py +67 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_parsing.py +155 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_process_helpers.py +684 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_processing.py +173 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_prompt_abort.py +71 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_reconcile.py +516 -0
- tests/backend/unit/live_locks_watcher/test_live_locks_watcher_scan.py +296 -0
- tests/backend/unit/lock_client/__init__.py +1 -0
- tests/backend/unit/lock_client/_helpers.py +132 -0
- tests/backend/unit/lock_client/test_lock_client_acquire.py +214 -0
- tests/backend/unit/lock_client/test_lock_client_active.py +104 -0
- tests/backend/unit/lock_client/test_lock_client_api.py +63 -0
- tests/backend/unit/lock_client/test_lock_client_cli.py +682 -0
- tests/backend/unit/lock_client/test_lock_client_daemon.py +3730 -0
- tests/backend/unit/lock_client/test_lock_client_dashboard.py +438 -0
- tests/backend/unit/lock_client/test_lock_client_discover.py +241 -0
- tests/backend/unit/lock_client/test_lock_client_force_release.py +354 -0
- tests/backend/unit/lock_client/test_lock_client_helper_branches.py +1890 -0
- tests/backend/unit/lock_client/test_lock_client_history.py +301 -0
- tests/backend/unit/lock_client/test_lock_client_isolation.py +316 -0
- tests/backend/unit/lock_client/test_lock_client_pid.py +75 -0
- tests/backend/unit/lock_client/test_lock_client_reconcile.py +464 -0
- tests/backend/unit/lock_client/test_lock_client_release.py +77 -0
- tests/backend/unit/lock_client/test_lock_client_shutdown.py +1110 -0
- tests/backend/unit/lock_client/test_lock_client_utils.py +474 -0
- tests/backend/unit/lock_client/test_lock_client_watch.py +866 -0
- tests/backend/unit/scripts/__init__.py +1 -0
- tests/backend/unit/scripts/_helpers.py +42 -0
- tests/backend/unit/scripts/test_cleanup.py +285 -0
- tests/backend/unit/scripts/test_collab_git_hook.py +280 -0
- tests/backend/unit/scripts/test_collab_git_hook_ported.py +50 -0
- tests/backend/unit/scripts/test_format_code.py +368 -0
- tests/backend/unit/scripts/test_format_code_ported.py +177 -0
- tests/backend/unit/scripts/test_generate_tests.py +305 -0
- tests/backend/unit/scripts/test_hook_templates.py +357 -0
- tests/backend/unit/scripts/test_setup_hook_overlay.py +95 -0
- tests/backend/unit/scripts/test_validate_code.py +867 -0
- tests/backend/unit/scripts/test_validate_code_ported.py +237 -0
- tests/backend/unit/test_entrypoints_main_run.py +83 -0
- tests/backend/unit/test_logging_config.py +529 -0
- tests/backend/unit/test_main_watch_pid_file.py +278 -0
- tests/conftest.py +167 -0
- tests/frontend/__init__.py +0 -0
- tests/frontend/jest/__init__.py +0 -0
- tests/frontend/playwright/__init__.py +0 -0
- tests/packaging/test_smoke_install.py +76 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Processing helpers tests for live_locks_watcher."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from ._helpers import load_watcher_module
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_process_new_files_handles_local_add_exception(monkeypatch):
|
|
11
|
+
mod = load_watcher_module()
|
|
12
|
+
|
|
13
|
+
# Replace _local_owned_locks with an object whose add() raises
|
|
14
|
+
class BadSet:
|
|
15
|
+
def add(self, *a, **k):
|
|
16
|
+
raise RuntimeError("boom add")
|
|
17
|
+
|
|
18
|
+
old = mod._local_owned_locks
|
|
19
|
+
mod._local_owned_locks = BadSet()
|
|
20
|
+
|
|
21
|
+
# Fake client returns success (no conflict)
|
|
22
|
+
class RpcClient:
|
|
23
|
+
def rpc(self, *a, **k):
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def execute(self):
|
|
27
|
+
return type("R", (), {"data": []})()
|
|
28
|
+
|
|
29
|
+
client = RpcClient()
|
|
30
|
+
mod.DEVELOPER_ID = "tester"
|
|
31
|
+
|
|
32
|
+
# Should not raise even though add() raises inside
|
|
33
|
+
mod._process_new_files(client, "main", {"src/a.py"})
|
|
34
|
+
|
|
35
|
+
# restore
|
|
36
|
+
mod._local_owned_locks = old
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_process_releases_handles_discard_exception(monkeypatch):
|
|
40
|
+
mod = load_watcher_module()
|
|
41
|
+
|
|
42
|
+
# Replace _local_owned_locks with object whose discard raises
|
|
43
|
+
class BadSet:
|
|
44
|
+
def discard(self, *a, **k):
|
|
45
|
+
raise RuntimeError("boom discard")
|
|
46
|
+
|
|
47
|
+
old = mod._local_owned_locks
|
|
48
|
+
mod._local_owned_locks = BadSet()
|
|
49
|
+
|
|
50
|
+
# Fake client for delete.execute()
|
|
51
|
+
class FakeClientLocal2:
|
|
52
|
+
"""Fake Supabase client with fluent CRUD interface for testing."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, data=None, explode=False):
|
|
55
|
+
self._data = data if data is not None else []
|
|
56
|
+
self._explode = explode
|
|
57
|
+
self._rows = list(self._data)
|
|
58
|
+
|
|
59
|
+
def table(self, *a, **k):
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def select(self, *a, **k):
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def insert(self, *a, **k):
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def update(self, *a, **k):
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def delete(self):
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def eq(self, *a, **k):
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
def ilike(self, *a, **k):
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def order(self, *a, **k):
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
def limit(self, *a, **k):
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def rpc(self, *a, **k):
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
def execute(self):
|
|
90
|
+
if self._explode:
|
|
91
|
+
raise RuntimeError("backend down")
|
|
92
|
+
|
|
93
|
+
class R:
|
|
94
|
+
data = self._data
|
|
95
|
+
|
|
96
|
+
return R()
|
|
97
|
+
|
|
98
|
+
fake = FakeClientLocal2(data=[])
|
|
99
|
+
mod.DEVELOPER_ID = "tester"
|
|
100
|
+
|
|
101
|
+
# Should not raise even though discard() raises inside
|
|
102
|
+
mod._process_releases(fake, {"src/b.py"})
|
|
103
|
+
|
|
104
|
+
mod._local_owned_locks = old
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# RESTORED: test_process_new_files_and_releases_moved (from HEAD)
|
|
108
|
+
def test_process_new_files_and_releases_moved(monkeypatch):
|
|
109
|
+
mod = load_watcher_module()
|
|
110
|
+
|
|
111
|
+
# prepare a fake client that returns conflict for a specific file
|
|
112
|
+
class Res:
|
|
113
|
+
def __init__(self, data):
|
|
114
|
+
self.data = data
|
|
115
|
+
|
|
116
|
+
def execute(self):
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
class Client:
|
|
120
|
+
def rpc(self, name, params):
|
|
121
|
+
return Res([{"status": "conflict", "owner": "bob"}])
|
|
122
|
+
|
|
123
|
+
def table(self, name):
|
|
124
|
+
class Q:
|
|
125
|
+
def delete(self):
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def eq(self, *a, **k):
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def execute(self):
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
return Q()
|
|
135
|
+
|
|
136
|
+
mod.DEVELOPER_ID = "alice"
|
|
137
|
+
# ensure sets are clean
|
|
138
|
+
mod._active_conflicts.clear()
|
|
139
|
+
mod._local_owned_locks.clear()
|
|
140
|
+
client = Client()
|
|
141
|
+
mod._process_new_files(client, "main", {"a.txt"})
|
|
142
|
+
assert "a.txt" in mod._active_conflicts
|
|
143
|
+
|
|
144
|
+
# test _process_releases for ephemeral dev
|
|
145
|
+
monkeypatch.setattr(mod, "DEVELOPER_ID", "test_dev_1")
|
|
146
|
+
mod._process_releases(client, {"a.txt"})
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_get_modified_and_unpushed_files_status_and_diff_migrated(
|
|
150
|
+
monkeypatch, tmp_path
|
|
151
|
+
):
|
|
152
|
+
mod = load_watcher_module()
|
|
153
|
+
# Simulate git status output by creating a fake repo structure
|
|
154
|
+
repo = tmp_path / "repo"
|
|
155
|
+
repo.mkdir()
|
|
156
|
+
# Create a fake file and ensure _get_modified_and_unpushed_files handles it
|
|
157
|
+
f = repo / "src" / "new.py"
|
|
158
|
+
f.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
f.write_text("print('hi')")
|
|
160
|
+
|
|
161
|
+
# Monkeypatch subprocess.check_output used by watcher to get git status
|
|
162
|
+
def fake_check_output(cmd, *a, **k):
|
|
163
|
+
if "status" in cmd:
|
|
164
|
+
return b" M src/new.py\n"
|
|
165
|
+
if "rev-list" in cmd:
|
|
166
|
+
return b""
|
|
167
|
+
raise subprocess.CalledProcessError(1, cmd)
|
|
168
|
+
|
|
169
|
+
monkeypatch.setattr(subprocess, "check_output", fake_check_output)
|
|
170
|
+
# The watcher implementation reads from module-level _PROJECT_ROOT
|
|
171
|
+
monkeypatch.setattr(mod, "_PROJECT_ROOT", str(repo))
|
|
172
|
+
changed = mod._get_modified_and_unpushed_files()
|
|
173
|
+
assert isinstance(changed, set)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Cover post-restart conflict abort branch in watcher prompt."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from ._helpers import load_watcher_module
|
|
11
|
+
|
|
12
|
+
watcher = load_watcher_module()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_handle_post_restart_conflict_abort_choice(monkeypatch):
|
|
16
|
+
shutdown_called = []
|
|
17
|
+
|
|
18
|
+
class _Stdin:
|
|
19
|
+
@staticmethod
|
|
20
|
+
def isatty():
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
monkeypatch.setattr(sys, "stdin", _Stdin())
|
|
24
|
+
monkeypatch.setattr(builtins, "input", lambda _prompt="": "4")
|
|
25
|
+
monkeypatch.setattr(
|
|
26
|
+
watcher,
|
|
27
|
+
"_graceful_shutdown",
|
|
28
|
+
lambda: shutdown_called.append(True),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
exits = []
|
|
32
|
+
|
|
33
|
+
def _fake_exit(code):
|
|
34
|
+
exits.append(code)
|
|
35
|
+
raise SystemExit(code)
|
|
36
|
+
|
|
37
|
+
monkeypatch.setattr(sys, "exit", _fake_exit)
|
|
38
|
+
|
|
39
|
+
with pytest.raises(SystemExit) as exc:
|
|
40
|
+
watcher._handle_post_restart_conflict(
|
|
41
|
+
client=object(),
|
|
42
|
+
fp="src/live_locks_watcher.py",
|
|
43
|
+
lock_data={"owner": "dev", "branch": "main", "reason": "test"},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
assert exc.value.code == 1
|
|
47
|
+
assert exits == [1]
|
|
48
|
+
assert shutdown_called == [True]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_handle_post_restart_conflict_dashboard_unavailable(monkeypatch, capsys):
|
|
52
|
+
choices = iter(["3", "1"])
|
|
53
|
+
|
|
54
|
+
class _Stdin:
|
|
55
|
+
@staticmethod
|
|
56
|
+
def isatty():
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
monkeypatch.setattr(sys, "stdin", _Stdin())
|
|
60
|
+
monkeypatch.setattr(builtins, "input", lambda _prompt="": next(choices))
|
|
61
|
+
monkeypatch.setattr(watcher, "_dashboard_url", None)
|
|
62
|
+
monkeypatch.setattr(watcher, "_start_dashboard_server", lambda: None)
|
|
63
|
+
|
|
64
|
+
watcher._handle_post_restart_conflict(
|
|
65
|
+
client=object(),
|
|
66
|
+
fp="src/live_locks_watcher.py",
|
|
67
|
+
lock_data={"owner": "dev", "branch": "main", "reason": "test"},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
out = capsys.readouterr().out
|
|
71
|
+
assert "Dashboard unavailable" in out
|