@@ -417,3 +417,162 @@ fn write_timing_report(path: &PathBuf, timings: &[StageTiming]) -> Result<(), St
417417 fs:: write ( path, output)
418418 . map_err ( |err| format ! ( "failed to write timing report {}: {err}" , path. display( ) ) )
419419}
420+
421+ #[ cfg( test) ]
422+ mod cli_bootstrap_tests {
423+ use super :: * ;
424+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
425+
426+ fn temp_dir ( name : & str ) -> PathBuf {
427+ let unique = SystemTime :: now ( )
428+ . duration_since ( UNIX_EPOCH )
429+ . unwrap ( )
430+ . as_nanos ( ) ;
431+ let dir = env:: temp_dir ( ) . join ( format ! (
432+ "bioscript-cli-bootstrap-{name}-{}-{unique}" ,
433+ std:: process:: id( )
434+ ) ) ;
435+ fs:: create_dir_all ( & dir) . unwrap ( ) ;
436+ dir
437+ }
438+
439+ #[ test]
440+ fn parse_cli_options_consumes_paths_loader_limits_and_filters ( ) {
441+ let options = parse_cli_options ( vec ! [
442+ "script.bs" . to_owned( ) ,
443+ "--root" . to_owned( ) ,
444+ "root" . to_owned( ) ,
445+ "--input-file" . to_owned( ) ,
446+ "input.txt" . to_owned( ) ,
447+ "--output-file" . to_owned( ) ,
448+ "output.txt" . to_owned( ) ,
449+ "--participant-id" . to_owned( ) ,
450+ "p1" . to_owned( ) ,
451+ "--trace-report" . to_owned( ) ,
452+ "trace.tsv" . to_owned( ) ,
453+ "--timing-report" . to_owned( ) ,
454+ "timing.tsv" . to_owned( ) ,
455+ "--filter" . to_owned( ) ,
456+ "tag=pgx" . to_owned( ) ,
457+ "--cache-dir" . to_owned( ) ,
458+ "cache" . to_owned( ) ,
459+ "--input-format" . to_owned( ) ,
460+ "text" . to_owned( ) ,
461+ "--input-index" . to_owned( ) ,
462+ "input.idx" . to_owned( ) ,
463+ "--reference-file" . to_owned( ) ,
464+ "ref.fa" . to_owned( ) ,
465+ "--reference-index" . to_owned( ) ,
466+ "ref.fa.fai" . to_owned( ) ,
467+ "--max-duration-ms" . to_owned( ) ,
468+ "250" . to_owned( ) ,
469+ "--max-memory-bytes" . to_owned( ) ,
470+ "1024" . to_owned( ) ,
471+ "--max-allocations" . to_owned( ) ,
472+ "2000" . to_owned( ) ,
473+ "--max-recursion-depth" . to_owned( ) ,
474+ "50" . to_owned( ) ,
475+ "--auto-index" . to_owned( ) ,
476+ ] )
477+ . unwrap ( ) ;
478+
479+ assert_eq ! ( options. script_path, Some ( PathBuf :: from( "script.bs" ) ) ) ;
480+ assert_eq ! ( options. root, Some ( PathBuf :: from( "root" ) ) ) ;
481+ assert_eq ! ( options. input_file. as_deref( ) , Some ( "input.txt" ) ) ;
482+ assert_eq ! ( options. output_file. as_deref( ) , Some ( "output.txt" ) ) ;
483+ assert_eq ! ( options. participant_id. as_deref( ) , Some ( "p1" ) ) ;
484+ assert_eq ! ( options. trace_report, Some ( PathBuf :: from( "trace.tsv" ) ) ) ;
485+ assert_eq ! ( options. timing_report, Some ( PathBuf :: from( "timing.tsv" ) ) ) ;
486+ assert_eq ! ( options. filters, vec![ "tag=pgx" ] ) ;
487+ assert_eq ! ( options. cache_dir, Some ( PathBuf :: from( "cache" ) ) ) ;
488+ assert_eq ! ( options. loader. format, Some ( GenotypeSourceFormat :: Text ) ) ;
489+ assert_eq ! ( options. loader. input_index, Some ( PathBuf :: from( "input.idx" ) ) ) ;
490+ assert_eq ! ( options. loader. reference_file, Some ( PathBuf :: from( "ref.fa" ) ) ) ;
491+ assert_eq ! (
492+ options. loader. reference_index,
493+ Some ( PathBuf :: from( "ref.fa.fai" ) )
494+ ) ;
495+ assert ! ( options. auto_index) ;
496+ }
497+
498+ #[ test]
499+ fn parse_cli_options_reports_missing_values_and_unexpected_arguments ( ) {
500+ for ( flag, message) in [
501+ ( "--root" , "--root requires" ) ,
502+ ( "--input-file" , "--input-file requires" ) ,
503+ ( "--output-file" , "--output-file requires" ) ,
504+ ( "--participant-id" , "--participant-id requires" ) ,
505+ ( "--trace-report" , "--trace-report requires" ) ,
506+ ( "--timing-report" , "--timing-report requires" ) ,
507+ ( "--filter" , "--filter requires" ) ,
508+ ( "--cache-dir" , "--cache-dir requires" ) ,
509+ ( "--input-format" , "--input-format requires" ) ,
510+ ( "--input-index" , "--input-index requires" ) ,
511+ ( "--reference-file" , "--reference-file requires" ) ,
512+ ( "--reference-index" , "--reference-index requires" ) ,
513+ ( "--max-duration-ms" , "--max-duration-ms requires" ) ,
514+ ( "--max-memory-bytes" , "--max-memory-bytes requires" ) ,
515+ ( "--max-allocations" , "--max-allocations requires" ) ,
516+ ( "--max-recursion-depth" , "--max-recursion-depth requires" ) ,
517+ ] {
518+ assert ! ( parse_err( vec![ flag. to_owned( ) ] ) . contains( message) ) ;
519+ }
520+ assert ! ( parse_err( vec![
521+ "script.bs" . to_owned( ) ,
522+ "extra.bs" . to_owned( ) ,
523+ ] )
524+ . contains( "unexpected argument" ) ) ;
525+ assert ! ( parse_err( vec![
526+ "--input-format" . to_owned( ) ,
527+ "bad" . to_owned( ) ,
528+ ] )
529+ . contains( "invalid --input-format" ) ) ;
530+ assert ! ( parse_err( vec![
531+ "--max-duration-ms" . to_owned( ) ,
532+ "bad" . to_owned( ) ,
533+ ] )
534+ . contains( "invalid --max-duration-ms" ) ) ;
535+ }
536+
537+ #[ test]
538+ fn write_timing_report_creates_parent_and_sanitizes_tabs ( ) {
539+ let dir = temp_dir ( "timing" ) ;
540+ let path = dir. join ( "nested/timing.tsv" ) ;
541+ write_timing_report (
542+ & path,
543+ & [
544+ StageTiming {
545+ stage : "stage1" . to_owned ( ) ,
546+ duration_ms : 12 ,
547+ detail : "a\t b" . to_owned ( ) ,
548+ } ,
549+ StageTiming {
550+ stage : "stage2" . to_owned ( ) ,
551+ duration_ms : 0 ,
552+ detail : "ok" . to_owned ( ) ,
553+ } ,
554+ ] ,
555+ )
556+ . unwrap ( ) ;
557+ let text = fs:: read_to_string ( & path) . unwrap ( ) ;
558+ assert ! ( text. starts_with( "stage\t duration_ms\t detail\n " ) ) ;
559+ assert ! ( text. contains( "stage1\t 12\t a b" ) ) ;
560+
561+ fs:: remove_dir_all ( dir) . unwrap ( ) ;
562+ }
563+
564+ #[ test]
565+ fn prepare_cli_indexes_noops_when_auto_index_is_disabled ( ) {
566+ let mut options = default_cli_options ( ) ;
567+ let timings = prepare_cli_indexes ( Path :: new ( "." ) , & mut options) . unwrap ( ) ;
568+ assert ! ( timings. is_empty( ) ) ;
569+ assert ! ( options. loader. input_index. is_none( ) ) ;
570+ }
571+
572+ fn parse_err ( args : Vec < String > ) -> String {
573+ match parse_cli_options ( args) {
574+ Ok ( _) => panic ! ( "expected CLI parse to fail" ) ,
575+ Err ( err) => err,
576+ }
577+ }
578+ }
0 commit comments