@@ -3,12 +3,15 @@ package testcontainers
33import (
44 "context"
55 "errors"
6+ "fmt"
67 "os"
78 "strconv"
89 "sync"
10+ "syscall"
911 "testing"
1012 "time"
1113
14+ "github.com/cenkalti/backoff/v4"
1215 "github.com/containerd/errdefs"
1316 "github.com/docker/docker/api/types/container"
1417 "github.com/docker/docker/api/types/network"
@@ -532,6 +535,110 @@ func TestSpawnerBackoff(t *testing.T) {
532535 }
533536}
534537
538+ type timeoutErr struct {}
539+
540+ func (timeoutErr ) Error () string {
541+ return "timeout"
542+ }
543+
544+ func (timeoutErr ) Timeout () bool {
545+ return true
546+ }
547+
548+ func TestSpawnerRetryError (t * testing.T ) {
549+ t .Run ("nil error" , func (t * testing.T ) {
550+ err := spawner .retryError (nil )
551+ require .NoError (t , err , "should return nil" )
552+ })
553+
554+ tests := []struct {
555+ name string
556+ err error
557+ permanent bool
558+ }{
559+ {
560+ name : "docker conflict error" ,
561+ err : errors .New ("Conflict. The container name \" foo\" is already in use by container \" 01234\" ." ),
562+ permanent : false ,
563+ },
564+ {
565+ name : "podman conflict error" ,
566+ err : errors .New ("creating container storage: the container name \" foo\" is already in use by 01234." ),
567+ permanent : false ,
568+ },
569+ {
570+ name : "errdefs.ErrNotFound" ,
571+ err : fmt .Errorf ("foo: %w" , errdefs .ErrNotFound ),
572+ permanent : false ,
573+ },
574+ {
575+ name : "errdefs.Conflict" ,
576+ err : fmt .Errorf ("foo: %w" , errdefs .ErrConflict ),
577+ permanent : true ,
578+ },
579+ {
580+ name : "syscall.ECONNREFUSED" ,
581+ err : fmt .Errorf ("foo: %w" , syscall .ECONNREFUSED ),
582+ permanent : false ,
583+ },
584+ {
585+ name : "syscall.ECONNRESET" ,
586+ err : fmt .Errorf ("foo: %w" , syscall .ECONNRESET ),
587+ permanent : false ,
588+ },
589+ {
590+ name : "syscall.ECONNABORTED" ,
591+ err : fmt .Errorf ("foo: %w" , syscall .ECONNABORTED ),
592+ permanent : false ,
593+ },
594+ {
595+ name : "syscall.ETIMEDOUT" ,
596+ err : fmt .Errorf ("foo: %w" , syscall .ETIMEDOUT ),
597+ permanent : false ,
598+ },
599+ {
600+ name : "os.ErrDeadlineExceeded" ,
601+ err : fmt .Errorf ("foo: %w" , os .ErrDeadlineExceeded ),
602+ permanent : false ,
603+ },
604+ {
605+ name : "context.DeadlineExceeded" ,
606+ err : fmt .Errorf ("foo: %w" , context .DeadlineExceeded ),
607+ permanent : false ,
608+ },
609+ {
610+ name : "timeout error" ,
611+ err : fmt .Errorf ("foo: %w" , timeoutErr {}),
612+ permanent : false ,
613+ },
614+ {
615+ name : "context.Canceled" ,
616+ err : fmt .Errorf ("foo: %w" , context .Canceled ),
617+ permanent : false ,
618+ },
619+ {
620+ name : "random error" ,
621+ err : errors .New ("some random error" ),
622+ permanent : true ,
623+ },
624+ }
625+
626+ for _ , tt := range tests {
627+ t .Run (tt .name , func (t * testing.T ) {
628+ gotErr := spawner .retryError (tt .err )
629+ require .Error (t , gotErr , "should not return nil" )
630+
631+ permanentError := & backoff.PermanentError {}
632+ isPermanent := errors .As (gotErr , & permanentError )
633+ if tt .permanent {
634+ require .True (t , isPermanent , "the error should be a PermanentError" )
635+ } else {
636+ require .False (t , isPermanent , "the error should not be a PermanentError" )
637+ }
638+ })
639+ }
640+ }
641+
535642// cleanupReaper schedules reaper for cleanup if it's not nil.
536643func cleanupReaper (t * testing.T , reaper * Reaper , spawner * reaperSpawner ) {
537644 t .Helper ()
0 commit comments