Skip to content

Wait for the Flyway container and split the container lifecycle into segregated collaborators. - #30

Merged
gustavofreze merged 2 commits into
mainfrom
feat/wait-for-flyway-and-segregate-lifecycle
Oct 15, 2026
Merged

gustavofreze merged 2 commits into
mainfrom
feat/wait-for-flyway-and-segregate-lifecycle

Conversation

@gustavofreze

@gustavofreze gustavofreze commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Summary

FlywayDockerContainer::cleanAndMigrate slept a fixed 10 seconds after it started the Flyway container instead of waiting for it to finish. Under load, the integration suites of the services that consume the library started before the migrations ended and failed with SQLSTATE[42S02] Base table or view not found and ContainerWaitTimeout. I reproduced it with a migration that runs DO SLEEP(15), and the old code failed with Table 'test_adm.slow' doesn't exist.

A second race sat in front of it. The container started before docker cp copied the migrations, so Flyway could read an empty folder, exit with 0 and apply nothing.

The refactor also found four defects. The CREATE DATABASE and GRANT setup of the MySQL container never ran, because it reached docker exec as a single argument and failed silently. runIfNotExists() did not wait for the database. run() ignored $waitAfterStarted. pullImage() never ran in the background, against what the README said.

What changes

  1. cleanAndMigrate, migrate, repair and validate run the Flyway container to completion, with a timeout of 120 seconds by default. A failure raises ContainerExitUnsuccessful with the stderr of Flyway, a timeout raises ContainerWaitTimeout, and the container is removed on success, failure and timeout.
  2. A container is created, receives its docker cp and only then starts.
  3. Every duration is a TinyBlocks\Time\Duration. The lifecycle is split into small immutable collaborators with one responsibility each: ContainerLauncher, OneShotContainer, ContainerSharing, NetworkProvisioner, MySQLStartup, JdbcUrl, StrictExecutor and CommandLine. The sequence of create, copy, start, wait and read the exit lives once, in runToCompletion.
  4. No interface asks an implementation to answer a method with null or a no-op. DockerCommand keeps only toArguments(), and DockerCommandWithTimeout adds timeout(): Duration for the two commands that have one. ContainerStarted keeps what every started container does, DisposableContainer adds remove and stopOnShutdown, and SharedContainer adds wasReused.
  5. The public DockerClient and DockerCommand are the seams to Docker. They replace the protected constructor and the Testable* subclasses that existed only for tests.
  6. infection/infection goes to ^0.35, which fixes the cacheResult deprecation raised by PHPUnit 13.3. The --coverage argument leaves the tests script, because it handed the timings of the integration tests to the mutation run and 240 of 313 mutants were skipped by time.

Public API

The change is breaking, so the release is 4.0.0.

Removed: DockerContainer, MySQLContainer and FlywayContainer, runWhen with EnvironmentFlag, pullImage() and withCleanDisabled().

Changed: from(image, ?name) becomes from(image) plus withName(). run() returns a DisposableContainer and runIfNotExists() returns a SharedContainer. The Flyway verbs return an ExecutionCompleted with the output. withValidateMigrationNaming(bool) becomes withMigrationNamingValidation(). The exceptions moved from Internal\Exceptions to Exceptions.

The README has a table to migrate from 3.x.

Names scoped to the runner

A service that runs two suites at once should give its containers a name scoped to the runner, for example sprintf('<service>-adm-test-%s', gethostname()). Two runs that share a fixed name share one MySQL, and one of them cleans the schema the other is using. The README shows the bootstrap.

Related issue

None.

Checklist

  • Tests added or updated. A slow migration that makes the old code fail, the order of create, copy and start with a file that must exist when the container starts, the exit codes, the timeouts, the removal on success, failure and timeout, and the segregated interfaces.
  • Documentation updated when applicable. README: the 4.0 API, the migration table from 3.x and a FAQ on the names scoped to the runner.
  • make review passes.
  • make tests passes. 161 tests, lines 100% (687/687), MSI 100% (284 of 284 killed).

@gustavofreze
gustavofreze merged commit 28be71f into main Oct 15, 2026
5 checks passed
@gustavofreze
gustavofreze deleted the feat/wait-for-flyway-and-segregate-lifecycle branch October 15, 2026 01:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant