core/dockerutil/docker_test.go
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
}
}
}