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,93 @@ pub(crate) fn get_result_from_exit_code(
147157
148158 result
149159}
160+
161+ #[ derive( Debug ) ]
162+ pub enum RustdocResult {
163+ /// The test failed to compile.
164+ CompileError ,
165+ /// The test is marked `compile_fail` but compiled successfully.
166+ UnexpectedCompilePass ,
167+ /// The test failed to compile (as expected) but the compiler output did not contain all
168+ /// expected error codes.
169+ MissingErrorCodes ( Vec < String > ) ,
170+ /// The test binary was unable to be executed.
171+ ExecutionError ( io:: Error ) ,
172+ /// The test binary exited with a non-zero exit code.
173+ ///
174+ /// This typically means an assertion in the test failed or another form of panic occurred.
175+ ExecutionFailure ( Output ) ,
176+ /// The test is marked `should_panic` but the test binary executed successfully.
177+ NoPanic ( Option < String > ) ,
178+ }
179+
180+ impl fmt:: Display for RustdocResult {
181+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
182+ match self {
183+ Self :: CompileError => {
184+ write ! ( f, "Couldn't compile the test." )
185+ }
186+ Self :: UnexpectedCompilePass => {
187+ write ! ( f, "Test compiled successfully, but it's marked `compile_fail`." )
188+ }
189+ Self :: NoPanic ( msg) => {
190+ write ! ( f, "Test didn't panic, but it's marked `should_panic`" ) ?;
191+ if let Some ( msg) = msg {
192+ write ! ( f, " ({msg})" ) ?;
193+ }
194+ f. write_str ( "." )
195+ }
196+ Self :: MissingErrorCodes ( codes) => {
197+ write ! ( f, "Some expected error codes were not found: {codes:?}" )
198+ }
199+ Self :: ExecutionError ( err) => {
200+ write ! ( f, "Couldn't run the test: {err}" ) ?;
201+ if err. kind ( ) == io:: ErrorKind :: PermissionDenied {
202+ f. write_str ( " - maybe your tempdir is mounted with noexec?" ) ?;
203+ }
204+ Ok ( ( ) )
205+ }
206+ Self :: ExecutionFailure ( out) => {
207+ writeln ! ( f, "Test executable failed ({reason})." , reason = out. status) ?;
208+
209+ // FIXME(#12309): An unfortunate side-effect of capturing the test
210+ // executable's output is that the relative ordering between the test's
211+ // stdout and stderr is lost. However, this is better than the
212+ // alternative: if the test executable inherited the parent's I/O
213+ // handles the output wouldn't be captured at all, even on success.
214+ //
215+ // The ordering could be preserved if the test process' stderr was
216+ // redirected to stdout, but that functionality does not exist in the
217+ // standard library, so it may not be portable enough.
218+ let stdout = str:: from_utf8 ( & out. stdout ) . unwrap_or_default ( ) ;
219+ let stderr = str:: from_utf8 ( & out. stderr ) . unwrap_or_default ( ) ;
220+
221+ if !stdout. is_empty ( ) || !stderr. is_empty ( ) {
222+ writeln ! ( f) ?;
223+
224+ if !stdout. is_empty ( ) {
225+ writeln ! ( f, "stdout:\n {stdout}" ) ?;
226+ }
227+
228+ if !stderr. is_empty ( ) {
229+ writeln ! ( f, "stderr:\n {stderr}" ) ?;
230+ }
231+ }
232+ Ok ( ( ) )
233+ }
234+ }
235+ }
236+ }
237+
238+ pub fn get_rustdoc_result ( output : Output , should_panic : bool ) -> Result < ( ) , RustdocResult > {
239+ let result = get_result_from_exit_code_inner ( output. status , 0 ) ;
240+ match ( result, should_panic) {
241+ ( TestResult :: TrFailed , true ) | ( TestResult :: TrOk , false ) => Ok ( ( ) ) ,
242+ ( TestResult :: TrOk , true ) => Err ( RustdocResult :: NoPanic ( None ) ) ,
243+ ( TestResult :: TrFailedMsg ( msg) , true ) => Err ( RustdocResult :: NoPanic ( Some ( msg) ) ) ,
244+ ( TestResult :: TrFailedMsg ( _) | TestResult :: TrFailed , false ) => {
245+ Err ( RustdocResult :: ExecutionFailure ( output) )
246+ }
247+ _ => unreachable ! ( "unexpected status for rustdoc test output" ) ,
248+ }
249+ }
0 commit comments