11use std:: process:: Command ;
2+ use std:: sync:: { Mutex , MutexGuard , OnceLock } ;
3+
4+ static TEST_LOCK : OnceLock < Mutex < ( ) > > = OnceLock :: new ( ) ;
5+
6+ fn test_lock ( ) -> MutexGuard < ' static , ( ) > {
7+ TEST_LOCK
8+ . get_or_init ( || Mutex :: new ( ( ) ) )
9+ . lock ( )
10+ . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) )
11+ }
212
313fn run ( args : & [ & str ] ) -> String {
414 let output = Command :: new ( env ! ( "CARGO_BIN_EXE_ctxt" ) )
@@ -11,20 +21,23 @@ fn run(args: &[&str]) -> String {
1121
1222#[ test]
1323fn help_mentions_safety_defaults ( ) {
24+ let _guard = test_lock ( ) ;
1425 let stdout = run ( & [ "--help" ] ) ;
1526 assert ! ( stdout. contains( "SAFETY DEFAULTS" ) ) ;
1627 assert ! ( stdout. contains( "network_default=deny" ) ) ;
1728}
1829
1930#[ test]
2031fn doctor_is_local_and_deterministic ( ) {
32+ let _guard = test_lock ( ) ;
2133 let stdout = run ( & [ "doctor" ] ) ;
2234 assert ! ( stdout. contains( "status: ok" ) ) ;
2335 assert ! ( stdout. contains( "provider_default: dummy" ) ) ;
2436}
2537
2638#[ test]
2739fn providers_include_dummy_and_ollama_variants ( ) {
40+ let _guard = test_lock ( ) ;
2841 let stdout = run ( & [ "providers" , "list" ] ) ;
2942 assert ! ( stdout. contains( "dummy" ) ) ;
3043 assert ! ( stdout. contains( "ollama-local" ) ) ;
@@ -33,6 +46,7 @@ fn providers_include_dummy_and_ollama_variants() {
3346
3447#[ test]
3548fn ask_dummy_provider_succeeds ( ) {
49+ let _guard = test_lock ( ) ;
3650 let stdout = run ( & [ "ask" , "--provider" , "dummy" , "How do I test this repo?" ] ) ;
3751 assert ! ( stdout. contains( "Response from dummy provider:" ) ) ;
3852 assert ! ( stdout. contains( "Mock LLM response from CompText Dummy Provider." ) ) ;
@@ -47,8 +61,9 @@ fn ask_dummy_provider_succeeds() {
4761
4862#[ test]
4963fn ask_ollama_provider_fails_gracefully_offline ( ) {
64+ let _guard = test_lock ( ) ;
5065 let output = std:: process:: Command :: new ( env ! ( "CARGO_BIN_EXE_ctxt" ) )
51- . args ( & [ "ask" , "--provider" , "ollama-local" , "hello" ] )
66+ . args ( [ "ask" , "--provider" , "ollama-local" , "hello" ] )
5267 . output ( )
5368 . expect ( "ctxt binary should run" ) ;
5469
@@ -57,11 +72,20 @@ fn ask_ollama_provider_fails_gracefully_offline() {
5772 "command should fail because local Ollama is offline"
5873 ) ;
5974 let stderr = String :: from_utf8 ( output. stderr ) . expect ( "stderr should be UTF-8" ) ;
60- assert ! ( stderr. contains( "error: HTTP request to Ollama failed" ) ) ;
75+ let stderr_lower = stderr. to_ascii_lowercase ( ) ;
76+ assert ! (
77+ stderr_lower. contains( "ollama" )
78+ && ( stderr_lower. contains( "error:" )
79+ || stderr_lower. contains( "failed" )
80+ || stderr_lower. contains( "refused" )
81+ || stderr_lower. contains( "connection" ) ) ,
82+ "unexpected stderr from offline ollama run: {stderr}"
83+ ) ;
6184}
6285
6386#[ test]
6487fn propose_dummy_provider_succeeds ( ) {
88+ let _guard = test_lock ( ) ;
6589 let slugified_path = std:: path:: Path :: new ( "proposals/proposal_add_context_inspect.json" ) ;
6690 let latest_path = std:: path:: Path :: new ( "proposals/proposal.latest.json" ) ;
6791 if slugified_path. exists ( ) {
@@ -83,10 +107,14 @@ fn propose_dummy_provider_succeeds() {
83107 assert ! ( proposal_content. contains( "\" task\" : \" Add context inspect\" " ) ) ;
84108 assert ! ( proposal_content. contains( "\" schema_version\" : \" 0.1\" " ) ) ;
85109 assert ! ( proposal_content. contains( "Mock patch generated by dummy provider:" ) ) ;
110+
111+ let _ = std:: fs:: remove_file ( slugified_path) ;
112+ let _ = std:: fs:: remove_file ( latest_path) ;
86113}
87114
88115#[ test]
89116fn apply_and_validate_succeeds ( ) {
117+ let _guard = test_lock ( ) ;
90118 let mock_file = std:: path:: Path :: new ( "tests/mock_applied_patch.rs" ) ;
91119 std:: fs:: write ( mock_file, "// initial\n " ) . unwrap ( ) ;
92120
@@ -135,10 +163,12 @@ fn apply_and_validate_succeeds() {
135163
136164 let _ = std:: fs:: remove_file ( mock_file) ;
137165 let _ = std:: fs:: remove_file ( proposal_path) ;
166+ let _ = std:: fs:: remove_file ( latest_path) ;
138167}
139168
140169#[ test]
141170fn apply_rejects_disallowed_paths ( ) {
171+ let _guard = test_lock ( ) ;
142172 let mock_proposal = serde_json:: json!( {
143173 "schema_version" : "0.1" ,
144174 "task" : "Malicious task" ,
@@ -161,7 +191,7 @@ fn apply_rejects_disallowed_paths() {
161191 std:: fs:: write ( path, serde_json:: to_string_pretty ( & mock_proposal) . unwrap ( ) ) . unwrap ( ) ;
162192
163193 let output = std:: process:: Command :: new ( env ! ( "CARGO_BIN_EXE_ctxt" ) )
164- . args ( & [ "apply" , "--yes" , "proposals/proposal_malicious.json" ] )
194+ . args ( [ "apply" , "--yes" , "proposals/proposal_malicious.json" ] )
165195 . output ( )
166196 . expect ( "ctxt binary should run" ) ;
167197
0 commit comments