@@ -30,9 +30,15 @@ use nextest_runner::{
30
30
partition:: PartitionerBuilder ,
31
31
platform:: { BuildPlatforms , HostPlatform , PlatformLibdir , TargetPlatform } ,
32
32
redact:: Redactor ,
33
- reporter:: { structured, FinalStatusLevel , StatusLevel , TestOutputDisplay , TestReporterBuilder } ,
33
+ reporter:: {
34
+ heuristic_extract_description, highlight_end, structured, DescriptionKind ,
35
+ FinalStatusLevel , StatusLevel , TestOutputDisplay , TestReporterBuilder ,
36
+ } ,
34
37
reuse_build:: { archive_to_file, ArchiveReporter , PathMapper , ReuseBuildInfo } ,
35
- runner:: { configure_handle_inheritance, FinalRunStats , RunStatsFailureKind , TestRunnerBuilder } ,
38
+ runner:: {
39
+ configure_handle_inheritance, ExecutionResult , FinalRunStats , RunStatsFailureKind ,
40
+ TestRunnerBuilder ,
41
+ } ,
36
42
show_config:: { ShowNextestVersion , ShowTestGroupSettings , ShowTestGroups , ShowTestGroupsMode } ,
37
43
signal:: SignalHandlerKind ,
38
44
target_runner:: { PlatformRunner , TargetRunner } ,
@@ -42,10 +48,12 @@ use nextest_runner::{
42
48
} ;
43
49
use once_cell:: sync:: OnceCell ;
44
50
use owo_colors:: { OwoColorize , Stream , Style } ;
51
+ use quick_junit:: XmlString ;
45
52
use semver:: Version ;
46
53
use std:: {
47
54
collections:: BTreeSet ,
48
55
env:: VarError ,
56
+ fmt,
49
57
io:: { Cursor , Write } ,
50
58
sync:: Arc ,
51
59
} ;
@@ -170,6 +178,7 @@ impl AppOpts {
170
178
output_writer,
171
179
) ,
172
180
Command :: Self_ { command } => command. exec ( self . common . output ) ,
181
+ Command :: Debug { command } => command. exec ( self . common . output ) ,
173
182
}
174
183
}
175
184
}
@@ -378,6 +387,15 @@ enum Command {
378
387
#[ clap( subcommand) ]
379
388
command : SelfCommand ,
380
389
} ,
390
+ /// Debug commands
391
+ ///
392
+ /// The commands in this section are for nextest's own developers and those integrating with it
393
+ /// to debug issues. They are not part of the public API and may change at any time.
394
+ #[ clap( hide = true ) ]
395
+ Debug {
396
+ #[ clap( subcommand) ]
397
+ command : DebugCommand ,
398
+ } ,
381
399
}
382
400
383
401
#[ derive( Debug , Args ) ]
@@ -1971,6 +1989,172 @@ impl SelfCommand {
1971
1989
}
1972
1990
}
1973
1991
1992
+ #[ derive( Debug , Subcommand ) ]
1993
+ enum DebugCommand {
1994
+ /// Show the data that nextest would extract from standard output or standard error.
1995
+ ///
1996
+ /// Text extraction is a heuristic process driven by a bunch of regexes and other similar logic.
1997
+ /// This command shows what nextest would extract from a given input.
1998
+ Extract {
1999
+ /// The path to the standard output produced by the test process.
2000
+ #[ arg( long, required_unless_present_any = [ "stderr" , "combined" ] ) ]
2001
+ stdout : Option < Utf8PathBuf > ,
2002
+
2003
+ /// The path to the standard error produced by the test process.
2004
+ #[ arg( long, required_unless_present_any = [ "stdout" , "combined" ] ) ]
2005
+ stderr : Option < Utf8PathBuf > ,
2006
+
2007
+ /// The combined output produced by the test process.
2008
+ #[ arg( long, conflicts_with_all = [ "stdout" , "stderr" ] ) ]
2009
+ combined : Option < Utf8PathBuf > ,
2010
+
2011
+ /// The kind of output to produce.
2012
+ #[ arg( value_enum) ]
2013
+ output_format : ExtractOutputFormat ,
2014
+ } ,
2015
+ }
2016
+
2017
+ impl DebugCommand {
2018
+ fn exec ( self , output : OutputOpts ) -> Result < i32 > {
2019
+ let _ = output. init ( ) ;
2020
+
2021
+ match self {
2022
+ DebugCommand :: Extract {
2023
+ stdout,
2024
+ stderr,
2025
+ combined,
2026
+ output_format,
2027
+ } => {
2028
+ // Either stdout + stderr or combined must be present.
2029
+ if let Some ( combined) = combined {
2030
+ let combined = std:: fs:: read ( & combined) . map_err ( |err| {
2031
+ ExpectedError :: DebugExtractReadError {
2032
+ kind : "combined" ,
2033
+ path : combined,
2034
+ err,
2035
+ }
2036
+ } ) ?;
2037
+
2038
+ let description_kind = extract_description ( & combined, & combined) ;
2039
+ display_description_kind ( description_kind, output_format) ?;
2040
+ } else {
2041
+ let stdout = stdout
2042
+ . map ( |path| {
2043
+ std:: fs:: read ( & path) . map_err ( |err| {
2044
+ ExpectedError :: DebugExtractReadError {
2045
+ kind : "stdout" ,
2046
+ path,
2047
+ err,
2048
+ }
2049
+ } )
2050
+ } )
2051
+ . transpose ( ) ?
2052
+ . unwrap_or_default ( ) ;
2053
+ let stderr = stderr
2054
+ . map ( |path| {
2055
+ std:: fs:: read ( & path) . map_err ( |err| {
2056
+ ExpectedError :: DebugExtractReadError {
2057
+ kind : "stderr" ,
2058
+ path,
2059
+ err,
2060
+ }
2061
+ } )
2062
+ } )
2063
+ . transpose ( ) ?
2064
+ . unwrap_or_default ( ) ;
2065
+
2066
+ let description_kind = extract_description ( & stdout, & stderr) ;
2067
+ display_description_kind ( description_kind, output_format) ?;
2068
+ }
2069
+ }
2070
+ }
2071
+
2072
+ Ok ( 0 )
2073
+ }
2074
+ }
2075
+
2076
+ fn extract_description < ' a > ( stdout : & ' a [ u8 ] , stderr : & ' a [ u8 ] ) -> Option < DescriptionKind < ' a > > {
2077
+ // The execution result is a generic one.
2078
+ heuristic_extract_description (
2079
+ ExecutionResult :: Fail {
2080
+ abort_status : None ,
2081
+ leaked : false ,
2082
+ } ,
2083
+ stdout,
2084
+ stderr,
2085
+ )
2086
+ }
2087
+
2088
+ fn display_description_kind (
2089
+ kind : Option < DescriptionKind < ' _ > > ,
2090
+ output_format : ExtractOutputFormat ,
2091
+ ) -> Result < ( ) > {
2092
+ match output_format {
2093
+ ExtractOutputFormat :: Raw => {
2094
+ if let Some ( kind) = kind {
2095
+ if let Some ( out) = kind. combined_subslice ( ) {
2096
+ return std:: io:: stdout ( ) . write_all ( out. slice ) . map_err ( |err| {
2097
+ ExpectedError :: DebugExtractWriteError {
2098
+ format : output_format,
2099
+ err,
2100
+ }
2101
+ } ) ;
2102
+ }
2103
+ }
2104
+ }
2105
+ ExtractOutputFormat :: JunitDescription => {
2106
+ if let Some ( kind) = kind {
2107
+ println ! (
2108
+ "{}" ,
2109
+ XmlString :: new( kind. display_human( ) . to_string( ) ) . as_str( )
2110
+ ) ;
2111
+ }
2112
+ }
2113
+ ExtractOutputFormat :: Highlight => {
2114
+ if let Some ( kind) = kind {
2115
+ if let Some ( out) = kind. combined_subslice ( ) {
2116
+ let end = highlight_end ( out. slice ) ;
2117
+ return std:: io:: stdout ( )
2118
+ . write_all ( & out. slice [ ..end] )
2119
+ . map_err ( |err| ExpectedError :: DebugExtractWriteError {
2120
+ format : output_format,
2121
+ err,
2122
+ } ) ;
2123
+ }
2124
+ }
2125
+ }
2126
+ }
2127
+
2128
+ eprintln ! ( "(no description found)" ) ;
2129
+ Ok ( ( ) )
2130
+ }
2131
+
2132
+ /// Output format for `nextest debug extract`.
2133
+ #[ derive( Clone , Copy , Debug , ValueEnum ) ]
2134
+ pub enum ExtractOutputFormat {
2135
+ /// Show the raw text extracted.
2136
+ Raw ,
2137
+
2138
+ /// Show what would be put in the description field of JUnit reports.
2139
+ ///
2140
+ /// This is similar to `Raw`, but is valid Unicode, and strips out ANSI escape codes and other
2141
+ /// invalid XML characters.
2142
+ JunitDescription ,
2143
+
2144
+ /// Show what would be highlighted in nextest's output.
2145
+ Highlight ,
2146
+ }
2147
+
2148
+ impl fmt:: Display for ExtractOutputFormat {
2149
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
2150
+ match self {
2151
+ Self :: Raw => write ! ( f, "raw" ) ,
2152
+ Self :: JunitDescription => write ! ( f, "junit-description" ) ,
2153
+ Self :: Highlight => write ! ( f, "highlight" ) ,
2154
+ }
2155
+ }
2156
+ }
2157
+
1974
2158
fn acquire_graph_data (
1975
2159
manifest_path : Option < & Utf8Path > ,
1976
2160
target_dir : Option < & Utf8Path > ,
0 commit comments