88 "os/exec"
99 "os/signal"
1010 "strings"
11+ "sync/atomic"
12+ "syscall"
1113
1214 "github.com/dnephin/pflag"
1315 "github.com/fatih/color"
@@ -46,8 +48,8 @@ func setupFlags(name string) (*pflag.FlagSet, *options) {
4648 junitTestCaseClassnameFormat : & junitFieldFormatValue {},
4749 junitTestSuiteNameFormat : & junitFieldFormatValue {},
4850 postRunHookCmd : & commandValue {},
49- stdout : os . Stdout ,
50- stderr : os . Stderr ,
51+ stdout : color . Output ,
52+ stderr : color . Error ,
5153 }
5254 flags := pflag .NewFlagSet (name , pflag .ContinueOnError )
5355 flags .SetInterspersed (false )
@@ -65,7 +67,7 @@ func setupFlags(name string) (*pflag.FlagSet, *options) {
6567 flags .StringVar (& opts .jsonFile , "jsonfile" ,
6668 lookEnvWithDefault ("GOTESTSUM_JSONFILE" , "" ),
6769 "write all TestEvents to file" )
68- flags .BoolVar (& opts .noColor , "no-color" , color . NoColor , "disable color output" )
70+ flags .BoolVar (& opts .noColor , "no-color" , defaultNoColor , "disable color output" )
6971
7072 flags .Var (opts .hideSummary , "no-summary" ,
7173 "do not print summary of: " + testjson .SummarizeAll .String ())
@@ -126,6 +128,7 @@ Formats:
126128
127129Commands:
128130 tool tools for working with test2json output
131+ help print this help next
129132` )
130133}
131134
@@ -172,6 +175,13 @@ func (o options) Validate() error {
172175 return nil
173176}
174177
178+ var defaultNoColor = func () bool {
179+ if os .Getenv ("GITHUB_ACTIONS" ) == "true" {
180+ return false
181+ }
182+ return color .NoColor
183+ }()
184+
175185func setupLogging (opts * options ) {
176186 if opts .debug {
177187 log .SetLevel (log .DebugLevel )
@@ -209,6 +219,9 @@ func run(opts *options) error {
209219 return finishRun (opts , exec , err )
210220 }
211221 exitErr := goTestProc .cmd .Wait ()
222+ if signum := atomic .LoadInt32 (& goTestProc .signal ); signum != 0 {
223+ return finishRun (opts , exec , exitError {num : signalExitCode + int (signum )})
224+ }
212225 if exitErr == nil || opts .rerunFailsMaxAttempts == 0 {
213226 return finishRun (opts , exec , exitErr )
214227 }
@@ -335,38 +348,42 @@ type proc struct {
335348 cmd waiter
336349 stdout io.Reader
337350 stderr io.Reader
351+ // signal is atomically set to the signal value when a signal is received
352+ // by newSignalHandler.
353+ signal int32
338354}
339355
340356type waiter interface {
341357 Wait () error
342358}
343359
344- func startGoTest (ctx context.Context , args []string ) (proc , error ) {
360+ func startGoTest (ctx context.Context , args []string ) (* proc , error ) {
345361 if len (args ) == 0 {
346- return proc {} , errors .New ("missing command to run" )
362+ return nil , errors .New ("missing command to run" )
347363 }
348364
349365 cmd := exec .CommandContext (ctx , args [0 ], args [1 :]... )
366+ cmd .Stdin = os .Stdin
350367 p := proc {cmd : cmd }
351368 log .Debugf ("exec: %s" , cmd .Args )
352369 var err error
353370 p .stdout , err = cmd .StdoutPipe ()
354371 if err != nil {
355- return p , err
372+ return nil , err
356373 }
357374 p .stderr , err = cmd .StderrPipe ()
358375 if err != nil {
359- return p , err
376+ return nil , err
360377 }
361378 if err := cmd .Start (); err != nil {
362- return p , errors .Wrapf (err , "failed to run %s" , strings .Join (cmd .Args , " " ))
379+ return nil , errors .Wrapf (err , "failed to run %s" , strings .Join (cmd .Args , " " ))
363380 }
364381 log .Debugf ("go test pid: %d" , cmd .Process .Pid )
365382
366383 ctx , cancel := context .WithCancel (ctx )
367- newSignalHandler (ctx , cmd .Process .Pid )
384+ newSignalHandler (ctx , cmd .Process .Pid , & p )
368385 p .cmd = & cancelWaiter {cancel : cancel , wrapped : p .cmd }
369- return p , nil
386+ return & p , nil
370387}
371388
372389// ExitCodeWithDefault returns the ExitStatus of a process from the error returned by
@@ -387,12 +404,28 @@ type exitCoder interface {
387404 ExitCode () int
388405}
389406
390- func isExitCoder (err error ) bool {
407+ func IsExitCoder (err error ) bool {
391408 _ , ok := err .(exitCoder )
392409 return ok
393410}
394411
395- func newSignalHandler (ctx context.Context , pid int ) {
412+ type exitError struct {
413+ num int
414+ }
415+
416+ func (e exitError ) Error () string {
417+ return fmt .Sprintf ("exit code %d" , e .num )
418+ }
419+
420+ func (e exitError ) ExitCode () int {
421+ return e .num
422+ }
423+
424+ // signalExitCode is the base value added to a signal number to produce the
425+ // exit code value. This matches the behaviour of bash.
426+ const signalExitCode = 128
427+
428+ func newSignalHandler (ctx context.Context , pid int , p * proc ) {
396429 c := make (chan os.Signal , 1 )
397430 signal .Notify (c , os .Interrupt )
398431
@@ -403,6 +436,8 @@ func newSignalHandler(ctx context.Context, pid int) {
403436 case <- ctx .Done ():
404437 return
405438 case s := <- c :
439+ atomic .StoreInt32 (& p .signal , int32 (s .(syscall.Signal )))
440+
406441 proc , err := os .FindProcess (pid )
407442 if err != nil {
408443 log .Errorf ("failed to find pid of 'go test': %v" , err )
0 commit comments