11use  std:: any:: Any ; 
22#[ cfg( unix) ]  
33use  std:: os:: unix:: process:: ExitStatusExt ; 
4- use  std:: process:: ExitStatus ; 
4+ use  std:: process:: { ExitStatus ,  Output } ; 
5+ use  std:: { fmt,  io} ; 
56
67pub  use  self :: TestResult :: * ; 
78use  super :: bench:: BenchSamples ; 
@@ -103,15 +104,14 @@ pub(crate) fn calc_result(
103104    result
104105} 
105106
106- /// Creates a `TestResult` depending on the exit code of test subprocess. 
107- pub ( crate )  fn  get_result_from_exit_code ( 
108-     desc :  & TestDesc , 
107+ /// Creates a `TestResult` depending on the exit code of test subprocess 
108+ pub ( crate )  fn  get_result_from_exit_code_inner ( 
109109    status :  ExitStatus , 
110-     time_opts :  Option < & time:: TestTimeOptions > , 
111-     exec_time :  Option < & time:: TestExecTime > , 
110+     success_error_code :  i32 , 
112111)  -> TestResult  { 
113-     let  result = match  status. code ( )  { 
114-         Some ( TR_OK )  => TestResult :: TrOk , 
112+     match  status. code ( )  { 
113+         Some ( error_code)  if  error_code == success_error_code => TestResult :: TrOk , 
114+         Some ( crate :: ERROR_EXIT_CODE )  => TestResult :: TrFailed , 
115115        #[ cfg( windows) ]  
116116        Some ( STATUS_FAIL_FAST_EXCEPTION )  => TestResult :: TrFailed , 
117117        #[ cfg( unix) ]  
@@ -131,7 +131,17 @@ pub(crate) fn get_result_from_exit_code(
131131        Some ( code)  => TestResult :: TrFailedMsg ( format ! ( "got unexpected return code {code}" ) ) , 
132132        #[ cfg( not( any( windows,  unix) ) ) ]  
133133        Some ( _)  => TestResult :: TrFailed , 
134-     } ; 
134+     } 
135+ } 
136+ 
137+ /// Creates a `TestResult` depending on the exit code of test subprocess and on its runtime. 
138+ pub ( crate )  fn  get_result_from_exit_code ( 
139+     desc :  & TestDesc , 
140+     status :  ExitStatus , 
141+     time_opts :  Option < & time:: TestTimeOptions > , 
142+     exec_time :  Option < & time:: TestExecTime > , 
143+ )  -> TestResult  { 
144+     let  result = get_result_from_exit_code_inner ( status,  TR_OK ) ; 
135145
136146    // If test is already failed (or allowed to fail), do not change the result. 
137147    if  result != TestResult :: TrOk  { 
@@ -147,3 +157,92 @@ pub(crate) fn get_result_from_exit_code(
147157
148158    result
149159} 
160+ 
161+ pub  enum  RustdocResult  { 
162+     /// The test failed to compile. 
163+ CompileError , 
164+     /// The test is marked `compile_fail` but compiled successfully. 
165+ UnexpectedCompilePass , 
166+     /// The test failed to compile (as expected) but the compiler output did not contain all 
167+ /// expected error codes. 
168+ MissingErrorCodes ( Vec < String > ) , 
169+     /// The test binary was unable to be executed. 
170+ ExecutionError ( io:: Error ) , 
171+     /// The test binary exited with a non-zero exit code. 
172+ /// 
173+ /// This typically means an assertion in the test failed or another form of panic occurred. 
174+ ExecutionFailure ( Output ) , 
175+     /// The test is marked `should_panic` but the test binary executed successfully. 
176+ NoPanic ( Option < String > ) , 
177+ } 
178+ 
179+ impl  fmt:: Display  for  RustdocResult  { 
180+     fn  fmt ( & self ,  f :  & mut  fmt:: Formatter < ' _ > )  -> fmt:: Result  { 
181+         match  self  { 
182+             Self :: CompileError  => { 
183+                 write ! ( f,  "Couldn't compile the test." ) 
184+             } 
185+             Self :: UnexpectedCompilePass  => { 
186+                 write ! ( f,  "Test compiled successfully, but it's marked `compile_fail`." ) 
187+             } 
188+             Self :: NoPanic ( msg)  => { 
189+                 write ! ( f,  "Test didn't panic, but it's marked `should_panic`" ) ?; 
190+                 if  let  Some ( msg)  = msg { 
191+                     write ! ( f,  " ({msg})" ) ?; 
192+                 } 
193+                 f. write_str ( "." ) 
194+             } 
195+             Self :: MissingErrorCodes ( codes)  => { 
196+                 write ! ( f,  "Some expected error codes were not found: {codes:?}" ) 
197+             } 
198+             Self :: ExecutionError ( err)  => { 
199+                 write ! ( f,  "Couldn't run the test: {err}" ) ?; 
200+                 if  err. kind ( )  == io:: ErrorKind :: PermissionDenied  { 
201+                     f. write_str ( " - maybe your tempdir is mounted with noexec?" ) ?; 
202+                 } 
203+                 Ok ( ( ) ) 
204+             } 
205+             Self :: ExecutionFailure ( out)  => { 
206+                 writeln ! ( f,  "Test executable failed ({reason})." ,  reason = out. status) ?; 
207+ 
208+                 // FIXME(#12309): An unfortunate side-effect of capturing the test 
209+                 // executable's output is that the relative ordering between the test's 
210+                 // stdout and stderr is lost. However, this is better than the 
211+                 // alternative: if the test executable inherited the parent's I/O 
212+                 // handles the output wouldn't be captured at all, even on success. 
213+                 // 
214+                 // The ordering could be preserved if the test process' stderr was 
215+                 // redirected to stdout, but that functionality does not exist in the 
216+                 // standard library, so it may not be portable enough. 
217+                 let  stdout = str:: from_utf8 ( & out. stdout ) . unwrap_or_default ( ) ; 
218+                 let  stderr = str:: from_utf8 ( & out. stderr ) . unwrap_or_default ( ) ; 
219+ 
220+                 if  !stdout. is_empty ( )  || !stderr. is_empty ( )  { 
221+                     writeln ! ( f) ?; 
222+ 
223+                     if  !stdout. is_empty ( )  { 
224+                         writeln ! ( f,  "stdout:\n {stdout}" ) ?; 
225+                     } 
226+ 
227+                     if  !stderr. is_empty ( )  { 
228+                         writeln ! ( f,  "stderr:\n {stderr}" ) ?; 
229+                     } 
230+                 } 
231+                 Ok ( ( ) ) 
232+             } 
233+         } 
234+     } 
235+ } 
236+ 
237+ pub  fn  get_rustdoc_result ( output :  Output ,  should_panic :  bool )  -> Result < ( ) ,  RustdocResult >  { 
238+     let  result = get_result_from_exit_code_inner ( output. status ,  0 ) ; 
239+     match  ( result,  should_panic)  { 
240+         ( TestResult :: TrFailed ,  true )  | ( TestResult :: TrOk ,  false )  => Ok ( ( ) ) , 
241+         ( TestResult :: TrOk ,  true )  => Err ( RustdocResult :: NoPanic ( None ) ) , 
242+         ( TestResult :: TrFailedMsg ( msg) ,  true )  => Err ( RustdocResult :: NoPanic ( Some ( msg) ) ) , 
243+         ( TestResult :: TrFailedMsg ( _)  | TestResult :: TrFailed ,  false )  => { 
244+             Err ( RustdocResult :: ExecutionFailure ( output) ) 
245+         } 
246+         _ => unreachable ! ( "unexpected status for rustdoc test output" ) , 
247+     } 
248+ } 
0 commit comments