Skip to content

Commit

Permalink
docs: add a basic layout for wait strategies in docs (#536)
Browse files Browse the repository at this point in the history
* docs: add a basic layout for wait strategies in docs

* fix: bump docs version

* docs: remove exposed ports

* docs: rename intro file

* chor: use simpler image

* chore: simplify waitFor http examples

* docs: add wait.ForExec

* docs: simplify wording

* docs: link to exec

* docs: add exit wait strategy

* chore: wording

* fix: relative links

* feat: support setting poll interval in host port

* chore: include startup timeout

* docs: add health strategy

* fix: add dots

* docs: add wait for log

* docs: add wait for multiple

* chore: consistency with startup timeout method in SQL wait

* docs: add wait for SQL

* chore: apply suggestions from code review

Co-authored-by: Kevin Wittek <kiview@users.noreply.github.com>

* fix: wrong copy & paste

* chore: wording

* chore: explain wait.FromExposedPort and container config

* docs: clarify

* chore: apply suggestions from code review

Co-authored-by: Kevin Wittek <kiview@users.noreply.github.com>

* fix: replace one shot with exit

* fix: refine introduction to wait strategies

* docs: create section for startup timeouts

* docs: rephrase startup timeout

* docs: explain poll interval

Co-authored-by: Kevin Wittek <kiview@users.noreply.github.com>
  • Loading branch information
mdelapenya and kiview committed Sep 28, 2022
1 parent 6ee7f6a commit db5eeb8
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 3 deletions.
19 changes: 19 additions & 0 deletions docs/features/wait/exec.md
@@ -0,0 +1,19 @@
# Exec Wait Strategy

The exec wait strategy will check the exit code of a process to be executed in the container, and allows to set the following conditions:

- the command and arguments to be executed, as an array of strings.
- a function to match a specific exit code, with the default matching `0`.
- the startup timeout to be used in seconds, default is 60 seconds.
- the poll interval to be used in milliseconds, default is 100 milliseconds.

## Match an exit code

```golang
req := ContainerRequest{
Image: "docker.io/nginx:alpine",
WaitingFor: wait.NewExecStrategy([]string{"git", "version"}).WithExitCodeMatcher(func(exitCode int) bool {
return exitCode == 10
}),
}
```
15 changes: 15 additions & 0 deletions docs/features/wait/exit.md
@@ -0,0 +1,15 @@
# Exit Wait strategy

The exit wait strategy will check that the container is not in the running state, and allows to set the following conditions:

- the exit timeout in seconds, default is `0`.
- the poll interval to be used in milliseconds, default is 100 milliseconds.

## Match an exit code

```golang
req := ContainerRequest{
Image: "docker.io/alpine:latest",
WaitingFor: wait.ForExit(),
}
```
13 changes: 13 additions & 0 deletions docs/features/wait/health.md
@@ -0,0 +1,13 @@
# Health Wait strategy

The health wait strategy will check that the container is in the healthy state and allows to set the following conditions:

- the startup timeout to be used in seconds, default is 60 seconds.
- the poll interval to be used in milliseconds, default is 100 milliseconds.

```golang
req := ContainerRequest{
Image: "docker.io/alpine:latest",
WaitingFor: wait.ForHealthCheck(),
}
```
41 changes: 41 additions & 0 deletions docs/features/wait/host_port.md
@@ -0,0 +1,41 @@
# HostPort Wait strategy

The host-port wait strategy will check if the container is listening to a specific port and allows to set the following conditions:

- a port exposed by the container. The port and protocol to be used, which is represented by a string containing the port number and protocol in the format "80/tcp".
- alternatively, wait for the first exposed port in the container.
- the startup timeout to be used, default is 60 seconds.
- the poll interval to be used, default is 100 milliseconds.

Variations on the HostPort wait strategy are supported, including:

## Listening port in the container

```golang
req := ContainerRequest{
Image: "docker.io/nginx:alpine",
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForListeningPort("80/tcp"),
}
```

## First exposed port in the container

The wait strategy will use the first exposed port from the container configuration.

```golang
req := ContainerRequest{
Image: "docker.io/nginx:alpine",
WaitingFor: wait.ForExposedPort(),
}
```

Said that, it could be the case that the container request included ports to be exposed. Therefore using `wait.ForExposedPort` will wait for the first exposed port in the request, because the container configuration retrieved from Docker will already include them.

```golang
req := ContainerRequest{
Image: "docker.io/nginx:alpine",
ExposedPorts: []string{"80/tcp", "9080/tcp"},
WaitingFor: wait.ForExposedPort(),
}
```
61 changes: 61 additions & 0 deletions docs/features/wait/http.md
@@ -0,0 +1,61 @@
# HTTP(S) Wait strategy

The HTTP wait strategy will check the result of an HTTP(S) request against the container and allows to set the following conditions:

- the port to be used.
- the path to be used.
- the HTTP method to be used.
- the HTTP request body to be sent.
- the HTTP status code matcher as a function.
- the HTTP response matcher as a function.
- the TLS config to be used for HTTPS.
- the startup timeout to be used in seconds, default is 60 seconds.
- the poll interval to be used in milliseconds, default is 100 milliseconds.

Variations on the HTTP wait strategy are supported, including:

## Match an HTTP method

```golang
req := ContainerRequest{
Image: "docker.io/nginx:alpine",
ExposedPorts: []string{"8086/tcp"},
WaitingFor: wait.ForHTTP("/ping").WithMethod(http.MethodPost).WithBody(bytes.NewReader([]byte("ping"))),
}
```

## Match an HTTP status code

```golang
req := ContainerRequest{
Image: "docker.io/nginx:alpine",
ExposedPorts: []string{"8086/tcp"},
WaitingFor: wait.ForHTTP("/ping").WithPort("8086/tcp").WithStatusCodeMatcher(
func(status int) bool {
return status == http.StatusNoContent
},
),
}
```

## Match an HTTPS status code and a response matcher

```golang
req := testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: workdir + "/testdata",
},
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.NewHTTPStrategy("/ping").
WithStartupTimeout(time.Second * 10).WithPort("80/tcp").
WithResponseMatcher(func(body io.Reader) bool {
data, _ := ioutil.ReadAll(body)
return bytes.Equal(data, []byte("pong"))
}).
WithStatusCodeMatcher(func(status int) bool {
i++ // always fail the first try in order to force the polling loop to be re-run
return i > 1 && status == 200
}).
WithMethod(http.MethodPost).WithBody(bytes.NewReader([]byte("ping"))),
}
```
26 changes: 26 additions & 0 deletions docs/features/wait/introduction.md
@@ -0,0 +1,26 @@
# Wait Strategies

There are scenarios where your tests need the external services they rely on to reach a specific state that is particularly useful for testing. This is generally approximated as 'Can we talk to this container over the network?' or 'Let's wait until the container is running an reaches certain state'.

Testcontainers-go comes with the concept of `wait strategy`, which allows your tests to actually wait for the most useful conditions to be met, before continuing with their execution. These wait strategies are implemented in the `wait` package.

Below you can find a list of the available wait strategies that you can use:

- [Exec](./exec.md)
- [Exit](./exit.md)
- [Health](./health.md)
- [HostPort](./host_port.md)
- [HTTP](./http.md)
- [Log](./log.md)
- [Multi](./multi.md)
- [SQL](./sql.md)

## Startup timeout and Poll interval

When defining a wait strategy, it should define a way to set the startup timeout to avoid waiting infinitely. For that, Testcontainers-go creates a cancel context with 60 seconds defined as timeout.

If the default 60s timeout is not sufficient, it can be updated with the `WithStartupTimeout(startupTimeout time.Duration)` function.

Besides that, it's possible to define a poll interval, which will actually stop 100 milliseconds the test execution.

If the default 100 milliseconds poll interval is not sufficient, it can be updated with the `WithPollInterval(pollInterval time.Duration)` function.
20 changes: 20 additions & 0 deletions docs/features/wait/log.md
@@ -0,0 +1,20 @@
# Log Wait strategy

The Log wait strategy will check if a string occurs in the container logs for a desired number of times, and allows to set the following conditions:

- the string to be waited for in the container log.
- the number of occurrences of the string to wait for, default is `1`.
- the startup timeout to be used in seconds, default is 60 seconds.
- the poll interval to be used in milliseconds, default is 100 milliseconds.

```golang
req := ContainerRequest{
Image: "docker.io/mysql:latest",
ExposedPorts: []string{"3306/tcp", "33060/tcp"},
Env: map[string]string{
"MYSQL_ROOT_PASSWORD": "password",
"MYSQL_DATABASE": "database",
},
WaitingFor: wait.ForLog("port: 3306 MySQL Community Server - GPL"),
}
```
20 changes: 20 additions & 0 deletions docs/features/wait/multi.md
@@ -0,0 +1,20 @@
# Multi Wait strategy

The Multi wait strategy will hold a list of wait strategies, in order to wait for all of them. It's possible to set the following conditions:

- the startup timeout to be used in seconds, default is 60 seconds.

```golang
req := ContainerRequest{
Image: "docker.io/mysql:latest",
ExposedPorts: []string{"3306/tcp", "33060/tcp"},
Env: map[string]string{
"MYSQL_ROOT_PASSWORD": "password",
"MYSQL_DATABASE": "database",
},
WaitingFor: wait.ForAll(
wait.ForLog("port: 3306 MySQL Community Server - GPL"),
wait.ForListeningPort("3306/tcp"),
).WithStartupTimeout(10*time.Second),
}
```
22 changes: 22 additions & 0 deletions docs/features/wait/sql.md
@@ -0,0 +1,22 @@
# SQL Wait strategy

The SQL wait strategy will check the result of a SQL query executed in a container representing a SQL database, and allows to set the following conditions:

- the SQL query to be used, default is `SELECT 1`.
- the port to be used.
- the database driver to be used, as a string.
- the URL of the database to be used, as a function returning the URL string.
- the startup timeout to be used in seconds, default is 60 seconds.
- the poll interval to be used in milliseconds, default is 100 milliseconds.

```golang
req := ContainerRequest{
Image: "postgres:14.1-alpine",
ExposedPorts: []string{port},
Cmd: []string{"postgres", "-c", "fsync=off"},
Env: env,
WaitingFor: wait.ForSQL(nat.Port(port), "postgres", dbURL).
WithStartupTimeout(time.Second * 5).
WithQuery("SELECT 10"),
}
```
12 changes: 11 additions & 1 deletion mkdocs.yml
Expand Up @@ -37,6 +37,16 @@ nav:
- features/override_container_command.md
- features/copy_file.md
- features/using_podman.md
- Wait Strategies:
- Introduction: features/wait/introduction.md
- Exec: features/wait/exec.md
- Exit: features/wait/exit.md
- Health: features/wait/health.md
- HostPort: features/wait/host_port.md
- HTTP: features/wait/http.md
- Log: features/wait/log.md
- Multi: features/wait/multi.md
- SQL: features/wait/sql.md
- Examples:
- examples/cockroachdb.md
- examples/nginx.md
Expand All @@ -46,4 +56,4 @@ nav:
- contributing_docs.md
- Getting help: getting_help.md
extra:
latest_version: 0.13.0
latest_version: 0.14.0
10 changes: 9 additions & 1 deletion wait/host_port.go
Expand Up @@ -21,13 +21,15 @@ type HostPortStrategy struct {
Port nat.Port
// all WaitStrategies should have a startupTimeout to avoid waiting infinitely
startupTimeout time.Duration
PollInterval time.Duration
}

// NewHostPortStrategy constructs a default host port strategy
func NewHostPortStrategy(port nat.Port) *HostPortStrategy {
return &HostPortStrategy{
Port: port,
startupTimeout: defaultStartupTimeout(),
PollInterval: defaultPollInterval(),
}
}

Expand All @@ -52,6 +54,12 @@ func (hp *HostPortStrategy) WithStartupTimeout(startupTimeout time.Duration) *Ho
return hp
}

// WithPollInterval can be used to override the default polling interval of 100 milliseconds
func (hp *HostPortStrategy) WithPollInterval(pollInterval time.Duration) *HostPortStrategy {
hp.PollInterval = pollInterval
return hp
}

// WaitUntilReady implements Strategy.WaitUntilReady
func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
// limit context to startupTimeout
Expand All @@ -63,7 +71,7 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT
return
}

var waitInterval = 100 * time.Millisecond
var waitInterval = hp.PollInterval

internalPort := hp.Port
if internalPort == "" {
Expand Down
8 changes: 7 additions & 1 deletion wait/sql.go
Expand Up @@ -33,8 +33,14 @@ type waitForSql struct {
}

//Timeout sets the maximum waiting time for the strategy after which it'll give up and return an error
// Deprecated: Use WithStartupTimeout
func (w *waitForSql) Timeout(duration time.Duration) *waitForSql {
w.startupTimeout = duration
return w.WithStartupTimeout(duration)
}

// WithStartupTimeout can be used to change the default startup timeout
func (w *waitForSql) WithStartupTimeout(startupTimeout time.Duration) *waitForSql {
w.startupTimeout = startupTimeout
return w
}

Expand Down

0 comments on commit db5eeb8

Please sign in to comment.