diff --git a/pkg/e2e/compose_run_build_once_test.go b/pkg/e2e/compose_run_build_once_test.go index 9ec809f44cb..f9726bb3b31 100644 --- a/pkg/e2e/compose_run_build_once_test.go +++ b/pkg/e2e/compose_run_build_once_test.go @@ -20,11 +20,11 @@ import ( "crypto/rand" "encoding/hex" "fmt" + "regexp" "strings" "testing" "gotest.tools/v3/assert" - "gotest.tools/v3/icmd" ) // TestRunBuildOnce tests that services with pull_policy: build are only built once @@ -32,22 +32,19 @@ import ( // This addresses a bug where dependencies were built twice: once in startDependencies // and once in ensureImagesExists. func TestRunBuildOnce(t *testing.T) { - c := NewCLI(t) + c := NewParallelCLI(t) t.Run("dependency with pull_policy build is built only once", func(t *testing.T) { projectName := randomProjectName("build-once") - res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once.yaml", "down", "--rmi", "local", "--remove-orphans") - res.Assert(t, icmd.Success) + _ = c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once.yaml", "down", "--rmi", "local", "--remove-orphans", "-v") + res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once.yaml", "--verbose", "run", "--build", "--rm", "curl") - res = c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once.yaml", "run", "--build", "--rm", "curl") - res.Assert(t, icmd.Success) + output := res.Stdout() - // Count how many times nginx was built by looking for its unique RUN command output - nginxBuilds := strings.Count(res.Combined(), "Building nginx at") + nginxBuilds := countServiceBuilds(output, projectName, "nginx") - // nginx should build exactly once, not twice - assert.Equal(t, nginxBuilds, 1, "nginx dependency should build once, but built %d times", nginxBuilds) - assert.Assert(t, strings.Contains(res.Combined(), "curl service")) + assert.Equal(t, nginxBuilds, 1, "nginx should build once, built %d times\nOutput:\n%s", nginxBuilds, output) + assert.Assert(t, strings.Contains(res.Stdout(), "curl service")) c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once.yaml", "down", "--remove-orphans") }) @@ -55,19 +52,13 @@ func TestRunBuildOnce(t *testing.T) { t.Run("nested dependencies build only once each", func(t *testing.T) { projectName := randomProjectName("build-nested") _ = c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-nested.yaml", "down", "--rmi", "local", "--remove-orphans", "-v") - res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-nested.yaml", "--verbose", "run", "--build", "--rm", "app") - res.Assert(t, icmd.Success) - - output := res.Combined() - dbBuildMarker := fmt.Sprintf("naming to docker.io/library/%s-db", projectName) - apiBuildMarker := fmt.Sprintf("naming to docker.io/library/%s-api", projectName) - appBuildMarker := fmt.Sprintf("naming to docker.io/library/%s-app", projectName) + output := res.Stdout() - dbBuilds := strings.Count(output, dbBuildMarker) - apiBuilds := strings.Count(output, apiBuildMarker) - appBuilds := strings.Count(output, appBuildMarker) + dbBuilds := countServiceBuilds(output, projectName, "db") + apiBuilds := countServiceBuilds(output, projectName, "api") + appBuilds := countServiceBuilds(output, projectName, "app") assert.Equal(t, dbBuilds, 1, "db should build once, built %d times\nOutput:\n%s", dbBuilds, output) assert.Equal(t, apiBuilds, 1, "api should build once, built %d times\nOutput:\n%s", apiBuilds, output) @@ -79,21 +70,27 @@ func TestRunBuildOnce(t *testing.T) { t.Run("service with no dependencies builds once", func(t *testing.T) { projectName := randomProjectName("build-simple") - res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-no-deps.yaml", "down", "--rmi", "local", "--remove-orphans") - res.Assert(t, icmd.Success) + _ = c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-no-deps.yaml", "down", "--rmi", "local", "--remove-orphans") + res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-no-deps.yaml", "run", "--build", "--rm", "simple") + + output := res.Stdout() - res = c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-no-deps.yaml", "run", "--build", "--rm", "simple") - res.Assert(t, icmd.Success) + simpleBuilds := countServiceBuilds(output, projectName, "simple") - // Should build exactly once - simpleBuilds := strings.Count(res.Combined(), "Simple service built at") - assert.Equal(t, simpleBuilds, 1, "simple should build once, built %d times", simpleBuilds) - assert.Assert(t, strings.Contains(res.Combined(), "Simple service")) + assert.Equal(t, simpleBuilds, 1, "simple should build once, built %d times\nOutput:\n%s", simpleBuilds, output) + assert.Assert(t, strings.Contains(res.Stdout(), "Simple service")) c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-no-deps.yaml", "down", "--remove-orphans") }) } +// countServiceBuilds counts how many times a service was built by matching +// the "naming to *{projectName}-{serviceName}* done" pattern in the output +func countServiceBuilds(output, projectName, serviceName string) int { + pattern := regexp.MustCompile(`naming to .*` + regexp.QuoteMeta(projectName) + `-` + regexp.QuoteMeta(serviceName) + `.* done`) + return len(pattern.FindAllString(output, -1)) +} + // randomProjectName generates a unique project name for parallel test execution // Format: prefix-<8 random hex chars> (e.g., "build-once-3f4a9b2c") func randomProjectName(prefix string) string { diff --git a/pkg/e2e/fixtures/run-test/build-once-no-deps.yaml b/pkg/e2e/fixtures/run-test/build-once-no-deps.yaml index bf53d951671..36f4258b380 100644 --- a/pkg/e2e/fixtures/run-test/build-once-no-deps.yaml +++ b/pkg/e2e/fixtures/run-test/build-once-no-deps.yaml @@ -5,6 +5,6 @@ services: build: dockerfile_inline: | FROM alpine - RUN echo "Simple service built at $(date)" > /build.txt + RUN echo "Simple built at $(date)" > /build.txt CMD echo "Simple service" diff --git a/pkg/e2e/fixtures/run-test/build-once.yaml b/pkg/e2e/fixtures/run-test/build-once.yaml index 1d86f875fc2..7a6f84dbc42 100644 --- a/pkg/e2e/fixtures/run-test/build-once.yaml +++ b/pkg/e2e/fixtures/run-test/build-once.yaml @@ -6,7 +6,7 @@ services: build: dockerfile_inline: | FROM alpine - RUN echo "Building nginx at $(date)" > /build-time.txt + RUN echo "Nginx built at $(date)" > /build-time.txt CMD sleep 3600 # Service that depends on nginx