11// FIXME: This is a complete copy of `cargo/src/cargo/util/read2.rs`
22// Consider unify the read2() in libstd, cargo and this to prevent further code duplication.
33
4+ #[ cfg( test) ]
5+ mod tests;
6+
47pub use self :: imp:: read2;
5- use std:: io;
8+ use std:: io:: { self , Write } ;
9+ use std:: mem:: replace;
610use std:: process:: { Child , Output } ;
711
8- pub fn read2_abbreviated ( mut child : Child ) -> io:: Result < Output > {
9- use io:: Write ;
10- use std:: mem:: replace;
11-
12- const HEAD_LEN : usize = 160 * 1024 ;
13- const TAIL_LEN : usize = 256 * 1024 ;
14-
15- enum ProcOutput {
16- Full ( Vec < u8 > ) ,
17- Abbreviated { head : Vec < u8 > , skipped : usize , tail : Box < [ u8 ] > } ,
18- }
19-
20- impl ProcOutput {
21- fn extend ( & mut self , data : & [ u8 ] ) {
22- let new_self = match * self {
23- ProcOutput :: Full ( ref mut bytes) => {
24- bytes. extend_from_slice ( data) ;
25- let new_len = bytes. len ( ) ;
26- if new_len <= HEAD_LEN + TAIL_LEN {
27- return ;
28- }
29- let tail = bytes. split_off ( new_len - TAIL_LEN ) . into_boxed_slice ( ) ;
30- let head = replace ( bytes, Vec :: new ( ) ) ;
31- let skipped = new_len - HEAD_LEN - TAIL_LEN ;
32- ProcOutput :: Abbreviated { head, skipped, tail }
33- }
34- ProcOutput :: Abbreviated { ref mut skipped, ref mut tail, .. } => {
35- * skipped += data. len ( ) ;
36- if data. len ( ) <= TAIL_LEN {
37- tail[ ..data. len ( ) ] . copy_from_slice ( data) ;
38- tail. rotate_left ( data. len ( ) ) ;
39- } else {
40- tail. copy_from_slice ( & data[ ( data. len ( ) - TAIL_LEN ) ..] ) ;
41- }
42- return ;
43- }
44- } ;
45- * self = new_self;
46- }
47-
48- fn into_bytes ( self ) -> Vec < u8 > {
49- match self {
50- ProcOutput :: Full ( bytes) => bytes,
51- ProcOutput :: Abbreviated { mut head, skipped, tail } => {
52- write ! ( & mut head, "\n \n <<<<<< SKIPPED {} BYTES >>>>>>\n \n " , skipped) . unwrap ( ) ;
53- head. extend_from_slice ( & tail) ;
54- head
55- }
56- }
57- }
58- }
59-
60- let mut stdout = ProcOutput :: Full ( Vec :: new ( ) ) ;
61- let mut stderr = ProcOutput :: Full ( Vec :: new ( ) ) ;
12+ pub fn read2_abbreviated ( mut child : Child , filter_paths_from_len : & [ String ] ) -> io:: Result < Output > {
13+ let mut stdout = ProcOutput :: new ( ) ;
14+ let mut stderr = ProcOutput :: new ( ) ;
6215
6316 drop ( child. stdin . take ( ) ) ;
6417 read2 (
6518 child. stdout . take ( ) . unwrap ( ) ,
6619 child. stderr . take ( ) . unwrap ( ) ,
6720 & mut |is_stdout, data, _| {
68- if is_stdout { & mut stdout } else { & mut stderr } . extend ( data) ;
21+ if is_stdout { & mut stdout } else { & mut stderr } . extend ( data, filter_paths_from_len ) ;
6922 data. clear ( ) ;
7023 } ,
7124 ) ?;
@@ -74,6 +27,98 @@ pub fn read2_abbreviated(mut child: Child) -> io::Result<Output> {
7427 Ok ( Output { status, stdout : stdout. into_bytes ( ) , stderr : stderr. into_bytes ( ) } )
7528}
7629
30+ const HEAD_LEN : usize = 160 * 1024 ;
31+ const TAIL_LEN : usize = 256 * 1024 ;
32+
33+ // Whenever a path is filtered when counting the length of the output, we need to add some
34+ // placeholder length to ensure a compiler emitting only filtered paths doesn't cause a OOM.
35+ //
36+ // 32 was chosen semi-arbitrarily: it was the highest power of two that still allowed the test
37+ // suite to pass at the moment of implementing path filtering.
38+ const FILTERED_PATHS_PLACEHOLDER_LEN : usize = 32 ;
39+
40+ enum ProcOutput {
41+ Full { bytes : Vec < u8 > , filtered_len : usize } ,
42+ Abbreviated { head : Vec < u8 > , skipped : usize , tail : Box < [ u8 ] > } ,
43+ }
44+
45+ impl ProcOutput {
46+ fn new ( ) -> Self {
47+ ProcOutput :: Full { bytes : Vec :: new ( ) , filtered_len : 0 }
48+ }
49+
50+ fn extend ( & mut self , data : & [ u8 ] , filter_paths_from_len : & [ String ] ) {
51+ let new_self = match * self {
52+ ProcOutput :: Full { ref mut bytes, ref mut filtered_len } => {
53+ let old_len = bytes. len ( ) ;
54+ bytes. extend_from_slice ( data) ;
55+ * filtered_len += data. len ( ) ;
56+
57+ // We had problems in the past with tests failing only in some environments,
58+ // due to the length of the base path pushing the output size over the limit.
59+ //
60+ // To make those failures deterministic across all environments we ignore known
61+ // paths when calculating the string length, while still including the full
62+ // path in the output. This could result in some output being larger than the
63+ // threshold, but it's better than having nondeterministic failures.
64+ //
65+ // The compiler emitting only excluded strings is addressed by adding a
66+ // placeholder size for each excluded segment, which will eventually reach
67+ // the configured threshold.
68+ for path in filter_paths_from_len {
69+ let path_bytes = path. as_bytes ( ) ;
70+ // We start matching `path_bytes - 1` into the previously loaded data,
71+ // to account for the fact a path_bytes might be included across multiple
72+ // `extend` calls. Starting from `- 1` avoids double-counting paths.
73+ let matches = ( & bytes[ ( old_len. saturating_sub ( path_bytes. len ( ) - 1 ) ) ..] )
74+ . windows ( path_bytes. len ( ) )
75+ . filter ( |window| window == & path_bytes)
76+ . count ( ) ;
77+ * filtered_len -= matches * path_bytes. len ( ) ;
78+
79+ // We can't just remove the length of the filtered path from the output lenght,
80+ // otherwise a compiler emitting only filtered paths would OOM compiletest. Add
81+ // a fixed placeholder length for each path to prevent that.
82+ * filtered_len += matches * FILTERED_PATHS_PLACEHOLDER_LEN ;
83+ }
84+
85+ let new_len = bytes. len ( ) ;
86+ if * filtered_len <= HEAD_LEN + TAIL_LEN {
87+ return ;
88+ }
89+
90+ let mut head = replace ( bytes, Vec :: new ( ) ) ;
91+ let mut middle = head. split_off ( HEAD_LEN ) ;
92+ let tail = middle. split_off ( middle. len ( ) - TAIL_LEN ) . into_boxed_slice ( ) ;
93+ let skipped = new_len - HEAD_LEN - TAIL_LEN ;
94+ ProcOutput :: Abbreviated { head, skipped, tail }
95+ }
96+ ProcOutput :: Abbreviated { ref mut skipped, ref mut tail, .. } => {
97+ * skipped += data. len ( ) ;
98+ if data. len ( ) <= TAIL_LEN {
99+ tail[ ..data. len ( ) ] . copy_from_slice ( data) ;
100+ tail. rotate_left ( data. len ( ) ) ;
101+ } else {
102+ tail. copy_from_slice ( & data[ ( data. len ( ) - TAIL_LEN ) ..] ) ;
103+ }
104+ return ;
105+ }
106+ } ;
107+ * self = new_self;
108+ }
109+
110+ fn into_bytes ( self ) -> Vec < u8 > {
111+ match self {
112+ ProcOutput :: Full { bytes, .. } => bytes,
113+ ProcOutput :: Abbreviated { mut head, skipped, tail } => {
114+ write ! ( & mut head, "\n \n <<<<<< SKIPPED {} BYTES >>>>>>\n \n " , skipped) . unwrap ( ) ;
115+ head. extend_from_slice ( & tail) ;
116+ head
117+ }
118+ }
119+ }
120+ }
121+
77122#[ cfg( not( any( unix, windows) ) ) ]
78123mod imp {
79124 use std:: io:: { self , Read } ;
0 commit comments