@@ -5226,6 +5226,117 @@ func TestReconcileCascadingRestartOrderingPreservesLogDrivers(t *testing.T) {
52265226` )
52275227}
52285228
5229+ // ---------------------------------------------------------------------------
5230+ // Cascading restart — StartContainers flag behavior
5231+ // ---------------------------------------------------------------------------
5232+
5233+ // TestReconcileCascadingRestartWithStartContainers verifies that when
5234+ // StartContainers is true, the cascading restart produces both stop AND start
5235+ // operations for the dependent service in the plan. The start waits for the
5236+ // dependency to be fully recreated.
5237+ func TestReconcileCascadingRestartWithStartContainers (t * testing.T ) {
5238+ fluentbit := types.ServiceConfig {Name : "fluentbit" , Image : "fluent/fluent-bit:latest" }
5239+ app := types.ServiceConfig {
5240+ Name : "app" , Image : "nginx" ,
5241+ DependsOn : types.DependsOnConfig {
5242+ "fluentbit" : {Condition : "service_started" , Restart : true },
5243+ },
5244+ }
5245+ appHash , err := ServiceHash (app )
5246+ assert .NilError (t , err )
5247+
5248+ project := & types.Project {
5249+ Name : "tp" ,
5250+ Services : types.Services {"fluentbit" : fluentbit , "app" : app },
5251+ }
5252+ observed := & ObservedState {
5253+ ProjectName : "tp" ,
5254+ Containers : map [string ]Containers {
5255+ "fluentbit" : {makeContainer ("tp" , "fluentbit" , 1 , "stale" )},
5256+ "app" : {makeContainer ("tp" , "app" , 1 , appHash )},
5257+ },
5258+ Networks : map [string ]ObservedNetwork {},
5259+ Volumes : map [string ]ObservedVolume {},
5260+ Orphans : Containers {},
5261+ }
5262+
5263+ plan , err := Reconcile (project , observed , ReconcileOptions {
5264+ StartContainers : true ,
5265+ Recreate : api .RecreateDiverged ,
5266+ RecreateDependencies : api .RecreateDiverged ,
5267+ })
5268+ assert .NilError (t , err )
5269+ assert .Equal (t , plan .String (), `
5270+ 1. emit event tp-app-1 reason: Stopping
5271+ [1] -> 2. stop container tp-app-1 reason: dependency "fluentbit" is being recreated (restart: true)
5272+ [2] -> 3. emit event tp-app-1 reason: Stopped
5273+ [3] -> 4. emit event tp-fluentbit-1 reason: Recreate
5274+ [3] -> 5. emit event tp-app-1 reason: Starting
5275+ [4] -> 6. create container tp-fluentbit_tp-fluentbit-1 reason: config hash changed
5276+ [6] -> 7. stop container tp-fluentbit-1 reason: config hash changed
5277+ [7] -> 8. remove container tp-fluentbit-1 reason: config hash changed
5278+ [8] -> 9. rename container tp-fluentbit-1 reason: config hash changed
5279+ [9] -> 10. start container tp-fluentbit-1 reason: config hash changed
5280+ [10] -> 11. emit event tp-fluentbit-1 reason: Recreated
5281+ [11,5] -> 12. start container tp-app-1 reason: restart after dependency "fluentbit" recreated
5282+ [12] -> 13. emit event tp-app-1 reason: Started
5283+ ` )
5284+ }
5285+
5286+ // TestReconcileCascadingRestartWithoutStartContainers verifies that when
5287+ // StartContainers is false (docker compose create, used by up -d), the
5288+ // cascading restart produces only the STOP operations for the dependent.
5289+ // The start is NOT in the plan — it is delegated to startService via
5290+ // InDependencyOrder, which respects depends_on conditions and gives
5291+ // services time to become ready (e.g. fluentd logging driver needs the
5292+ // dependency to be listening before the dependent can start).
5293+ func TestReconcileCascadingRestartWithoutStartContainers (t * testing.T ) {
5294+ fluentbit := types.ServiceConfig {Name : "fluentbit" , Image : "fluent/fluent-bit:latest" }
5295+ app := types.ServiceConfig {
5296+ Name : "app" , Image : "nginx" ,
5297+ DependsOn : types.DependsOnConfig {
5298+ "fluentbit" : {Condition : "service_started" , Restart : true },
5299+ },
5300+ }
5301+ appHash , err := ServiceHash (app )
5302+ assert .NilError (t , err )
5303+
5304+ project := & types.Project {
5305+ Name : "tp" ,
5306+ Services : types.Services {"fluentbit" : fluentbit , "app" : app },
5307+ }
5308+ observed := & ObservedState {
5309+ ProjectName : "tp" ,
5310+ Containers : map [string ]Containers {
5311+ "fluentbit" : {makeContainer ("tp" , "fluentbit" , 1 , "stale" )},
5312+ "app" : {makeContainer ("tp" , "app" , 1 , appHash )},
5313+ },
5314+ Networks : map [string ]ObservedNetwork {},
5315+ Volumes : map [string ]ObservedVolume {},
5316+ Orphans : Containers {},
5317+ }
5318+
5319+ plan , err := Reconcile (project , observed , ReconcileOptions {
5320+ StartContainers : false ,
5321+ Recreate : api .RecreateDiverged ,
5322+ RecreateDependencies : api .RecreateDiverged ,
5323+ })
5324+ assert .NilError (t , err )
5325+ // No start ops for app — only stop + recreate of dependency.
5326+ // The start of app will be handled by startService after ExecutePlan.
5327+ assert .Equal (t , plan .String (), `
5328+ 1. emit event tp-app-1 reason: Stopping
5329+ [1] -> 2. stop container tp-app-1 reason: dependency "fluentbit" is being recreated (restart: true)
5330+ [2] -> 3. emit event tp-app-1 reason: Stopped
5331+ [3] -> 4. emit event tp-fluentbit-1 reason: Recreate
5332+ [4] -> 5. create container tp-fluentbit_tp-fluentbit-1 reason: config hash changed
5333+ [5] -> 6. stop container tp-fluentbit-1 reason: config hash changed
5334+ [6] -> 7. remove container tp-fluentbit-1 reason: config hash changed
5335+ [7] -> 8. rename container tp-fluentbit-1 reason: config hash changed
5336+ [8] -> 9. emit event tp-fluentbit-1 reason: Recreated
5337+ ` )
5338+ }
5339+
52295340// ---------------------------------------------------------------------------
52305341// Test helpers
52315342// ---------------------------------------------------------------------------
0 commit comments