mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-30 09:38:26 -05:00
193 lines
5.3 KiB
Go
193 lines
5.3 KiB
Go
package testcontainers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/containerd/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()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Skipf("Recovered from panic: %v. Docker is not running. Testcontainers can't perform is work without it", r)
|
|
}
|
|
}()
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
// SkipIfNotDockerDesktop is a utility function capable of skipping tests
|
|
// if tests are not run using Docker Desktop.
|
|
func SkipIfNotDockerDesktop(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 needs 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, it's 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, it's a no-op.
|
|
func CleanupNetwork(tb testing.TB, network Network) {
|
|
tb.Helper()
|
|
|
|
tb.Cleanup(func() {
|
|
if !isNil(network) {
|
|
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
|
|
}
|
|
|
|
// First try with containerd's errdefs
|
|
switch {
|
|
case errdefs.IsNotFound(err):
|
|
return true
|
|
case errdefs.IsConflict(err):
|
|
// Terminating a container that is already terminating.
|
|
if errAlreadyInProgress.MatchString(err.Error()) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
switch x := err.(type) { //nolint:errorlint // We need to check for interfaces.
|
|
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)
|
|
}
|