11//! Run all tests in a project, similar to `cargo test`, but using the mir interpreter.
22
3+ use std:: convert:: identity;
4+ use std:: thread:: Builder ;
5+ use std:: time:: { Duration , Instant } ;
36use std:: { cell:: RefCell , fs:: read_to_string, panic:: AssertUnwindSafe , path:: PathBuf } ;
47
58use hir:: { Change , Crate } ;
69use ide:: { AnalysisHost , DiagnosticCode , DiagnosticsConfig } ;
10+ use itertools:: Either ;
711use profile:: StopWatch ;
812use project_model:: target_data_layout:: RustcDataLayoutConfig ;
913use project_model:: { target_data_layout, CargoConfig , ProjectWorkspace , RustLibSource , Sysroot } ;
@@ -100,6 +104,7 @@ impl Tester {
100104 }
101105
102106 fn test ( & mut self , p : PathBuf ) {
107+ println ! ( "{}" , p. display( ) ) ;
103108 if p. parent ( ) . unwrap ( ) . file_name ( ) . unwrap ( ) == "auxiliary" {
104109 // These are not tests
105110 return ;
@@ -132,15 +137,44 @@ impl Tester {
132137 self . host . apply_change ( change) ;
133138 let diagnostic_config = DiagnosticsConfig :: test_sample ( ) ;
134139
140+ let res = std:: thread:: scope ( |s| {
141+ let worker = Builder :: new ( )
142+ . stack_size ( 40 * 1024 * 1024 )
143+ . spawn_scoped ( s, {
144+ let diagnostic_config = & diagnostic_config;
145+ let main = std:: thread:: current ( ) ;
146+ let analysis = self . host . analysis ( ) ;
147+ let root_file = self . root_file ;
148+ move || {
149+ let res = std:: panic:: catch_unwind ( move || {
150+ analysis. diagnostics (
151+ diagnostic_config,
152+ ide:: AssistResolveStrategy :: None ,
153+ root_file,
154+ )
155+ } ) ;
156+ main. unpark ( ) ;
157+ res
158+ }
159+ } )
160+ . unwrap ( ) ;
161+
162+ let timeout = Duration :: from_secs ( 5 ) ;
163+ let now = Instant :: now ( ) ;
164+ while now. elapsed ( ) <= timeout && !worker. is_finished ( ) {
165+ std:: thread:: park_timeout ( timeout - now. elapsed ( ) ) ;
166+ }
167+
168+ if !worker. is_finished ( ) {
169+ // attempt to cancel the worker, won't work for chalk hangs unfortunately
170+ self . host . request_cancellation ( ) ;
171+ }
172+ worker. join ( ) . and_then ( identity)
173+ } ) ;
135174 let mut actual = FxHashMap :: default ( ) ;
136- let panicked = match std:: panic:: catch_unwind ( || {
137- self . host
138- . analysis ( )
139- . diagnostics ( & diagnostic_config, ide:: AssistResolveStrategy :: None , self . root_file )
140- . unwrap ( )
141- } ) {
142- Err ( e) => Some ( e) ,
143- Ok ( diags) => {
175+ let panicked = match res {
176+ Err ( e) => Some ( Either :: Left ( e) ) ,
177+ Ok ( Ok ( diags) ) => {
144178 for diag in diags {
145179 if !matches ! ( diag. code, DiagnosticCode :: RustcHardError ( _) ) {
146180 continue ;
@@ -152,21 +186,27 @@ impl Tester {
152186 }
153187 None
154188 }
189+ Ok ( Err ( e) ) => Some ( Either :: Right ( e) ) ,
155190 } ;
156191 // Ignore tests with diagnostics that we don't emit.
157192 ignore_test |= expected. keys ( ) . any ( |k| !SUPPORTED_DIAGNOSTICS . contains ( k) ) ;
158193 if ignore_test {
159194 println ! ( "{p:?} IGNORE" ) ;
160195 self . ignore_count += 1 ;
161196 } else if let Some ( panic) = panicked {
162- if let Some ( msg) = panic
163- . downcast_ref :: < String > ( )
164- . map ( String :: as_str)
165- . or_else ( || panic. downcast_ref :: < & str > ( ) . copied ( ) )
166- {
167- println ! ( "{msg:?} " )
197+ match panic {
198+ Either :: Left ( panic) => {
199+ if let Some ( msg) = panic
200+ . downcast_ref :: < String > ( )
201+ . map ( String :: as_str)
202+ . or_else ( || panic. downcast_ref :: < & str > ( ) . copied ( ) )
203+ {
204+ println ! ( "{msg:?} " )
205+ }
206+ println ! ( "{p:?} PANIC" ) ;
207+ }
208+ Either :: Right ( _) => println ! ( "{p:?} CANCELLED" ) ,
168209 }
169- println ! ( "PANIC" ) ;
170210 self . fail_count += 1 ;
171211 } else if actual == expected {
172212 println ! ( "{p:?} PASS" ) ;
0 commit comments