TestWatchRebuildIgnoresDependencies verifies that when `compose up --watch` rebuilds a service after a file change, the rebuild does NOT cascade to its `depends_on` dependencies. Reproduces docker/compose#13853: `up --build` sets BuildOptions.Deps=true to build images for dependencies on initial st
(t *testing.T)
| 380 | // The test scans build progress output between the "Rebuilding service(s)" and |
| 381 | // "successfully built" markers and asserts only the watched service appears. |
| 382 | func TestWatchRebuildIgnoresDependencies(t *testing.T) { |
| 383 | c := NewCLI(t) |
| 384 | const projectName = "test_watch_rebuild_deps" |
| 385 | |
| 386 | defer c.cleanupWithDown(t, projectName) |
| 387 | |
| 388 | tmpdir := t.TempDir() |
| 389 | composeFilePath := filepath.Join(tmpdir, "compose.yaml") |
| 390 | CopyFile(t, filepath.Join("fixtures", "watch", "rebuild-deps.yaml"), composeFilePath) |
| 391 | |
| 392 | testFile := filepath.Join(tmpdir, "test") |
| 393 | assert.NilError(t, os.WriteFile(testFile, []byte("initial"), 0o600)) |
| 394 | |
| 395 | cmd := c.NewDockerComposeCmd(t, "-p", projectName, "-f", composeFilePath, "up", "--build", "--watch") |
| 396 | buffer := bytes.NewBuffer(nil) |
| 397 | cmd.Stdout = buffer |
| 398 | cmd.Stderr = buffer |
| 399 | watch := icmd.StartCmd(cmd) |
| 400 | assert.NilError(t, watch.Error) |
| 401 | t.Cleanup(func() { |
| 402 | if watch.Cmd.Process != nil { |
| 403 | _ = watch.Cmd.Process.Kill() |
| 404 | } |
| 405 | }) |
| 406 | |
| 407 | // Wait until the watcher is actually running. "Watch enabled" is logged |
| 408 | // AFTER the initial up (and its build output) is done, so anchoring the |
| 409 | // cutoff here keeps initial-build noise out of the rebuild assertions. |
| 410 | poll.WaitOn(t, func(l poll.LogT) poll.Result { |
| 411 | if strings.Contains(buffer.String(), "Watch enabled") { |
| 412 | return poll.Success() |
| 413 | } |
| 414 | return poll.Continue("waiting for watch to start: %v", buffer.String()) |
| 415 | }, poll.WithTimeout(120*time.Second)) |
| 416 | |
| 417 | // Record the cutoff point in the log buffer so we only inspect output |
| 418 | // produced AFTER the file change triggers the rebuild. |
| 419 | logCutoff := buffer.Len() |
| 420 | |
| 421 | // Trigger a rebuild of the frontend (only) by modifying the watched file. |
| 422 | assert.NilError(t, os.WriteFile(testFile, []byte("updated"), 0o600)) |
| 423 | |
| 424 | // Wait until the rebuild is reported as completed. |
| 425 | poll.WaitOn(t, func(l poll.LogT) poll.Result { |
| 426 | out := buffer.String() |
| 427 | if len(out) <= logCutoff { |
| 428 | return poll.Continue("no new output yet") |
| 429 | } |
| 430 | if strings.Contains(out[logCutoff:], `service(s) ["frontend"] successfully built`) { |
| 431 | return poll.Success() |
| 432 | } |
| 433 | return poll.Continue("waiting for rebuild to finish: %v", out[logCutoff:]) |
| 434 | }, poll.WithTimeout(120*time.Second), poll.WithDelay(time.Second)) |
| 435 | |
| 436 | rebuildLog := buffer.String()[logCutoff:] |
| 437 | |
| 438 | // The watch rebuild must only touch the frontend service. The backend is |
| 439 | // an upstream dependency and must not be rebuilt. |
nothing calls this directly
no test coverage detected