Skip to content

Commit c4e345e

Browse files
authored
feat: improve container conflict detection (#3574)
Modify the container conflict detection regex for improved Podman compatibility.
1 parent be25c17 commit c4e345e

2 files changed

Lines changed: 108 additions & 1 deletion

File tree

docker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const (
5454

5555
var (
5656
// createContainerFailDueToNameConflictRegex is a regular expression that matches the container is already in use error.
57-
createContainerFailDueToNameConflictRegex = regexp.MustCompile("Conflict. The container name .* is already in use by container .*")
57+
createContainerFailDueToNameConflictRegex = regexp.MustCompile("[Tt]he container name .* is already in use by .*")
5858

5959
// minLogProductionTimeout is the minimum log production timeout.
6060
minLogProductionTimeout = time.Duration(5 * time.Second)

reaper_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package testcontainers
33
import (
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.
536643
func cleanupReaper(t *testing.T, reaper *Reaper, spawner *reaperSpawner) {
537644
t.Helper()

0 commit comments

Comments
 (0)