Initial QSfera import
This commit is contained in:
+204
@@ -0,0 +1,204 @@
|
||||
package testcontainers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/moby/moby/client"
|
||||
"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 or another VM-based Docker
|
||||
// environment (e.g. colima) where host network access is not available.
|
||||
func SkipIfDockerDesktop(t *testing.T, ctx context.Context) {
|
||||
t.Helper()
|
||||
|
||||
// Colima runs Docker inside a Linux VM, so host networking doesn't work
|
||||
// the same way as native Docker on Linux. Detect it via DOCKER_HOST which
|
||||
// typically contains the colima socket path.
|
||||
if strings.Contains(os.Getenv("DOCKER_HOST"), "colima") {
|
||||
t.Skip("Skipping test that requires host network access when running in colima")
|
||||
}
|
||||
|
||||
cli, err := NewDockerClientWithOpts(ctx)
|
||||
require.NoErrorf(t, err, "failed to create docker client: %s", err)
|
||||
|
||||
res, err := cli.Info(ctx, client.InfoOptions{})
|
||||
require.NoErrorf(t, err, "failed to get docker info: %s", err)
|
||||
|
||||
if res.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)
|
||||
|
||||
res, err := cli.Info(ctx, client.InfoOptions{})
|
||||
require.NoErrorf(t, err, "failed to get docker info: %s", err)
|
||||
|
||||
if res.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)
|
||||
}
|
||||
Reference in New Issue
Block a user