diff --git a/tests/debug.go b/tests/debug.go new file mode 100644 index 000000000..e27685328 --- /dev/null +++ b/tests/debug.go @@ -0,0 +1,131 @@ +package tests + +import ( + "bytes" + "io" + "os/exec" + "strings" + "sync" + "testing" + "time" + + "github.com/containerd/continuity/fs/fstest" + "github.com/creack/pty" + "github.com/moby/buildkit/util/testutil/integration" + "github.com/stretchr/testify/require" +) + +var debugTests = []func(t *testing.T, sb integration.Sandbox){ + testDebugBuildMissingBindMountSource, +} + +func testDebugBuildMissingBindMountSource(t *testing.T, sb integration.Sandbox) { + if !isExperimental() { + t.Skip("debug command is experimental") + } + if !isDockerWorker(sb) { + t.Skip("debug monitor test only needs a docker worker") + } + + dockerfile := []byte(` +FROM busybox:latest +RUN --mount=type=bind,source=missing,target=/src true +`) + dir := tmpdir(t, + fstest.CreateFile("Dockerfile", dockerfile, 0o600), + ) + + cmd := buildxCmd(sb, withArgs("debug", "--on=error", "build", "--progress=plain", "--output=type=cacheonly", dir)) + f, err := pty.StartWithSize(cmd, &pty.Winsize{ + Cols: 120, + Rows: 24, + }) + require.NoError(t, err) + defer f.Close() + + var output debugOutput + copyDone := make(chan struct{}) + go func() { + _, _ = io.Copy(&output, f) + close(copyDone) + }() + + waitCh := make(chan error, 1) + go func() { + waitCh <- cmd.Wait() + }() + + exited, waitErr := waitForDebugPromptOrExit(t, &output, waitCh) + if !exited { + _, err = f.Write([]byte("exit\r")) + require.NoError(t, err) + waitErr = waitForDebugCommandExit(t, cmd, waitCh, &output) + } + + _ = f.Close() + select { + case <-copyDone: + case <-time.After(time.Second): + } + + out := output.String() + require.Error(t, waitErr, out) + require.Contains(t, out, "failed to calculate checksum") + require.Contains(t, out, "failed to create container") + require.NotContains(t, out, "panic:") + require.NotContains(t, out, "index out of range") +} + +func waitForDebugPromptOrExit(t *testing.T, output *debugOutput, waitCh <-chan error) (bool, error) { + t.Helper() + + timeout := time.NewTimer(time.Minute) + defer timeout.Stop() + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case err := <-waitCh: + return true, err + case <-ticker.C: + if strings.Contains(output.String(), "(buildx) ") { + return false, nil + } + case <-timeout.C: + require.FailNow(t, "timeout waiting for debug monitor", output.String()) + } + } +} + +func waitForDebugCommandExit(t *testing.T, cmd *exec.Cmd, waitCh <-chan error, output *debugOutput) error { + t.Helper() + + select { + case err := <-waitCh: + return err + case <-time.After(30 * time.Second): + if cmd.Process != nil { + _ = cmd.Process.Kill() + } + require.FailNow(t, "timeout waiting for debug command to exit", output.String()) + return nil + } +} + +type debugOutput struct { + mu sync.Mutex + buf bytes.Buffer +} + +func (b *debugOutput) Write(p []byte) (int, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.buf.Write(p) +} + +func (b *debugOutput) String() string { + b.mu.Lock() + defer b.mu.Unlock() + return b.buf.String() +} diff --git a/tests/integration_test.go b/tests/integration_test.go index 574251dc5..d2bcfec2c 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -24,6 +24,7 @@ func TestIntegration(t *testing.T) { var tests []func(t *testing.T, sb integration.Sandbox) tests = append(tests, commonTests...) tests = append(tests, buildTests...) + tests = append(tests, debugTests...) tests = append(tests, policyBuildTests...) tests = append(tests, policyEvalTests...) tests = append(tests, policyBakeTests...)