-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_project_resolution_multiwindow.py
More file actions
146 lines (111 loc) · 6.17 KB
/
Copy pathtest_project_resolution_multiwindow.py
File metadata and controls
146 lines (111 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"""Регрессионные тесты multi-window изоляции резолвера проекта (INC-MULTI-WINDOW).
История бага: SQLite `multi_workspace_state.active_workspace_id` глобальный
на весь Zed — одна строка на namespace (key = window_id), но резолв берёт
`rowid DESC LIMIT 1` БЕЗ фильтра по окну. Два окна Zed резолвили ОДИН
проект → PID-lock конфликт (database_lock.py) → ProjectState.FAILED
во втором окне и привязка MCP к чужому проекту.
Фикс: CWD-first. Zed запускает отдельный MCP-процесс на окно и ставит
CWD = корень окна — CWD единственный per-process сигнал.
"""
from __future__ import annotations
import os
import sqlite3
from pathlib import Path
import pytest
import src.core.project_resolution as pr
from src.core.project_resolution import (
_check_sqlite_schema_health,
resolve_project_root,
)
@pytest.fixture()
def _cwd_restorer():
"""Восстанавливает CWD после теста, меняющего os.chdir."""
old = Path.cwd()
yield
os.chdir(old)
def _make_project(tmp_path: Path, name: str) -> Path:
p = tmp_path / name
p.mkdir()
return p
class TestCwdFirstIsolation:
"""Главная регрессия: каждое «окно» резолвит СВОЙ CWD."""
def test_two_windows_resolve_own_cwd(self, tmp_path, _cwd_restorer):
"""Два окна (два CWD) — два разных проекта, без обращения к SQLite."""
project_a = _make_project(tmp_path, "project_a")
project_b = _make_project(tmp_path, "project_b")
os.chdir(project_a)
assert resolve_project_root().resolve() == project_a.resolve()
os.chdir(project_b)
assert resolve_project_root().resolve() == project_b.resolve()
def test_cwd_wins_over_global_active_workspace(self, tmp_path, _cwd_restorer, monkeypatch):
"""Регрессия бага: CWD побеждает глобальный active_workspace_id.
FakeConn имитирует SQLite Zed, где «активный workspace» = project_b.
Раньше (SQLite-first) оба окна резолвили project_b → PID-lock конфликт.
"""
project_a = _make_project(tmp_path, "project_a")
project_b = _make_project(tmp_path, "project_b")
class FakeCursor:
def execute(self, *a, **k):
pass
def fetchone(self):
# scoped_kv_store row: (key, value) — активный = project_b
return ("5", '{"active_workspace_id":5}')
class FakeConn:
def cursor(self):
return FakeCursor()
monkeypatch.setattr(pr, "_get_sqlite_connection", lambda: FakeConn())
monkeypatch.setattr(pr, "_resolve_env_project_root", lambda: None)
monkeypatch.delenv("ZED_WORKTREE_ROOT", raising=False)
os.chdir(project_a)
assert resolve_project_root().resolve() == project_a.resolve()
os.chdir(project_b)
assert resolve_project_root().resolve() == project_b.resolve()
def test_zed_literal_project_path_is_ignored(self, tmp_path, _cwd_restorer, monkeypatch):
"""PROJECT_PATH="$ZED_WORKTREE_ROOT" (литерал Zed, не раскрыт) → None → CWD."""
project = _make_project(tmp_path, "project_a")
monkeypatch.setenv("PROJECT_PATH", "$ZED_WORKTREE_ROOT")
monkeypatch.delenv("ZED_WORKTREE_ROOT", raising=False)
monkeypatch.setattr(pr, "_get_sqlite_connection", lambda: None)
os.chdir(project)
assert resolve_project_root().resolve() == project.resolve()
def test_provided_wins_over_cwd(self, tmp_path, _cwd_restorer):
"""Явный provided (аргумент вызова) важнее CWD."""
project_a = _make_project(tmp_path, "project_a")
project_b = _make_project(tmp_path, "project_b")
os.chdir(project_a)
assert resolve_project_root(str(project_b)).resolve() == project_b.resolve()
class TestCwdSelfIndexGuard:
def test_cwd_equal_ext_root_is_skipped(self, tmp_path, _cwd_restorer, monkeypatch):
"""CWD == ext_root (dev/test-режим) — CWD отклоняется, fallback работает."""
monkeypatch.setattr(pr, "ext_root", tmp_path) # CWD == ext_root
monkeypatch.setattr(pr, "_get_sqlite_connection", lambda: None)
env_fallback = tmp_path / "env-project"
monkeypatch.setattr(pr, "_resolve_env_project_root", lambda: env_fallback)
monkeypatch.delenv("ZED_WORKTREE_ROOT", raising=False)
os.chdir(tmp_path)
assert resolve_project_root() == env_fallback # CWD пропущен guard'ом
class TestSqliteSchemaHealth:
def test_current_schema_ok(self):
"""Реальная схема Zed (workspace_id/paths/timestamp) — без предупреждений."""
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE scoped_kv_store (namespace TEXT, key TEXT, value TEXT)")
conn.execute(
"CREATE TABLE workspaces (workspace_id TEXT, paths TEXT, paths_order TEXT, "
"remote_connection_id TEXT, timestamp TEXT)"
)
assert _check_sqlite_schema_health(conn) is None
conn.close()
def test_old_schema_detected(self):
"""Устаревшая схема (workspace/data) — предупреждение, а не молчание."""
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE scoped_kv_store (namespace TEXT, key TEXT, value TEXT)")
conn.execute("CREATE TABLE workspaces (workspace TEXT, data TEXT)")
assert _check_sqlite_schema_health(conn) is not None
conn.close()
def test_missing_table_detected(self):
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE scoped_kv_store (namespace TEXT, key TEXT, value TEXT)")
assert _check_sqlite_schema_health(conn) is not None
conn.close()
def test_none_connection(self):
assert _check_sqlite_schema_health(None) is not None