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