@@ -848,6 +848,154 @@ func TestSortStdin(t *testing.T) {
848848 }
849849}
850850
851+ // TestRunDefaultCommand_Stdin verifies that runDefaultCommand calls sortStdin
852+ // when the input is set to "-" (stdin) and that sorting succeeds
853+ func TestRunDefaultCommand_Stdin (t * testing.T ) {
854+ // Prepare temporary stdin FASTQ file
855+ tmpInFile , err := os .CreateTemp ("" , "default_cmd_stdin_*.fastq" )
856+ if err != nil {
857+ t .Fatal (err )
858+ }
859+ defer os .Remove (tmpInFile .Name ())
860+
861+ // Two simple records with different qualities so we can verify ordering
862+ fmt .Fprintf (tmpInFile , "@seq1\n ACGT\n +\n IIII\n " ) // high quality
863+ fmt .Fprintf (tmpInFile , "@seq2\n ACGT\n +\n $$$$\n " ) // low quality
864+ tmpInFile .Close ()
865+
866+ // Redirect stdin to the temp file
867+ oldStdin := os .Stdin
868+ newStdin , err := os .Open (tmpInFile .Name ())
869+ if err != nil {
870+ t .Fatal (err )
871+ }
872+ os .Stdin = newStdin
873+ defer func () {
874+ os .Stdin = oldStdin
875+ newStdin .Close ()
876+ }()
877+
878+ // Prepare temporary output file
879+ tmpOutFile , err := os .CreateTemp ("" , "default_cmd_stdout_*.fastq" )
880+ if err != nil {
881+ t .Fatal (err )
882+ }
883+ tmpOutFile .Close ()
884+ defer os .Remove (tmpOutFile .Name ())
885+
886+ // Set global flags used by runDefaultCommand
887+ inFile = "-"
888+ outFile = tmpOutFile .Name ()
889+ metric = "avgphred"
890+ minPhred = DEFAULT_MIN_PHRED
891+ minQualFilter = - math .MaxFloat64
892+ maxQualFilter = math .MaxFloat64
893+ headerMetrics = ""
894+ ascending = false
895+ compLevel = 0
896+ version = false
897+
898+ // Call runDefaultCommand; on success it should not call exitFunc.
899+ // exitFunc is already mocked to panic in TestMain, but since we
900+ // expect no error, we can call directly without a recover wrapper
901+ runDefaultCommand (nil , nil )
902+
903+ // Verify output order (seq1 should come before seq2)
904+ reader , err := fastx .NewReader (seq .DNAredundant , tmpOutFile .Name (), fastx .DefaultIDRegexp )
905+ if err != nil {
906+ t .Fatal (err )
907+ }
908+ defer reader .Close ()
909+
910+ var gotOrder []string
911+ for {
912+ record , err := reader .Read ()
913+ if err == io .EOF {
914+ break
915+ }
916+ if err != nil {
917+ t .Fatal (err )
918+ }
919+ name := strings .Split (string (record .Name ), " " )[0 ]
920+ gotOrder = append (gotOrder , name )
921+ }
922+
923+ wantOrder := []string {"seq1" , "seq2" }
924+ if ! reflect .DeepEqual (gotOrder , wantOrder ) {
925+ t .Errorf ("runDefaultCommand(stdin) produced order %v, want %v" , gotOrder , wantOrder )
926+ }
927+ }
928+
929+ // TestSortStdin_ReadError covers the error path when reading from stdin fails
930+ // inside sortStdin (i.e. the "Error reading record" branch)
931+ func TestSortStdin_ReadError (t * testing.T ) {
932+ // Capture stderr and recover from the expected panic triggered via exitFunc
933+ oldStderr := os .Stderr
934+ rErr , wErr , _ := os .Pipe ()
935+ os .Stderr = wErr
936+
937+ defer func () {
938+ wErr .Close ()
939+ os .Stderr = oldStderr
940+ }()
941+
942+ // Prepare a valid output path (we are testing read errors, not write errors)
943+ tmpDir , err := os .MkdirTemp ("" , "sortstdin_readerr_*" )
944+ if err != nil {
945+ t .Fatal (err )
946+ }
947+ defer os .RemoveAll (tmpDir )
948+ outPath := filepath .Join (tmpDir , "out.fastq" )
949+
950+ // Redirect stdin to a malformed FASTQ file to trigger a read error
951+ tmpInFile , err := os .CreateTemp ("" , "sortstdin_err_in_*.fastq" )
952+ if err != nil {
953+ t .Fatal (err )
954+ }
955+ // Write clearly invalid FASTQ content (not following 4-line record structure)
956+ fmt .Fprintln (tmpInFile , "this_is_not_a_valid_fastq_record" )
957+ tmpInFile .Close ()
958+ defer os .Remove (tmpInFile .Name ())
959+
960+ oldStdin := os .Stdin
961+ newStdin , err := os .Open (tmpInFile .Name ())
962+ if err != nil {
963+ t .Fatal (err )
964+ }
965+ os .Stdin = newStdin
966+ defer func () {
967+ os .Stdin = oldStdin
968+ newStdin .Close ()
969+ }()
970+
971+ // Call sortStdin and expect it to call exitFunc(1), which panics
972+ didPanic := false
973+ func () {
974+ defer func () {
975+ if r := recover (); r != nil {
976+ // exitFunc is configured to panic with "exit <code>"
977+ if s , ok := r .(string ); ok && strings .HasPrefix (s , "exit " ) {
978+ didPanic = true
979+ } else {
980+ t .Fatalf ("Unexpected panic: %v" , r )
981+ }
982+ }
983+ }()
984+
985+ sortStdin (outPath , false , AvgPhred , 0 , nil , DEFAULT_MIN_PHRED , - math .MaxFloat64 , math .MaxFloat64 )
986+ }()
987+
988+ // Read captured stderr
989+ wErr .Close ()
990+ errOutput , _ := io .ReadAll (rErr )
991+
992+ if ! didPanic {
993+ t .Fatalf ("Expected sortStdin to call exitFunc and panic, but it did not" )
994+ }
995+ if ! strings .Contains (string (errOutput ), "Error reading record" ) {
996+ t .Errorf ("Expected stderr to contain 'Error reading record', got: %s" , string (errOutput ))
997+ }
998+ }
851999// TestMainCommand tests the main command functionality
8521000func TestMainCommand (t * testing.T ) {
8531001 // Create temporary directory for test files
0 commit comments