@@ -6,6 +6,7 @@ package sqlcmd
66import (
77 "bytes"
88 "fmt"
9+ "io"
910 "os"
1011 "strings"
1112 "testing"
@@ -477,11 +478,24 @@ func TestHelpCommand(t *testing.T) {
477478 s .SetOutput (buf )
478479 defer func () { _ = buf .Close () }()
479480
480- err := helpCommand (s , []string {"" }, 1 )
481- assert .NoError (t , err , "helpCommand should not error" )
481+ // helpCommand writes to os.Stdout; capture it with a pipe
482+ captureHelp := func (fn func () error ) (string , error ) {
483+ old := os .Stdout
484+ r , w , pErr := os .Pipe ()
485+ require .NoError (t , pErr )
486+ os .Stdout = w
487+ fnErr := fn ()
488+ _ = w .Close ()
489+ os .Stdout = old
490+ var out bytes.Buffer
491+ _ , _ = io .Copy (& out , r )
492+ _ = r .Close ()
493+ return out .String (), fnErr
494+ }
482495
483- output := buf .buf .String ()
484- // Verify every registered command with a help field appears in output
496+ // Full listing
497+ output , err := captureHelp (func () error { return helpCommand (s , []string {"" }, 1 ) })
498+ assert .NoError (t , err , "helpCommand should not error" )
485499 for name , cmd := range s .Cmd {
486500 if cmd .help != "" {
487501 assert .Contains (t , output , cmd .help ,
@@ -490,32 +504,27 @@ func TestHelpCommand(t *testing.T) {
490504 }
491505
492506 // :HELP <command> should show only that command's help
493- buf .buf .Reset ()
494- err = helpCommand (s , []string {"CONNECT" }, 1 )
507+ output , err = captureHelp (func () error { return helpCommand (s , []string {"CONNECT" }, 1 ) })
495508 assert .NoError (t , err , "helpCommand CONNECT should not error" )
496- output = buf .buf .String ()
497509 assert .Contains (t , output , ":connect" , "HELP CONNECT should show connect help" )
498510 assert .NotContains (t , output , ":exit" , "HELP CONNECT should not show exit help" )
499511
500512 // Case-insensitive lookup
501- buf .buf .Reset ()
502- err = helpCommand (s , []string {"exit" }, 1 )
513+ output , err = captureHelp (func () error { return helpCommand (s , []string {"exit" }, 1 ) })
503514 assert .NoError (t , err , "helpCommand exit should not error" )
504- output = buf .buf .String ()
505515 assert .Contains (t , output , ":exit" , "HELP exit should show exit help" )
506516 assert .NotContains (t , output , ":connect" , "HELP exit should not show connect help" )
507517
508- // Unknown command falls through to full listing
509- buf .buf .Reset ()
510- err = helpCommand (s , []string {"NOSUCHCMD" }, 1 )
511- assert .NoError (t , err , "helpCommand unknown should not error" )
512- output = buf .buf .String ()
513- for name , cmd := range s .Cmd {
514- if cmd .help != "" {
515- assert .Contains (t , output , cmd .help ,
516- "unknown command should show full help, missing %s" , name )
517- }
518- }
518+ // Unknown command returns error
519+ _ , err = captureHelp (func () error { return helpCommand (s , []string {"NOSUCHCMD" }, 1 ) })
520+ assert .Error (t , err , "helpCommand unknown should return error" )
521+ assert .Contains (t , err .Error (), "'NOSUCHCMD' is not a recognized command" )
522+
523+ // Short forms like ED are resolved by the command regex, not by :HELP.
524+ // :HELP uses map keys (EDIT, READFILE, etc.), so :HELP ED is unrecognized.
525+ _ , err = captureHelp (func () error { return helpCommand (s , []string {"ED" }, 1 ) })
526+ assert .Error (t , err , "helpCommand ED should error (not a map key)" )
527+ assert .Contains (t , err .Error (), "'ED' is not a recognized command" )
519528}
520529
521530func TestAllCommandsHaveHelp (t * testing.T ) {
@@ -564,3 +573,34 @@ func TestPerftraceCommand(t *testing.T) {
564573 assert .NoError (t , err , "perftraceCommand with a variable" )
565574 assert .Equal (t , os .Stdout , s .GetStat (), "stat set to stdout using a variable" )
566575}
576+
577+ func TestPerftraceCloseChain (t * testing.T ) {
578+ s := New (nil , "" , InitializeVariables (false ))
579+ buf := & memoryBuffer {buf : new (bytes.Buffer )}
580+ s .SetOutput (buf )
581+ defer func () { _ = buf .Close () }()
582+
583+ file1 , err := os .CreateTemp ("" , "sqlcmdperf1" )
584+ assert .NoError (t , err )
585+ defer func () { _ = os .Remove (file1 .Name ()) }()
586+ _ = file1 .Close ()
587+
588+ file2 , err := os .CreateTemp ("" , "sqlcmdperf2" )
589+ assert .NoError (t , err )
590+ defer func () { _ = os .Remove (file2 .Name ()) }()
591+ _ = file2 .Close ()
592+
593+ // Redirect to file1, then to file2 -- file1 should be closed
594+ err = perftraceCommand (s , []string {file1 .Name ()}, 1 )
595+ assert .NoError (t , err )
596+ err = perftraceCommand (s , []string {file2 .Name ()}, 1 )
597+ assert .NoError (t , err )
598+
599+ // file1 should be closed: a second close returns an error
600+ f1 , err := os .Open (file1 .Name ())
601+ assert .NoError (t , err , "file1 should be reopenable after close" )
602+ _ = f1 .Close ()
603+
604+ // Clean up
605+ s .SetStat (nil )
606+ }
0 commit comments