44
55from __future__ import annotations
66
7+ import atexit
78import hashlib
89import importlib .util
910import json
1011import os
1112import re
13+ import shutil
1214import subprocess
1315import sys
16+ import tempfile
1417import threading
1518import time
1619from pathlib import Path
@@ -30,8 +33,27 @@ def load_support():
3033 return module
3134
3235
36+ def cleanup_test_repository (repository : Path , test_repository : Path ) -> None :
37+ """Remove the case-owned candidate worktree on success or early failure."""
38+ subprocess .run (
39+ [
40+ "git" ,
41+ "-C" ,
42+ str (repository ),
43+ "worktree" ,
44+ "remove" ,
45+ "--force" ,
46+ str (test_repository ),
47+ ],
48+ check = False ,
49+ capture_output = True ,
50+ text = True ,
51+ )
52+ shutil .rmtree (test_repository , ignore_errors = True )
53+
54+
3355def run_tests (
34- runtime : dict [str , object ], env : dict [str , str ]
56+ repository : Path , runtime : dict [str , object ], env : dict [str , str ]
3557) -> subprocess .CompletedProcess [str ]:
3658 """Run the three candidate Cloudflare client tests against the local model."""
3759 return subprocess .run (
@@ -46,7 +68,7 @@ def run_tests(
4668 "--" ,
4769 "--nocapture" ,
4870 ],
49- cwd = Path ( str ( runtime [ " repository" ])) / "dstack" ,
71+ cwd = repository / "dstack" ,
5072 env = env ,
5173 text = True ,
5274 capture_output = True ,
@@ -72,6 +94,43 @@ def main() -> int:
7294 result_dir = Path (os .environ ["DSTACK_TEST_RESULT_DIR" ])
7395 runtime = json .loads (Path (os .environ ["DSTACK_TEST_RUNTIME_MANIFEST" ]).read_text ())
7496 support = load_support ()
97+ repository = Path (str (runtime ["repository" ]))
98+ common_dir = Path (
99+ subprocess .run (
100+ ["git" , "-C" , str (repository ), "rev-parse" , "--git-common-dir" ],
101+ check = True ,
102+ capture_output = True ,
103+ text = True ,
104+ ).stdout .strip ()
105+ ).resolve ()
106+ worktree_root = common_dir .parent .parent / f"{ common_dir .parent .name } .worktrees"
107+ worktree_root .mkdir (parents = True , exist_ok = True )
108+ test_repository = Path (
109+ tempfile .mkdtemp (prefix = "certbot-cloudflare-case-" , dir = worktree_root )
110+ )
111+ test_repository .rmdir ()
112+ subprocess .run (
113+ [
114+ "git" ,
115+ "-C" ,
116+ str (repository ),
117+ "worktree" ,
118+ "add" ,
119+ "--detach" ,
120+ str (test_repository ),
121+ str (runtime ["candidate_commit" ]),
122+ ],
123+ check = True ,
124+ capture_output = True ,
125+ text = True ,
126+ )
127+ atexit .register (cleanup_test_repository , repository , test_repository )
128+ source = test_repository / "dstack/certbot/src/dns01_client/cloudflare.rs"
129+ source_text = source .read_text ()
130+ disabled_gate = " #![cfg(not(test))]\n "
131+ if source_text .count (disabled_gate ) != 1 :
132+ raise RuntimeError ("candidate Cloudflare test gate changed unexpectedly" )
133+ source .write_text (source_text .replace (disabled_gate , "" , 1 ))
75134 state = support .DnsState (["example.test" , "adjacent.test" ])
76135 server = support .CloudflareServer (state )
77136 worker = threading .Thread (
@@ -87,23 +146,25 @@ def main() -> int:
87146 "CLOUDFLARE_API_URL" : f"http://127.0.0.1:{ server .server_port } /client/v4" ,
88147 }
89148 )
90- valid = run_tests (runtime , base_env )
149+ valid = run_tests (test_repository , runtime , base_env )
91150 valid_passed = passed_count (valid )
92151 wrong_env = base_env .copy ()
93152 wrong_env ["CLOUDFLARE_API_TOKEN" ] = "invalid-sentinel"
94- wrong = run_tests (runtime , wrong_env )
153+ wrong = run_tests (test_repository , runtime , wrong_env )
95154 with state .lock :
96155 state .failure = True
97- outage = run_tests (runtime , base_env )
156+ outage = run_tests (test_repository , runtime , base_env )
98157 with state .lock :
99158 state .failure = False
100- recovery = run_tests (runtime , base_env )
159+ recovery = run_tests (test_repository , runtime , base_env )
101160 recovery_passed = passed_count (recovery )
102161 snapshot = state .snapshot ()
103162 operation_count = len (state .operations )
104163 server .shutdown ()
105164 server .server_close ()
106165 worker .join (2 )
166+ cleanup_test_repository (repository , test_repository )
167+ atexit .unregister (cleanup_test_repository )
107168 checks = {
108169 "valid_add_list_remove_matrix" : valid .returncode == 0 and valid_passed >= 3 ,
109170 "wrong_token_rejected" : wrong .returncode != 0 ,
0 commit comments