synapsecns/sanguine

View on GitHub
core/dockerutil/docker_test.go

Summary

Maintainability
A
35 mins
Test Coverage
package dockerutil_test

import (
    "context"
    "errors"
    "fmt"
    "github.com/ory/dockertest/v3"
    "github.com/ory/dockertest/v3/docker"
    "github.com/stretchr/testify/assert"
    "github.com/synapsecns/sanguine/core/dockerutil"
    "github.com/synapsecns/sanguine/core/mocktesting"
    "github.com/synapsecns/sanguine/core/processlog"
    "os"
    "testing"
)

const stdOut = "hello world\n"

func (c *DockerSuite) TestTailContainerLogs() {
    out := testContainer(c.GetTestContext(), c.T())
    assert.FileExists(c.T(), out.CombinedFile())
    assert.FileExists(c.T(), out.StdOutFile())
    assert.FileExists(c.T(), out.StdErrFile())
}

func ExampleTailContainerLogs() {
    ctx, cancel := context.WithCancel(context.Background())

    t := mocktesting.NewMockTester("TestTailContainerLogs")
    out := testContainer(ctx, t)
    cancel()

    // get the log dir
    _ = out.LogDir()
    // get the stdout file
    _ = out.StdOutFile()
    // get the stderr file
    _ = out.StdErrFile()
    // get the combined file
    _ = out.CombinedFile()
    // Output: hello world
}

// testContainer creates a test alpine container that prints hello world.
// the return will be empty if this fails.
func testContainer(ctx context.Context, tb testing.TB) processlog.LogMetadata {
    tb.Helper()

    pool, err := dockertest.NewPool("")
    assert.Nil(tb, err)

    runOptions := &dockertest.RunOptions{
        Repository: "alpine",
        Tag:        "latest",
        Cmd:        []string{"sh", "-c", fmt.Sprintf("echo %s", stdOut)},
    }

    resource, err := pool.RunWithOptions(runOptions, func(config *docker.HostConfig) {
        config.RestartPolicy = docker.RestartPolicy{Name: "no"}
    })
    assert.Nil(tb, err)

    assert.Nil(tb, resource.Expire(60))

    logInfoChan := make(chan processlog.LogMetadata)
    go func() {
        // we can't check the error here
        _ = dockerutil.TailContainerLogs(dockerutil.WithContext(ctx), dockerutil.WithResource(resource), dockerutil.WithPool(pool), dockerutil.WithFollow(true), dockerutil.WithCallback(func(ctx context.Context, metadata processlog.LogMetadata) {
            select {
            case <-ctx.Done():
                return
            case logInfoChan <- metadata:
            }
        }))
    }()

    if err := pool.Retry(func() error {
        container, err := pool.Client.InspectContainer(resource.Container.ID)
        assert.Nil(tb, err)

        if !container.State.FinishedAt.IsZero() {
            return nil
        }
        return errors.New("container is still running")
    }); err != nil {
        tb.Fatal(err)
    }

    select {
    case <-ctx.Done():
        tb.Errorf("context canceled before container started")
    case logInfo := <-logInfoChan:
        fileContents := waitForFileContents(ctx, tb, logInfo.CombinedFile())

        assert.Nil(tb, err)

        // this is what's tested in the example
        fmt.Println(string(fileContents))
        return logInfo
    }
    return nil
}

func waitForFileContents(ctx context.Context, tb testing.TB, file string) []byte {
    tb.Helper()

    for {
        select {
        case <-ctx.Done():
            return nil
        default:
            //nolint: gosec
            fileContents, err := os.ReadFile(file)
            assert.Nil(tb, err)

            if len(fileContents) == 0 {
                continue
            }

            return fileContents
        }
    }
}