Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add a basic layout for wait strategies in docs #536

Merged
merged 31 commits into from Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d0b477d
docs: add a basic layout for wait strategies in docs
mdelapenya Sep 26, 2022
6c62479
fix: bump docs version
mdelapenya Sep 26, 2022
a85f628
docs: remove exposed ports
mdelapenya Sep 27, 2022
57aba3c
docs: rename intro file
mdelapenya Sep 27, 2022
c449248
chor: use simpler image
mdelapenya Sep 27, 2022
c44d50f
chore: simplify waitFor http examples
mdelapenya Sep 27, 2022
2d82146
docs: add wait.ForExec
mdelapenya Sep 27, 2022
d83e5f1
docs: simplify wording
mdelapenya Sep 27, 2022
7944e88
docs: link to exec
mdelapenya Sep 27, 2022
199235b
docs: add exit wait strategy
mdelapenya Sep 27, 2022
2897466
chore: wording
mdelapenya Sep 27, 2022
afdcec2
fix: relative links
mdelapenya Sep 27, 2022
5e77113
feat: support setting poll interval in host port
mdelapenya Sep 27, 2022
340282f
chore: include startup timeout
mdelapenya Sep 27, 2022
63e4321
docs: add health strategy
mdelapenya Sep 27, 2022
b5f4aac
fix: add dots
mdelapenya Sep 27, 2022
0586124
docs: add wait for log
mdelapenya Sep 27, 2022
ce6170b
docs: add wait for multiple
mdelapenya Sep 27, 2022
d9e060b
chore: consistency with startup timeout method in SQL wait
mdelapenya Sep 27, 2022
7431879
docs: add wait for SQL
mdelapenya Sep 27, 2022
2bdbf45
chore: apply suggestions from code review
mdelapenya Sep 27, 2022
dc7674e
fix: wrong copy & paste
mdelapenya Sep 27, 2022
7c054c6
chore: wording
mdelapenya Sep 27, 2022
39d5046
chore: explain wait.FromExposedPort and container config
mdelapenya Sep 28, 2022
f50fced
docs: clarify
mdelapenya Sep 28, 2022
4671f4d
chore: apply suggestions from code review
mdelapenya Sep 28, 2022
9c1278c
fix: replace one shot with exit
mdelapenya Sep 28, 2022
6a26f09
fix: refine introduction to wait strategies
mdelapenya Sep 28, 2022
c8a83eb
docs: create section for startup timeouts
mdelapenya Sep 28, 2022
b57fe60
docs: rephrase startup timeout
mdelapenya Sep 28, 2022
fb87139
docs: explain poll interval
mdelapenya Sep 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved

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 {
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
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 {
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
w.startupTimeout = startupTimeout
return w
}

Expand Down