forked from testcontainers/testcontainers-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
testing.go
165 lines (139 loc) · 4.57 KB
/
testing.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package testcontainers
import (
"context"
"fmt"
"io"
"regexp"
"testing"
"github.com/docker/docker/errdefs"
"github.com/stretchr/testify/require"
)
// errAlreadyInProgress is a regular expression that matches the error for a container
// removal that is already in progress.
var errAlreadyInProgress = regexp.MustCompile(`removal of container .* is already in progress`)
// SkipIfProviderIsNotHealthy is a utility function capable of skipping tests
// if the provider is not healthy, or running at all.
// This is a function designed to be used in your test, when Docker is not mandatory for CI/CD.
// In this way tests that depend on Testcontainers won't run if the provider is provisioned correctly.
func SkipIfProviderIsNotHealthy(t *testing.T) {
t.Helper()
ctx := context.Background()
provider, err := ProviderDocker.GetProvider()
if err != nil {
t.Skipf("Docker is not running. TestContainers can't perform is work without it: %s", err)
}
err = provider.Health(ctx)
if err != nil {
t.Skipf("Docker is not running. TestContainers can't perform is work without it: %s", err)
}
}
// SkipIfDockerDesktop is a utility function capable of skipping tests
// if tests are run using Docker Desktop.
func SkipIfDockerDesktop(t *testing.T, ctx context.Context) {
t.Helper()
cli, err := NewDockerClientWithOpts(ctx)
require.NoErrorf(t, err, "failed to create docker client: %s", err)
info, err := cli.Info(ctx)
require.NoErrorf(t, err, "failed to get docker info: %s", err)
if info.OperatingSystem == "Docker Desktop" {
t.Skip("Skipping test that requires host network access when running in Docker Desktop")
}
}
// exampleLogConsumer {
// StdoutLogConsumer is a LogConsumer that prints the log to stdout
type StdoutLogConsumer struct{}
// Accept prints the log to stdout
func (lc *StdoutLogConsumer) Accept(l Log) {
fmt.Print(string(l.Content))
}
// }
// CleanupContainer is a helper function that schedules the container
// to be stopped / terminated when the test ends.
//
// This should be called as a defer directly after (before any error check)
// of [GenericContainer](...) or a modules Run(...) in a test to ensure the
// container is stopped when the function ends.
//
// before any error check. If container is nil, its a no-op.
func CleanupContainer(tb testing.TB, ctr Container, options ...TerminateOption) {
tb.Helper()
tb.Cleanup(func() {
noErrorOrIgnored(tb, TerminateContainer(ctr, options...))
})
}
// CleanupNetwork is a helper function that schedules the network to be
// removed when the test ends.
// This should be the first call after NewNetwork(...) in a test before
// any error check. If network is nil, its a no-op.
func CleanupNetwork(tb testing.TB, network Network) {
tb.Helper()
tb.Cleanup(func() {
noErrorOrIgnored(tb, network.Remove(context.Background()))
})
}
// noErrorOrIgnored is a helper function that checks if the error is nil or an error
// we can ignore.
func noErrorOrIgnored(tb testing.TB, err error) {
tb.Helper()
if isCleanupSafe(err) {
return
}
require.NoError(tb, err)
}
// causer is an interface that allows to get the cause of an error.
type causer interface {
Cause() error
}
// wrapErr is an interface that allows to unwrap an error.
type wrapErr interface {
Unwrap() error
}
// unwrapErrs is an interface that allows to unwrap multiple errors.
type unwrapErrs interface {
Unwrap() []error
}
// isCleanupSafe reports whether all errors in err's tree are one of the
// following, so can safely be ignored:
// - nil
// - not found
// - already in progress
func isCleanupSafe(err error) bool {
if err == nil {
return true
}
switch x := err.(type) { //nolint:errorlint // We need to check for interfaces.
case errdefs.ErrNotFound:
return true
case errdefs.ErrConflict:
// Terminating a container that is already terminating.
if errAlreadyInProgress.MatchString(err.Error()) {
return true
}
return false
case causer:
return isCleanupSafe(x.Cause())
case wrapErr:
return isCleanupSafe(x.Unwrap())
case unwrapErrs:
for _, e := range x.Unwrap() {
if !isCleanupSafe(e) {
return false
}
}
return true
default:
return false
}
}
// RequireContainerExec is a helper function that executes a command in a container
// It insures that there is no error during the execution
// Finally returns the output of its execution
func RequireContainerExec(ctx context.Context, t *testing.T, container Container, cmd []string) string {
t.Helper()
code, out, err := container.Exec(ctx, cmd)
require.NoError(t, err)
require.Zero(t, code)
checkBytes, err := io.ReadAll(out)
require.NoError(t, err)
return string(checkBytes)
}