@@ -73,6 +73,72 @@ class FakeStat:
7373 assert root_calls == []
7474
7575
76+ def test_benchmark_resolves_symlinked_tmp_dir (cli , monkeypatch , tmp_path ):
77+ # Regression: when the temp dir path contains a symlink (e.g. ton_work is a
78+ # symlink like /var/ton-work -> /mnt/data/ton-work), the cwd passed to uv must be
79+ # resolved. Otherwise uv's getcwd() (real path) differs from the symlink-prefixed
80+ # tontester_dir argument, its in-tree workspace-member check fails, and tontester
81+ # installs non-editable -- hiding the generated `tonapi` package (ModuleNotFoundError).
82+ # Real filesystem setup FIRST, before Path.mkdir is patched to a no-op below,
83+ # otherwise real_dir is never created and `link` becomes a dangling symlink.
84+ real_dir = tmp_path / "real"
85+ real_dir .mkdir ()
86+ link_dir = tmp_path / "link"
87+ link_dir .symlink_to (real_dir ) # symlink prefix, like a symlinked ton_work
88+
89+ monkeypatch .setattr (shutil , "which" , lambda name : "/usr/bin/uv" if name == "uv" else None )
90+ monkeypatch .setattr (shutil , "copytree" , lambda src , dst : None )
91+ monkeypatch .setattr (shutil , "copy" , lambda src , dst : None )
92+
93+ # Only pretend the benchmark temp parent is missing; delegate every other path to the
94+ # real lstat, otherwise a globally-broken os.lstat would also break Path.resolve()
95+ # (which follows symlinks via lstat) -- the very behavior under test.
96+ real_lstat = general_module .os .lstat
97+
98+ def fake_lstat (path ):
99+ if str (path ) == "/var/ton-work/tmp" :
100+ raise FileNotFoundError
101+ return real_lstat (path )
102+
103+ monkeypatch .setattr (general_module .os , "lstat" , fake_lstat )
104+ monkeypatch .setattr (general_module , "run_as_root" , lambda args : 0 )
105+ monkeypatch .setattr (Path , "mkdir" , lambda * a , ** kw : None )
106+ monkeypatch .setattr (Path , "glob" , lambda self , pattern : [])
107+
108+ class FakeTemporaryDirectory :
109+ def __init__ (self , dir = None ):
110+ pass
111+
112+ def __enter__ (self ):
113+ return str (link_dir )
114+
115+ def __exit__ (self , exc_type , exc_val , exc_tb ):
116+ return None
117+
118+ monkeypatch .setattr (general_module .tempfile , "TemporaryDirectory" , FakeTemporaryDirectory )
119+
120+ calls = []
121+
122+ def fake_subprocess_run (args , ** kwargs ):
123+ calls .append ({"args" : [str (a ) for a in args ], "kwargs" : kwargs })
124+ return subprocess .CompletedProcess (args , 0 )
125+
126+ monkeypatch .setattr (subprocess , "run" , fake_subprocess_run )
127+
128+ cli .execute ("benchmark" , no_color = True )
129+
130+ resolved = str (real_dir .resolve ())
131+ assert resolved != str (link_dir ) # sanity: symlink path really differs from its target
132+ # every uv invocation must run from the resolved real path, never the symlink prefix
133+ for call in calls :
134+ assert str (call ["kwargs" ].get ("cwd" )) == resolved
135+ # and the tontester path argument lives under that same resolved dir, so uv's
136+ # in-tree containment check succeeds and it installs editable
137+ add_args = calls [1 ]["args" ]
138+ assert add_args [:3 ] == ["uv" , "add" , "--editable" ]
139+ assert add_args [3 ].startswith (resolved )
140+
141+
76142def test_benchmark_runs (cli , monkeypatch , tmp_path ):
77143 monkeypatch .setattr (shutil , "which" , lambda name : "/usr/bin/uv" if name == "uv" else None )
78144 monkeypatch .setattr (shutil , "copytree" , lambda src , dst : None )
@@ -134,10 +200,10 @@ def fake_subprocess_run(args, **kwargs):
134200 assert "cwd" in calls [0 ]["kwargs" ]
135201 tmp_dir = str (calls [0 ]["kwargs" ]["cwd" ])
136202
137- # uv add tontester
203+ # uv add --editable tontester
138204 add_args = calls [1 ]["args" ]
139- assert add_args [:2 ] == ["uv" , "add" ]
140- assert "tontester" in add_args [2 ]
205+ assert add_args [:3 ] == ["uv" , "add" , "--editable " ]
206+ assert "tontester" in add_args [3 ]
141207
142208 # uv run generate_tl.py
143209 assert calls [2 ]["args" ][:2 ] == ["uv" , "run" ]
0 commit comments