@@ -10,8 +10,12 @@ use std::sync::LazyLock;
1010use std:: sync:: Mutex ;
1111
1212use anyhow:: Context ;
13+ use async_process_traits:: Command ;
14+ use async_process_traits:: CommandSpawner ;
15+ use async_process_traits:: ExitStatus ;
16+ use async_process_traits:: Output ;
17+ use async_process_traits:: TokioCommandSpawner ;
1318use lru_cache:: LruCache ;
14- use tokio:: process:: Command ;
1519
1620use crate :: error:: Result ;
1721use crate :: error:: SaplingError ;
6367 D : AsRef < Path > ,
6468 C : AsRef < str > ,
6569 M : AsRef < str > ,
70+ {
71+ get_mergebase_details_impl ( & TokioCommandSpawner , current_dir, commit, mergegase_with) . await
72+ }
73+
74+ pub async fn get_mergebase_details_impl < Spawner , D , C , M > (
75+ spawner : & Spawner ,
76+ current_dir : D ,
77+ commit : C ,
78+ mergegase_with : M ,
79+ ) -> Result < Option < MergebaseDetails > >
80+ where
81+ Spawner : CommandSpawner ,
82+ D : AsRef < Path > ,
83+ C : AsRef < str > ,
84+ M : AsRef < str > ,
6685{
6786 let lru_key = format ! ( "{}:{}" , commit. as_ref( ) , mergegase_with. as_ref( ) ) ;
6887 {
7493 }
7594
7695 let result = {
77- let output = Command :: new ( get_sapling_executable_path ( ) )
96+ let mut command = Spawner :: Command :: new ( get_sapling_executable_path ( ) ) ;
97+ command
7898 . current_dir ( current_dir)
7999 . envs ( get_sapling_options ( ) )
80100 . args ( [
@@ -84,17 +104,17 @@ where
84104 "{node}\n {date}\n {get(extras, \" global_rev\" )}" ,
85105 "-r" ,
86106 format ! ( "ancestor({}, {})" , commit. as_ref( ) , mergegase_with. as_ref( ) ) . as_str ( ) ,
87- ] )
88- . output ( )
89- . await ?;
107+ ] ) ;
108+ let output = spawner. output ( & mut command) . await ?;
90109
91- if !output. status . success ( ) || !output. stderr . is_empty ( ) {
110+ if !output. status ( ) . success ( ) || !output. stderr ( ) . is_empty ( ) {
92111 Err ( SaplingError :: Other ( format ! (
93112 "Failed to obtain mergebase:\n {}" ,
94- String :: from_utf8( output. stderr) . unwrap_or( "Failed to parse stderr" . to_string( ) )
113+ String :: from_utf8( output. stderr( ) . to_vec( ) )
114+ . unwrap_or( "Failed to parse stderr" . to_string( ) )
95115 ) ) )
96116 } else {
97- parse_mergebase_details ( output. stdout )
117+ parse_mergebase_details ( output. stdout ( ) . to_vec ( ) )
98118 }
99119 } ?;
100120
@@ -132,34 +152,62 @@ fn parse_mergebase_details(mergebase_details: Vec<u8>) -> Result<Option<Mergebas
132152
133153#[ cfg( test) ]
134154mod tests {
135- use super :: * ;
155+ use crate :: mergebase:: * ;
156+ use crate :: utils:: tests:: get_mock_spawner;
157+
158+ // the format is {node}\n{date}\n{global_rev}
159+ const DETAILS : & str = "0000111122223333444455556666777788889999\n 1234567890.012345\n 9876543210" ;
160+ const DETAILS_NO_GLOBAL_REV : & str =
161+ "0000111122223333444455556666777788889999\n 1234567890.012345\n " ;
162+ const COMMIT_ID : & str = "0000111122223333444455556666777788889999" ;
163+ const MERGEBASE_WITH : & str = "master" ;
164+
165+ #[ tokio:: test]
166+ #[ allow( clippy:: unnecessary_literal_unwrap) ]
167+ async fn test_get_mergebase_details ( ) -> Result < ( ) > {
168+ let output = DETAILS . to_owned ( ) ;
169+ let spawner = get_mock_spawner (
170+ get_sapling_executable_path ( ) ,
171+ Some ( ( 0 , Some ( output. as_bytes ( ) . to_vec ( ) ) ) ) ,
172+ ) ;
173+
174+ let current_dir = "." ;
175+ let details =
176+ get_mergebase_details_impl ( & spawner, current_dir, COMMIT_ID , MERGEBASE_WITH ) . await ?;
177+ let expected = Some ( MergebaseDetails {
178+ mergebase : COMMIT_ID . to_owned ( ) ,
179+ timestamp : Some ( 1234567890 ) ,
180+ global_rev : Some ( 9876543210 ) ,
181+ } ) ;
182+
183+ assert_eq ! ( details, expected) ;
184+
185+ // PartialEq is implemented for MergebaseDetails, only comparing the mergebase field
186+ // Unwrap and compare remaining fields one by one
187+ let details = details. unwrap ( ) ;
188+ let expected = expected. unwrap ( ) ;
189+ assert_eq ! ( details. timestamp, expected. timestamp) ;
190+ assert_eq ! ( details. global_rev, expected. global_rev) ;
191+
192+ Ok ( ( ) )
193+ }
136194
137195 #[ test]
138196 fn test_parse_mergebase_details ( ) -> Result < ( ) > {
139- // the format is {node}\n{date}\n{global_rev}
140- let output =
141- "71de423b796418e8ff5300dbe9bd9ad3aef63a9c\n 1739790802.028800\n 1020164040" . to_owned ( ) ;
142- let details = parse_mergebase_details ( output. as_bytes ( ) . to_vec ( ) ) ?. unwrap ( ) ;
143- assert_eq ! (
144- details. mergebase,
145- "71de423b796418e8ff5300dbe9bd9ad3aef63a9c"
146- ) ;
147- assert_eq ! ( details. timestamp, Some ( 1739790802 ) ) ;
148- assert_eq ! ( details. global_rev, Some ( 1020164040 ) ) ;
197+ let details = parse_mergebase_details ( DETAILS . as_bytes ( ) . to_vec ( ) ) ?. unwrap ( ) ;
198+ assert_eq ! ( details. mergebase, COMMIT_ID ) ;
199+ assert_eq ! ( details. timestamp, Some ( 1234567890 ) ) ;
200+ assert_eq ! ( details. global_rev, Some ( 9876543210 ) ) ;
149201 Ok ( ( ) )
150202 }
151203
152204 #[ test]
153205 fn test_parse_mergebase_details_no_global_rev ( ) -> Result < ( ) > {
154206 // Not all repos have global revision
155- let output = "71de423b796418e8ff5300dbe9bd9ad3aef63a9c\n 1739790802.028800\n " . to_owned ( ) ;
156- let details = parse_mergebase_details ( output. as_bytes ( ) . to_vec ( ) ) ?. unwrap ( ) ;
157- assert_eq ! (
158- details. mergebase,
159- "71de423b796418e8ff5300dbe9bd9ad3aef63a9c"
160- ) ;
207+ let details = parse_mergebase_details ( DETAILS_NO_GLOBAL_REV . as_bytes ( ) . to_vec ( ) ) ?. unwrap ( ) ;
208+ assert_eq ! ( details. mergebase, COMMIT_ID ) ;
209+ assert_eq ! ( details. timestamp, Some ( 1234567890 ) ) ;
161210 assert_eq ! ( details. global_rev, None ) ;
162- assert_eq ! ( details. timestamp, Some ( 1739790802 ) ) ;
163211 Ok ( ( ) )
164212 }
165213}
0 commit comments