diff --git a/compose_test.go b/compose_test.go index 3045e7b79c..a4eda172e0 100644 --- a/compose_test.go +++ b/compose_test.go @@ -106,6 +106,7 @@ func TestLocalDockerCompose(t *testing.T) { Invoke() checkIfError(t, err) } + func TestDockerComposeStrategyForInvalidService(t *testing.T) { path := "./testresources/docker-compose-simple.yml" @@ -121,7 +122,9 @@ func TestDockerComposeStrategyForInvalidService(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). // Appending with _1 as given in the Java Test-Containers Example - WithExposedService("mysql_1", 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WithExposedService("mysql_1", 13306, wait.NewLogStrategy("started"). + WithTimeout(10*time.Second). + WithOccurrence(1)). Invoke() assert.NotEqual(t, err.Error, nil, "Expected error to be thrown because service with wait strategy is not running") @@ -144,7 +147,9 @@ func TestDockerComposeWithWaitLogStrategy(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). // Appending with _1 as given in the Java Test-Containers Example - WithExposedService("mysql_1", 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WithExposedService("mysql_1", 13306, wait.NewLogStrategy("started"). + WithTimeout(10*time.Second). + WithOccurrence(1)). Invoke() checkIfError(t, err) @@ -170,7 +175,9 @@ func TestDockerComposeWithWaitHTTPStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WithExposedService("nginx_1", 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WithExposedService("nginx_1", 9080, wait.NewHTTPStrategy("/"). + WithPort("80/tcp"). + WithTimeout(10*time.Second)). Invoke() checkIfError(t, err) @@ -214,8 +221,11 @@ func TestDockerComposeWithMultipleWaitStrategies(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). - WithExposedService("mysql_1", 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). - WithExposedService("nginx_1", 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WithExposedService("mysql_1", 13306, wait.NewLogStrategy("started"). + WithTimeout(10*time.Second)). + WithExposedService("nginx_1", 9080, wait.NewHTTPStrategy("/"). + WithPort("80/tcp"). + WithTimeout(10*time.Second)). Invoke() checkIfError(t, err) @@ -241,7 +251,9 @@ func TestDockerComposeWithFailedStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WithExposedService("nginx_1", 9080, wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). + WithExposedService("nginx_1", 9080, wait.NewHTTPStrategy("/"). + WithPort("8080/tcp"). + WithTimeout(5*time.Second)). Invoke() // Verify that an error is thrown and not nil // A specific error message matcher is not asserted since the docker library can change the return message, breaking this test diff --git a/container_test.go b/container_test.go index 6c140e104d..e666683d94 100644 --- a/container_test.go +++ b/container_test.go @@ -16,7 +16,6 @@ import ( ) func Test_ContainerValidation(t *testing.T) { - type ContainerValidationTestCase struct { Name string ExpectedError error @@ -66,7 +65,6 @@ func Test_ContainerValidation(t *testing.T) { } }) } - } func Test_GetDockerfile(t *testing.T) { @@ -247,7 +245,8 @@ func Test_BuildImageWithContexts(t *testing.T) { Context: testCase.ContextPath, Dockerfile: testCase.Dockerfile, }, - WaitingFor: wait.ForLog(testCase.ExpectedEchoOutput).WithStartupTimeout(1 * time.Minute), + WaitingFor: wait.ForLog(testCase.ExpectedEchoOutput). + WithTimeout(1 * time.Minute), } c, err := GenericContainer(ctx, GenericContainerRequest{ @@ -263,18 +262,17 @@ func Test_BuildImageWithContexts(t *testing.T) { } else { c.Terminate(ctx) } - }) - } } func Test_GetLogsFromFailedContainer(t *testing.T) { ctx := context.Background() req := ContainerRequest{ - Image: "alpine", - Cmd: []string{"echo", "-n", "I was not expecting this"}, - WaitingFor: wait.ForLog("I was expecting this").WithStartupTimeout(5 * time.Second), + Image: "alpine", + Cmd: []string{"echo", "-n", "I was not expecting this"}, + WaitingFor: wait.ForLog("I was expecting this"). + WithTimeout(5 * time.Second), } c, err := GenericContainer(ctx, GenericContainerRequest{ diff --git a/docker_test.go b/docker_test.go index 3de0994687..b55c4e37cf 100644 --- a/docker_test.go +++ b/docker_test.go @@ -2,10 +2,10 @@ package testcontainers import ( "context" + "database/sql" "encoding/json" "errors" "fmt" - "github.com/stretchr/testify/assert" "io/ioutil" "math/rand" "net/http" @@ -16,19 +16,17 @@ import ( "testing" "time" - "github.com/docker/docker/errdefs" - - "github.com/docker/docker/api/types/volume" - - "database/sql" // Import mysql into the scope of this package (required) _ "github.com/go-sql-driver/mysql" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" + "github.com/docker/docker/errdefs" "github.com/docker/go-connections/nat" "github.com/go-redis/redis" + "github.com/stretchr/testify/assert" "github.com/testcontainers/testcontainers-go/wait" ) @@ -58,7 +56,6 @@ func TestContainerAttachedToNewNetwork(t *testing.T) { CheckDuplicate: true, }, }) - if err != nil { t.Fatal(err) } @@ -694,7 +691,8 @@ func TestContainerCreationTimesOut(t *testing.T) { ExposedPorts: []string{ "80/tcp", }, - WaitingFor: wait.ForListeningPort("80").WithStartupTimeout(1 * time.Second), + WaitingFor: wait.ForListeningPort("80"). + WithTimeout(1 * time.Second), }, Started: true, }) @@ -756,7 +754,8 @@ func TestContainerCreationTimesOutWithHttp(t *testing.T) { ExposedPorts: []string{ "80/tcp", }, - WaitingFor: wait.ForHTTP("/").WithStartupTimeout(1 * time.Second), + WaitingFor: wait.ForHTTP("/"). + WithTimeout(1 * time.Second), }, Started: true, }) @@ -781,7 +780,8 @@ func TestContainerCreationWaitsForLogContextTimeout(t *testing.T) { "MYSQL_ROOT_PASSWORD": "password", "MYSQL_DATABASE": "database", }, - WaitingFor: wait.ForLog("test context timeout").WithStartupTimeout(1 * time.Second), + WaitingFor: wait.ForLog("test context timeout"). + WithTimeout(1 * time.Second), } _, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, @@ -1024,7 +1024,6 @@ func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) { if err == nil { t.Fatal("Expected timeout") } - } func TestContainerCreationWaitingForHostPort(t *testing.T) { @@ -1092,7 +1091,6 @@ func TestContainerCreationWaitsForLogAndPort(t *testing.T) { ContainerRequest: req, Started: true, }) - if err != nil { t.Fatal(err) } @@ -1112,7 +1110,6 @@ func TestContainerCreationWaitsForLogAndPort(t *testing.T) { "root", "password", host, port, "database") db, err := sql.Open("mysql", connectionString) - if err != nil { t.Fatal(err) } @@ -1122,7 +1119,6 @@ func TestContainerCreationWaitsForLogAndPort(t *testing.T) { if err = db.Ping(); err != nil { t.Errorf("error pinging db: %+v\n", err) } - } func TestCMD(t *testing.T) { @@ -1146,7 +1142,6 @@ func TestCMD(t *testing.T) { ContainerRequest: req, Started: true, }) - if err != nil { t.Fatal(err) } @@ -1176,7 +1171,6 @@ func TestEntrypoint(t *testing.T) { ContainerRequest: req, Started: true, }) - if err != nil { t.Fatal(err) } @@ -1322,7 +1316,7 @@ func TestContainerWithTmpFs(t *testing.T) { } }() - var path = "/testtmpfs/test.file" + path := "/testtmpfs/test.file" c, err := container.Exec(ctx, []string{"ls", path}) if err != nil { diff --git a/network_test.go b/network_test.go index ab136b86b0..b0b9b32e3e 100644 --- a/network_test.go +++ b/network_test.go @@ -3,9 +3,10 @@ package testcontainers import ( "context" "fmt" - "github.com/testcontainers/testcontainers-go/wait" "testing" "time" + + "github.com/testcontainers/testcontainers-go/wait" ) // Create a network using a provider. By default it is Docker. @@ -60,7 +61,6 @@ func Test_MultipleContainersInTheNewNetwork(t *testing.T) { net, err := GenericNetwork(ctx, GenericNetworkRequest{ NetworkRequest: networkRequest, }) - if err != nil { t.Fatal("cannot create network") } @@ -82,7 +82,7 @@ func Test_MultipleContainersInTheNewNetwork(t *testing.T) { env["RABBITMQ_DEFAULT_USER"] = "admin" env["RABBITMQ_DEFAULT_PASS"] = "Password1" hp := wait.ForListeningPort("5672/tcp") - hp.WithStartupTimeout(3 * time.Minute) + hp.WithTimeout(3 * time.Minute) amqpRequest := ContainerRequest{ Image: "rabbitmq:management-alpine", ExposedPorts: []string{"15672/tcp", "5672/tcp"}, diff --git a/wait/health.go b/wait/health.go index 73885c2e81..caba792b07 100644 --- a/wait/health.go +++ b/wait/health.go @@ -10,8 +10,8 @@ var _ Strategy = (*HealthStrategy)(nil) // HealthStrategy will wait until the container becomes healthy type HealthStrategy struct { - // all Strategies should have a startupTimeout to avoid waiting infinitely - startupTimeout time.Duration + // all Strategies should have a timeout to avoid waiting infinitely + timeout time.Duration // additional properties PollInterval time.Duration @@ -20,10 +20,9 @@ type HealthStrategy struct { // NewHealthStrategy constructs with polling interval of 100 milliseconds and startup timeout of 60 seconds by default func NewHealthStrategy() *HealthStrategy { return &HealthStrategy{ - startupTimeout: defaultStartupTimeout(), - PollInterval: defaultPollInterval(), + timeout: defaultTimeout(), + PollInterval: defaultPollInterval(), } - } // fluent builders for each property @@ -31,8 +30,15 @@ func NewHealthStrategy() *HealthStrategy { // this is true for all properties, even the "shared" ones like startupTimeout // WithStartupTimeout can be used to change the default startup timeout -func (ws *HealthStrategy) WithStartupTimeout(startupTimeout time.Duration) *HealthStrategy { - ws.startupTimeout = startupTimeout +// +// Deprecated: use WithTimeout instead +func (ws *HealthStrategy) WithStartupTimeout(timeout time.Duration) *HealthStrategy { + return ws.WithTimeout(timeout) +} + +// WithTimeout can be used to change the default startup timeout +func (ws *HealthStrategy) WithTimeout(timeout time.Duration) *HealthStrategy { + ws.timeout = timeout return ws } @@ -55,7 +61,7 @@ func ForHealthCheck() *HealthStrategy { // WaitUntilReady implements Strategy.WaitUntilReady func (ws *HealthStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) { // limit context to exitTimeout - ctx, cancelContext := context.WithTimeout(ctx, ws.startupTimeout) + ctx, cancelContext := context.WithTimeout(ctx, ws.timeout) defer cancelContext() for { diff --git a/wait/host_port.go b/wait/host_port.go index d862e9dcc4..4c51c626bd 100644 --- a/wait/host_port.go +++ b/wait/host_port.go @@ -18,21 +18,21 @@ var _ Strategy = (*HostPortStrategy)(nil) type HostPortStrategy struct { Port nat.Port - // all WaitStrategies should have a startupTimeout to avoid waiting infinitely - startupTimeout time.Duration + // all WaitStrategies should have a timeout to avoid waiting infinitely + timeout time.Duration } // NewHostPortStrategy constructs a default host port strategy func NewHostPortStrategy(port nat.Port) *HostPortStrategy { return &HostPortStrategy{ - Port: port, - startupTimeout: defaultStartupTimeout(), + Port: port, + timeout: defaultTimeout(), } } // fluent builders for each property // since go has neither covariance nor generics, the return type must be the type of the concrete implementation -// this is true for all properties, even the "shared" ones like startupTimeout +// this is true for all properties, even the "shared" ones like timeout // ForListeningPort is a helper similar to those in Wait.java // https://github.com/testcontainers/testcontainers-java/blob/1d85a3834bd937f80aad3a4cec249c027f31aeb4/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java @@ -40,15 +40,23 @@ func ForListeningPort(port nat.Port) *HostPortStrategy { return NewHostPortStrategy(port) } -func (hp *HostPortStrategy) WithStartupTimeout(startupTimeout time.Duration) *HostPortStrategy { - hp.startupTimeout = startupTimeout - return hp +// WithStartupTimeout can be used to change the default startup timeout +// +// Deprecated: use WithTimeout instead +func (s *HostPortStrategy) WithStartupTimeout(timeout time.Duration) *HostPortStrategy { + return s.WithTimeout(timeout) +} + +// WithTimeout can be used to change the default startup timeout +func (s *HostPortStrategy) WithTimeout(timeout time.Duration) *HostPortStrategy { + s.timeout = timeout + return s } // WaitUntilReady implements Strategy.WaitUntilReady func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) { - // limit context to startupTimeout - ctx, cancelContext := context.WithTimeout(ctx, hp.startupTimeout) + // limit context to timeout + ctx, cancelContext := context.WithTimeout(ctx, hp.timeout) defer cancelContext() ipAddress, err := target.Host(ctx) @@ -56,11 +64,11 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT return } - var waitInterval = 100 * time.Millisecond + waitInterval := 100 * time.Millisecond var port nat.Port port, err = target.MappedPort(ctx, hp.Port) - var i = 0 + i := 0 for port == "" { i++ @@ -80,7 +88,7 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT portNumber := port.Int() portString := strconv.Itoa(portNumber) - //external check + // external check dialer := net.Dialer{} address := net.JoinHostPort(ipAddress, portString) for { @@ -101,7 +109,7 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT } } - //internal check + // internal check command := buildInternalCheckCommand(hp.Port.Int()) for { if ctx.Err() != nil { diff --git a/wait/host_port_test.go b/wait/host_port_test.go new file mode 100644 index 0000000000..ddb0698268 --- /dev/null +++ b/wait/host_port_test.go @@ -0,0 +1,23 @@ +package wait + +import ( + "testing" + "time" + + "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" +) + +func TestWaitHostPort_TimeoutAccessors(t *testing.T) { + strategy := ForListeningPort(nat.Port("8080")) + + strategy.timeout = time.Second * 2 + assert.Equal(t, time.Second*2, strategy.timeout) + + strategy.WithTimeout(time.Second * 3) + assert.Equal(t, time.Second*3, strategy.timeout) + + // Deprecated + strategy.WithStartupTimeout(time.Second * 4) + assert.Equal(t, time.Second*4, strategy.timeout) +} diff --git a/wait/http.go b/wait/http.go index f882dca1a6..a9b2856250 100644 --- a/wait/http.go +++ b/wait/http.go @@ -18,8 +18,8 @@ import ( var _ Strategy = (*HTTPStrategy)(nil) type HTTPStrategy struct { - // all Strategies should have a startupTimeout to avoid waiting infinitely - startupTimeout time.Duration + // all Strategies should have a timeout to avoid waiting infinitely + timeout time.Duration // additional properties Port nat.Port @@ -37,7 +37,7 @@ type HTTPStrategy struct { // NewHTTPStrategy constructs a HTTP strategy waiting on port 80 and status code 200 func NewHTTPStrategy(path string) *HTTPStrategy { return &HTTPStrategy{ - startupTimeout: defaultStartupTimeout(), + timeout: defaultTimeout(), Port: "80/tcp", Path: path, StatusCodeMatcher: defaultStatusCodeMatcher, @@ -56,11 +56,19 @@ func defaultStatusCodeMatcher(status int) bool { // fluent builders for each property // since go has neither covariance nor generics, the return type must be the type of the concrete implementation -// this is true for all properties, even the "shared" ones like startupTimeout +// this is true for all properties, even the "shared" ones like timeout -func (ws *HTTPStrategy) WithStartupTimeout(startupTimeout time.Duration) *HTTPStrategy { - ws.startupTimeout = startupTimeout - return ws +// WithStartupTimeout can be used to change the default startup timeout +// +// Deprecated: use WithTimeout instead +func (s *HTTPStrategy) WithStartupTimeout(timeout time.Duration) *HTTPStrategy { + return s.WithTimeout(timeout) +} + +// WithTimeout can be used to change the default startup timeout +func (s *HTTPStrategy) WithTimeout(timeout time.Duration) *HTTPStrategy { + s.timeout = timeout + return s } func (ws *HTTPStrategy) WithPort(port nat.Port) *HTTPStrategy { @@ -115,8 +123,8 @@ func ForHTTP(path string) *HTTPStrategy { // WaitUntilReady implements Strategy.WaitUntilReady func (ws *HTTPStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) { - // limit context to startupTimeout - ctx, cancelContext := context.WithTimeout(ctx, ws.startupTimeout) + // limit context to timeout + ctx, cancelContext := context.WithTimeout(ctx, ws.timeout) defer cancelContext() ipAddress, err := target.Host(ctx) diff --git a/wait/http_test.go b/wait/http_test.go index a3a34431e1..e439c742b7 100644 --- a/wait/http_test.go +++ b/wait/http_test.go @@ -1,123 +1,22 @@ -package wait_test +package wait import ( - "bytes" - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "os" "testing" "time" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" + "github.com/stretchr/testify/assert" ) -// -// https://github.com/testcontainers/testcontainers-go/issues/183 -func ExampleHTTPStrategy() { - ctx := context.Background() - req := testcontainers.ContainerRequest{ - Image: "gogs/gogs:0.11.91", - ExposedPorts: []string{"3000/tcp"}, - WaitingFor: wait.ForHTTP("/").WithPort("3000/tcp"), - } +func TestWaitHTTP_TimeoutAccessors(t *testing.T) { + strategy := ForHTTP("/test") - gogs, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: req, - Started: true, - }) - if err != nil { - panic(err) - } + strategy.timeout = time.Second * 2 + assert.Equal(t, time.Second*2, strategy.timeout) - defer gogs.Terminate(ctx) // nolint: errcheck - // Here you have a running container + strategy.WithTimeout(time.Second * 3) + assert.Equal(t, time.Second*3, strategy.timeout) -} - -func TestHTTPStrategyWaitUntilReady(t *testing.T) { - workdir, err := os.Getwd() - if err != nil { - t.Error(err) - return - } - - capath := workdir + "/testdata/root.pem" - cafile, err := ioutil.ReadFile(capath) - if err != nil { - t.Errorf("can't load ca file: %v", err) - return - } - - certpool := x509.NewCertPool() - if !certpool.AppendCertsFromPEM(cafile) { - t.Errorf("the ca file isn't valid") - return - } - - tlsconfig := &tls.Config{RootCAs: certpool, ServerName: "testcontainer.go.test"} - dockerReq := testcontainers.ContainerRequest{ - FromDockerfile: testcontainers.FromDockerfile{ - Context: workdir + "/testdata", - }, - ExposedPorts: []string{"6443/tcp"}, - WaitingFor: wait.NewHTTPStrategy("/ping").WithTLS(true, tlsconfig). - WithStartupTimeout(time.Second * 10).WithPort("6443/tcp"). - WithResponseMatcher(func(body io.Reader) bool { - data, _ := ioutil.ReadAll(body) - return bytes.Equal(data, []byte("pong")) - }). - WithMethod(http.MethodPost).WithBody(bytes.NewReader([]byte("ping"))), - } - - container, err := testcontainers.GenericContainer(context.Background(), - testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - defer container.Terminate(context.Background()) // nolint: errcheck - - host, err := container.Host(context.Background()) - if err != nil { - t.Error(err) - return - } - port, err := container.MappedPort(context.Background(), "6443/tcp") - if err != nil { - t.Error(err) - return - } - client := http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsconfig, - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, - ForceAttemptHTTP2: true, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - }, - } - resp, err := client.Get(fmt.Sprintf("https://%s:%s", host, port.Port())) - if err != nil { - t.Error(err) - return - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("status code isn't ok: %s", resp.Status) - return - } + // Deprecated + strategy.WithStartupTimeout(time.Second * 4) + assert.Equal(t, time.Second*4, strategy.timeout) } diff --git a/wait/integration_tests/http_test.go b/wait/integration_tests/http_test.go new file mode 100644 index 0000000000..8498e8a748 --- /dev/null +++ b/wait/integration_tests/http_test.go @@ -0,0 +1,122 @@ +package integration_tests + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "os" + "testing" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// +// https://github.com/testcontainers/testcontainers-go/issues/183 +func ExampleHTTPStrategy() { + ctx := context.Background() + req := testcontainers.ContainerRequest{ + Image: "gogs/gogs:0.11.91", + ExposedPorts: []string{"3000/tcp"}, + WaitingFor: wait.ForHTTP("/").WithPort("3000/tcp"), + } + + gogs, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + if err != nil { + panic(err) + } + + defer gogs.Terminate(ctx) // nolint: errcheck + // Here you have a running container +} + +func TestHTTPStrategyWaitUntilReady(t *testing.T) { + workdir, err := os.Getwd() + if err != nil { + t.Error(err) + return + } + + capath := workdir + "/testdata/root.pem" + cafile, err := ioutil.ReadFile(capath) + if err != nil { + t.Errorf("can't load ca file: %v", err) + return + } + + certpool := x509.NewCertPool() + if !certpool.AppendCertsFromPEM(cafile) { + t.Errorf("the ca file isn't valid") + return + } + + tlsconfig := &tls.Config{RootCAs: certpool, ServerName: "testcontainer.go.test"} + dockerReq := testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Context: workdir + "/testdata", + }, + ExposedPorts: []string{"6443/tcp"}, + WaitingFor: wait.NewHTTPStrategy("/ping").WithTLS(true, tlsconfig). + WithTimeout(time.Second * 10).WithPort("6443/tcp"). + WithResponseMatcher(func(body io.Reader) bool { + data, _ := ioutil.ReadAll(body) + return bytes.Equal(data, []byte("pong")) + }). + WithMethod(http.MethodPost).WithBody(bytes.NewReader([]byte("ping"))), + } + + container, err := testcontainers.GenericContainer(context.Background(), + testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) + if err != nil { + t.Error(err) + return + } + defer container.Terminate(context.Background()) // nolint: errcheck + + host, err := container.Host(context.Background()) + if err != nil { + t.Error(err) + return + } + port, err := container.MappedPort(context.Background(), "6443/tcp") + if err != nil { + t.Error(err) + return + } + client := http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsconfig, + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + } + resp, err := client.Get(fmt.Sprintf("https://%s:%s", host, port.Port())) + if err != nil { + t.Error(err) + return + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Errorf("status code isn't ok: %s", resp.Status) + return + } +} diff --git a/wait/testdata/Dockerfile b/wait/integration_tests/testdata/Dockerfile similarity index 100% rename from wait/testdata/Dockerfile rename to wait/integration_tests/testdata/Dockerfile diff --git a/wait/testdata/go.mod b/wait/integration_tests/testdata/go.mod similarity index 100% rename from wait/testdata/go.mod rename to wait/integration_tests/testdata/go.mod diff --git a/wait/testdata/main.go b/wait/integration_tests/testdata/main.go similarity index 100% rename from wait/testdata/main.go rename to wait/integration_tests/testdata/main.go diff --git a/wait/testdata/root.pem b/wait/integration_tests/testdata/root.pem similarity index 100% rename from wait/testdata/root.pem rename to wait/integration_tests/testdata/root.pem diff --git a/wait/testdata/tls-key.pem b/wait/integration_tests/testdata/tls-key.pem similarity index 100% rename from wait/testdata/tls-key.pem rename to wait/integration_tests/testdata/tls-key.pem diff --git a/wait/testdata/tls.pem b/wait/integration_tests/testdata/tls.pem similarity index 100% rename from wait/testdata/tls.pem rename to wait/integration_tests/testdata/tls.pem diff --git a/wait/log.go b/wait/log.go index d16b3d1b82..bbe4874c13 100644 --- a/wait/log.go +++ b/wait/log.go @@ -12,8 +12,8 @@ var _ Strategy = (*LogStrategy)(nil) // LogStrategy will wait until a given log entry shows up in the docker logs type LogStrategy struct { - // all Strategies should have a startupTimeout to avoid waiting infinitely - startupTimeout time.Duration + // all Strategies should have a timeout to avoid waiting infinitely + timeout time.Duration // additional properties Log string @@ -24,21 +24,27 @@ type LogStrategy struct { // NewLogStrategy constructs with polling interval of 100 milliseconds and startup timeout of 60 seconds by default func NewLogStrategy(log string) *LogStrategy { return &LogStrategy{ - startupTimeout: defaultStartupTimeout(), - Log: log, - Occurrence: 1, - PollInterval: defaultPollInterval(), + timeout: defaultTimeout(), + Log: log, + Occurrence: 1, + PollInterval: defaultPollInterval(), } - } // fluent builders for each property // since go has neither covariance nor generics, the return type must be the type of the concrete implementation -// this is true for all properties, even the "shared" ones like startupTimeout +// this is true for all properties, even the "shared" ones like timeout // WithStartupTimeout can be used to change the default startup timeout -func (ws *LogStrategy) WithStartupTimeout(startupTimeout time.Duration) *LogStrategy { - ws.startupTimeout = startupTimeout +// +// Deprecated: use WithTimeout instead +func (s *LogStrategy) WithStartupTimeout(timeout time.Duration) *LogStrategy { + return s.WithTimeout(timeout) +} + +// WithTimeout can be used to change the default startup timeout +func (ws *LogStrategy) WithTimeout(timeout time.Duration) *LogStrategy { + ws.timeout = timeout return ws } @@ -69,8 +75,8 @@ func ForLog(log string) *LogStrategy { // WaitUntilReady implements Strategy.WaitUntilReady func (ws *LogStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) { - // limit context to startupTimeout - ctx, cancelContext := context.WithTimeout(ctx, ws.startupTimeout) + // limit context to timeout + ctx, cancelContext := context.WithTimeout(ctx, ws.timeout) defer cancelContext() LOOP: @@ -80,7 +86,6 @@ LOOP: return ctx.Err() default: reader, err := target.Logs(ctx) - if err != nil { time.Sleep(ws.PollInterval) continue diff --git a/wait/log_test.go b/wait/log_test.go index 85be13effa..57d6990dfe 100644 --- a/wait/log_test.go +++ b/wait/log_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" ) type noopStrategyTarget struct { @@ -39,7 +40,8 @@ func TestWaitForLog(t *testing.T) { target := noopStrategyTarget{ ioReaderCloser: ioutil.NopCloser(bytes.NewReader([]byte("docker"))), } - wg := NewLogStrategy("docker").WithStartupTimeout(100 * time.Microsecond) + wg := NewLogStrategy("docker"). + WithTimeout(100 * time.Microsecond) err := wg.WaitUntilReady(context.Background(), target) if err != nil { t.Fatal(err) @@ -51,7 +53,7 @@ func TestWaitWithExactNumberOfOccurrences(t *testing.T) { ioReaderCloser: ioutil.NopCloser(bytes.NewReader([]byte("kubernetes\r\ndocker\n\rdocker"))), } wg := NewLogStrategy("docker"). - WithStartupTimeout(100 * time.Microsecond). + WithTimeout(100 * time.Microsecond). WithOccurrence(2) err := wg.WaitUntilReady(context.Background(), target) if err != nil { @@ -64,7 +66,7 @@ func TestWaitWithExactNumberOfOccurrencesButItWillNeverHappen(t *testing.T) { ioReaderCloser: ioutil.NopCloser(bytes.NewReader([]byte("kubernetes\r\ndocker"))), } wg := NewLogStrategy("containerd"). - WithStartupTimeout(100 * time.Microsecond). + WithTimeout(100 * time.Microsecond). WithOccurrence(2) err := wg.WaitUntilReady(context.Background(), target) if err == nil { @@ -77,10 +79,24 @@ func TestWaitShouldFailWithExactNumberOfOccurrences(t *testing.T) { ioReaderCloser: ioutil.NopCloser(bytes.NewReader([]byte("kubernetes\r\ndocker"))), } wg := NewLogStrategy("docker"). - WithStartupTimeout(100 * time.Microsecond). + WithTimeout(100 * time.Microsecond). WithOccurrence(2) err := wg.WaitUntilReady(context.Background(), target) if err == nil { t.Fatal("expected error") } } + +func TestWaitLog_TimeoutAccessors(t *testing.T) { + strategy := ForLog("") + + strategy.timeout = time.Second * 2 + assert.Equal(t, time.Second*2, strategy.timeout) + + strategy.WithTimeout(time.Second * 3) + assert.Equal(t, time.Second*3, strategy.timeout) + + // Deprecated + strategy.WithStartupTimeout(time.Second * 4) + assert.Equal(t, time.Second*4, strategy.timeout) +} diff --git a/wait/multi.go b/wait/multi.go index 468a5a1932..2c82d13114 100644 --- a/wait/multi.go +++ b/wait/multi.go @@ -10,27 +10,35 @@ import ( var _ Strategy = (*MultiStrategy)(nil) type MultiStrategy struct { - // all Strategies should have a startupTimeout to avoid waiting infinitely - startupTimeout time.Duration + // all Strategies should have a timeout to avoid waiting infinitely + timeout time.Duration // additional properties Strategies []Strategy } -func (ms *MultiStrategy) WithStartupTimeout(startupTimeout time.Duration) *MultiStrategy { - ms.startupTimeout = startupTimeout +// WithStartupTimeout can be used to change the default startup timeout +// +// Deprecated: use WithTimeout instead +func (s *MultiStrategy) WithStartupTimeout(timeout time.Duration) *MultiStrategy { + return s.WithTimeout(timeout) +} + +// WithTimeout can be used to change the default startup timeout +func (ms *MultiStrategy) WithTimeout(timeout time.Duration) *MultiStrategy { + ms.timeout = timeout return ms } func ForAll(strategies ...Strategy) *MultiStrategy { return &MultiStrategy{ - startupTimeout: defaultStartupTimeout(), - Strategies: strategies, + timeout: defaultTimeout(), + Strategies: strategies, } } func (ms *MultiStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) { - ctx, cancelContext := context.WithTimeout(ctx, ms.startupTimeout) + ctx, cancelContext := context.WithTimeout(ctx, ms.timeout) defer cancelContext() if len(ms.Strategies) == 0 { diff --git a/wait/multi_test.go b/wait/multi_test.go new file mode 100644 index 0000000000..935bda5bc2 --- /dev/null +++ b/wait/multi_test.go @@ -0,0 +1,22 @@ +package wait + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestWaitMulti_TimeoutAccessors(t *testing.T) { + strategy := ForAll() + + strategy.timeout = time.Second * 2 + assert.Equal(t, time.Second*2, strategy.timeout) + + strategy.WithTimeout(time.Second * 3) + assert.Equal(t, time.Second*3, strategy.timeout) + + // Deprecated + strategy.WithStartupTimeout(time.Second * 4) + assert.Equal(t, time.Second*4, strategy.timeout) +} diff --git a/wait/sql.go b/wait/sql.go index 6fd8d9965d..26893672d2 100644 --- a/wait/sql.go +++ b/wait/sql.go @@ -9,41 +9,48 @@ import ( "github.com/docker/go-connections/nat" ) -//ForSQL constructs a new waitForSql strategy for the given driver +// ForSQL constructs a new waitForSql strategy for the given driver func ForSQL(port nat.Port, driver string, url func(nat.Port) string) *waitForSql { return &waitForSql{ - Port: port, - URL: url, - Driver: driver, - startupTimeout: defaultStartupTimeout(), - PollInterval: defaultPollInterval(), + Port: port, + URL: url, + Driver: driver, + timeout: defaultTimeout(), + PollInterval: defaultPollInterval(), } } type waitForSql struct { - URL func(port nat.Port) string - Driver string - Port nat.Port - startupTimeout time.Duration - PollInterval time.Duration + URL func(port nat.Port) string + Driver string + Port nat.Port + timeout time.Duration + PollInterval time.Duration } -//Timeout sets the maximum waiting time for the strategy after which it'll give up and return an error -func (w *waitForSql) Timeout(duration time.Duration) *waitForSql { - w.startupTimeout = duration - return w +// Timeout sets the maximum waiting time for the strategy after which it'll give up and return an error +// +// Deprecated: use WithTimeout instead +func (s *waitForSql) Timeout(timeout time.Duration) *waitForSql { + return s.WithTimeout(timeout) +} + +// WithTimeout sets the maximum waiting time for the strategy after which it'll give up and return an error +func (s *waitForSql) WithTimeout(duration time.Duration) *waitForSql { + s.timeout = duration + return s } -//WithPollInterval can be used to override the default polling interval of 100 milliseconds +// WithPollInterval can be used to override the default polling interval of 100 milliseconds func (w *waitForSql) WithPollInterval(pollInterval time.Duration) *waitForSql { w.PollInterval = pollInterval return w } -//WaitUntilReady repeatedly tries to run "SELECT 1" query on the given port using sql and driver. +// WaitUntilReady repeatedly tries to run "SELECT 1" query on the given port using sql and driver. // If the it doesn't succeed until the timeout value which defaults to 60 seconds, it will return an error func (w *waitForSql) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) { - ctx, cancel := context.WithTimeout(ctx, w.startupTimeout) + ctx, cancel := context.WithTimeout(ctx, w.timeout) defer cancel() ticker := time.NewTicker(w.PollInterval) diff --git a/wait/sql_test.go b/wait/sql_test.go new file mode 100644 index 0000000000..4910ab1435 --- /dev/null +++ b/wait/sql_test.go @@ -0,0 +1,25 @@ +package wait + +import ( + "testing" + "time" + + "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" +) + +func TestWaitSql_TimeoutAccessors(t *testing.T) { + strategy := ForSQL(nat.Port("8080"), "", func(p nat.Port) string { + return p.Port() + }) + + strategy.timeout = time.Second * 2 + assert.Equal(t, time.Second*2, strategy.timeout) + + strategy.WithTimeout(time.Second * 3) + assert.Equal(t, time.Second*3, strategy.timeout) + + // Deprecated + strategy.Timeout(time.Second * 4) + assert.Equal(t, time.Second*4, strategy.timeout) +} diff --git a/wait/wait.go b/wait/wait.go index 5fcc284ccc..2ec5009274 100644 --- a/wait/wait.go +++ b/wait/wait.go @@ -21,7 +21,7 @@ type StrategyTarget interface { State(context.Context) (*types.ContainerState, error) } -func defaultStartupTimeout() time.Duration { +func defaultTimeout() time.Duration { return 60 * time.Second }