11use build_fs_tree:: { dir, file, Build , MergeableFileSystemTree } ;
2+ use command_extra:: CommandExtra ;
23use derive_more:: { AsRef , Deref } ;
34use parallel_disk_usage:: {
45 data_tree:: { DataTree , DataTreeReflection } ,
@@ -16,6 +17,7 @@ use std::{
1617 fs:: { create_dir, metadata, remove_dir_all, Metadata } ,
1718 io:: Error ,
1819 path:: { Path , PathBuf } ,
20+ process:: { Command , Output } ,
1921} ;
2022
2123/// Representation of a temporary filesystem item.
@@ -42,6 +44,7 @@ impl Temp {
4244}
4345
4446impl Drop for Temp {
47+ /// Delete the created temporary directory.
4548 fn drop ( & mut self ) {
4649 let path = & self . 0 ;
4750 if let Err ( error) = remove_dir_all ( path) {
@@ -55,6 +58,7 @@ impl Drop for Temp {
5558pub struct SampleWorkspace ( Temp ) ;
5659
5760impl Default for SampleWorkspace {
61+ /// Set up a temporary directory for tests.
5862 fn default ( ) -> Self {
5963 let temp = Temp :: new_dir ( ) . expect ( "create working directory for sample workspace" ) ;
6064
@@ -202,3 +206,116 @@ where
202206 } ) ,
203207 ) ;
204208}
209+
210+ /// Path to the `pdu` executable
211+ pub const PDU : & str = env ! ( "CARGO_BIN_EXE_pdu" ) ;
212+
213+ /// Representation of a `pdu` command.
214+ #[ derive( Debug , Default , Clone ) ]
215+ pub struct CommandRepresentation < ' a > {
216+ args : Vec < & ' a str > ,
217+ }
218+
219+ impl < ' a > CommandRepresentation < ' a > {
220+ /// Add an argument.
221+ pub fn arg ( mut self , arg : & ' a str ) -> Self {
222+ self . args . push ( arg) ;
223+ self
224+ }
225+ }
226+
227+ /// List of `pdu` commands.
228+ #[ derive( Debug , Clone , AsRef , Deref ) ]
229+ pub struct CommandList < ' a > ( Vec < CommandRepresentation < ' a > > ) ;
230+
231+ impl < ' a > Default for CommandList < ' a > {
232+ /// Initialize a list with one `pdu` command.
233+ fn default ( ) -> Self {
234+ CommandRepresentation :: default ( )
235+ . pipe ( |x| vec ! [ x] )
236+ . pipe ( CommandList )
237+ }
238+ }
239+
240+ impl < ' a > CommandList < ' a > {
241+ /// Duplicate the list with a flag argument.
242+ ///
243+ /// The resulting list would include the original list with the flag
244+ /// followed by the original list without the flag.
245+ pub fn flag_matrix ( self , name : & ' a str ) -> Self {
246+ Self :: assert_flag ( name) ;
247+ let CommandList ( list) = self ;
248+ list. clone ( )
249+ . into_iter ( )
250+ . map ( |cmd| cmd. arg ( name) )
251+ . chain ( list)
252+ . collect :: < Vec < _ > > ( )
253+ . pipe ( CommandList )
254+ }
255+
256+ /// Duplicate the list with one or many option argument(s).
257+ ///
258+ /// The resulting list would include the original list with the option(s)
259+ /// followed by the original list without the option(s).
260+ pub fn option_matrix < const LEN : usize > ( self , name : & ' a str , values : [ & ' a str ; LEN ] ) -> Self {
261+ Self :: assert_flag ( name) ;
262+ let CommandList ( tail) = self ;
263+ let mut head: Vec < _ > = values
264+ . iter ( )
265+ . copied ( )
266+ . flat_map ( |value| {
267+ tail. clone ( )
268+ . into_iter ( )
269+ . map ( move |cmd| cmd. arg ( name) . arg ( value) )
270+ } )
271+ . collect ( ) ;
272+ head. extend ( tail) ;
273+ CommandList ( head)
274+ }
275+
276+ /// Create a list of `pdu` [command](Command).
277+ pub fn commands ( & ' a self ) -> impl Iterator < Item = Command > + ' a {
278+ self . iter ( )
279+ . map ( |cmd| Command :: new ( PDU ) . with_args ( & cmd. args ) )
280+ }
281+
282+ /// Make sure a flag name has valid syntax.
283+ fn assert_flag ( name : & str ) {
284+ match name. len ( ) {
285+ 0 | 1 => panic ! ( "{:?} is not a valid flag" , name) ,
286+ 2 => assert ! ( name. starts_with( '-' ) , "{:?} is not a valid flag" , name) ,
287+ _ => assert ! ( name. starts_with( "--" ) , "{:?} is not a valid flag" , name) ,
288+ }
289+ }
290+ }
291+
292+ /// Make sure that status code is 0, print stderr if it's not empty,
293+ /// and turn stdin into a string.
294+ pub fn stdout_text (
295+ Output {
296+ status,
297+ stdout,
298+ stderr,
299+ } : Output ,
300+ ) -> String {
301+ inspect_stderr ( & stderr) ;
302+ assert ! (
303+ status. success( ) ,
304+ "progress exits with non-zero status: {:?}" ,
305+ status
306+ ) ;
307+ stdout
308+ . pipe ( String :: from_utf8)
309+ . expect ( "parse stdout as UTF-8" )
310+ . trim_end ( )
311+ . to_string ( )
312+ }
313+
314+ /// Print stderr if it's not empty.
315+ pub fn inspect_stderr ( stderr : & [ u8 ] ) {
316+ let text = String :: from_utf8_lossy ( stderr) ;
317+ let text = text. trim ( ) ;
318+ if !text. is_empty ( ) {
319+ eprintln ! ( "STDERR:\n {}\n " , text) ;
320+ }
321+ }
0 commit comments