Clarify startup recovery behavior and tests
- commit
- 637b278167463d556a82535603567c408eff7982
- author
- brice · · 2026-05-10 13:20 UTC
- parent
- ec88514
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +23 -59
M docs/cli/serve.md +6 -4
| @@ -114,12 +114,14 @@ sudo loginctl enable-linger $USER | |||
| 114 | 114 | After Gordon starts, including after a host reboot, it runs a best-effort recovery pass once the listeners are bound. Errors are logged, but Gordon keeps starting. | |
| 115 | 115 | ||
| 116 | 116 | 1. **Sync existing containers** - Gordon reconciles runtime state with configured routes. | |
| 117 | - | 2. **Recover configured routes** - Gordon starts any configured route that has no running container. | |
| 117 | + | 2. **Recover configured routes** - Gordon runs `AutoStart` for any configured route that has no running container. | |
| 118 | 118 | 3. **Start the background monitor** - Ongoing crash recovery resumes after the startup pass. | |
| 119 | 119 | ||
| 120 | 120 | This recovery runs even when `[auto_route].enabled = false`. `auto_route` only controls whether new image pushes create routes automatically; it does not disable restart recovery for routes already in the config. | |
| 121 | 121 | ||
| 122 | - | Recovery stays inside Gordon's own control flow instead of relying on Docker or Podman restart policies. That keeps the behavior consistent across both runtimes and applies Gordon's normal deploy checks, including fresh image pulls, readiness checks, and drain behavior when needed. | |
| 122 | + | Recovery stays inside Gordon's own control flow instead of relying on Docker or Podman restart policies. That keeps the behavior consistent across both runtimes. | |
| 123 | + | ||
| 124 | + | Startup recovery is intentionally narrower than a manual deploy. It only starts routes that are missing a running container, skips readiness checks during boot, and does not perform drain/replacement logic for routes that are already running. | |
| 123 | 125 | ||
| 124 | 126 | ### Shutdown Sequence | |
| 125 | 127 | ||
| @@ -189,11 +191,11 @@ Use `--remote` and `--token` to override. See [CLI Overview](./index.md). | |||
| 189 | 191 | ||
| 190 | 192 | ### Description | |
| 191 | 193 | ||
| 192 | - | **Local mode:** On the Gordon host, writes a deploy request and sends `SIGUSR2` to the local Gordon process. Gordon then runs the deploy inside the server through the same internal path it uses during startup recovery. | |
| 194 | + | **Local mode:** On the Gordon host, Gordon uses the explicit deploy path for the selected route. When the CLI cannot execute that path directly, it falls back to queueing the request with `SIGUSR2` for the running server. This is different from startup recovery: startup recovery uses `AutoStart`, while a manual deploy performs an explicit redeploy for the selected route. | |
| 193 | 195 | ||
| 194 | 196 | **Remote mode:** Calls the remote Gordon Admin API to trigger deployment. The remote Gordon instance still performs the actual deploy internally; the CLI only submits the request. | |
| 195 | 197 | ||
| 196 | - | Both modes use Gordon's internal deploy path: | |
| 198 | + | Both local and remote manual deploys use Gordon's explicit deploy path: | |
| 197 | 199 | ||
| 198 | 200 | - Fresh image content is pulled for the route | |
| 199 | 201 | - The specified route is redeployed | |
| @@ -114,12 +114,14 @@ sudo loginctl enable-linger $USER | |||||
| 114 | After Gordon starts, including after a host reboot, it runs a best-effort recovery pass once the listeners are bound. Errors are logged, but Gordon keeps starting. | 114 | After Gordon starts, including after a host reboot, it runs a best-effort recovery pass once the listeners are bound. Errors are logged, but Gordon keeps starting. | ||
| 115 | 115 | ||||
| 116 | 1. **Sync existing containers** - Gordon reconciles runtime state with configured routes. | 116 | 1. **Sync existing containers** - Gordon reconciles runtime state with configured routes. | ||
| 117 | - | 2. **Recover configured routes** - Gordon starts any configured route that has no running container. | 117 | + | 2. **Recover configured routes** - Gordon runs `AutoStart` for any configured route that has no running container. |
| 118 | 3. **Start the background monitor** - Ongoing crash recovery resumes after the startup pass. | 118 | 3. **Start the background monitor** - Ongoing crash recovery resumes after the startup pass. | ||
| 119 | 119 | ||||
| 120 | This recovery runs even when `[auto_route].enabled = false`. `auto_route` only controls whether new image pushes create routes automatically; it does not disable restart recovery for routes already in the config. | 120 | This recovery runs even when `[auto_route].enabled = false`. `auto_route` only controls whether new image pushes create routes automatically; it does not disable restart recovery for routes already in the config. | ||
| 121 | 121 | ||||
| 122 | - | Recovery stays inside Gordon's own control flow instead of relying on Docker or Podman restart policies. That keeps the behavior consistent across both runtimes and applies Gordon's normal deploy checks, including fresh image pulls, readiness checks, and drain behavior when needed. | 122 | + | Recovery stays inside Gordon's own control flow instead of relying on Docker or Podman restart policies. That keeps the behavior consistent across both runtimes. |
| 123 | + | ||||
| 124 | + | Startup recovery is intentionally narrower than a manual deploy. It only starts routes that are missing a running container, skips readiness checks during boot, and does not perform drain/replacement logic for routes that are already running. | |||
| 123 | 125 | ||||
| 124 | ### Shutdown Sequence | 126 | ### Shutdown Sequence | ||
| 125 | 127 | ||||
| @@ -189,11 +191,11 @@ Use `--remote` and `--token` to override. See [CLI Overview](./index.md). | |||||
| 189 | 191 | ||||
| 190 | ### Description | 192 | ### Description | ||
| 191 | 193 | ||||
| 192 | - | **Local mode:** On the Gordon host, writes a deploy request and sends `SIGUSR2` to the local Gordon process. Gordon then runs the deploy inside the server through the same internal path it uses during startup recovery. | 194 | + | **Local mode:** On the Gordon host, Gordon uses the explicit deploy path for the selected route. When the CLI cannot execute that path directly, it falls back to queueing the request with `SIGUSR2` for the running server. This is different from startup recovery: startup recovery uses `AutoStart`, while a manual deploy performs an explicit redeploy for the selected route. |
| 193 | 195 | ||||
| 194 | **Remote mode:** Calls the remote Gordon Admin API to trigger deployment. The remote Gordon instance still performs the actual deploy internally; the CLI only submits the request. | 196 | **Remote mode:** Calls the remote Gordon Admin API to trigger deployment. The remote Gordon instance still performs the actual deploy internally; the CLI only submits the request. | ||
| 195 | 197 | ||||
| 196 | - | Both modes use Gordon's internal deploy path: | 198 | + | Both local and remote manual deploys use Gordon's explicit deploy path: |
| 197 | 199 | ||||
| 198 | - Fresh image content is pulled for the route | 200 | - Fresh image content is pulled for the route | ||
| 199 | - The specified route is redeployed | 201 | - The specified route is redeployed | ||
M internal/app/startup_recovery.go +4 -2
| @@ -3,12 +3,14 @@ package app | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | ||
| 6 | - | "github.com/bnema/gordon/internal/domain" | |
| 7 | 6 | "github.com/bnema/zerowrap" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/gordon/internal/domain" | |
| 8 | 9 | ) | |
| 9 | 10 | ||
| 10 | 11 | // startupConfigService defines the route configuration needed during startup | |
| 11 | - | // recovery. | |
| 12 | + | // recovery. It intentionally excludes auto-route settings because reboot | |
| 13 | + | // recovery always works from configured routes. | |
| 12 | 14 | type startupConfigService interface { | |
| 13 | 15 | GetRoutes(ctx context.Context) []domain.Route | |
| 14 | 16 | } | |
| @@ -3,12 +3,14 @@ package app | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | 5 | ||||
| 6 | - | "github.com/bnema/gordon/internal/domain" | |||
| 7 | "github.com/bnema/zerowrap" | 6 | "github.com/bnema/zerowrap" | ||
| 7 | + | ||||
| 8 | + | "github.com/bnema/gordon/internal/domain" | |||
| 8 | ) | 9 | ) | ||
| 9 | 10 | ||||
| 10 | // startupConfigService defines the route configuration needed during startup | 11 | // startupConfigService defines the route configuration needed during startup | ||
| 11 | - | // recovery. | 12 | + | // recovery. It intentionally excludes auto-route settings because reboot |
| 13 | + | // recovery always works from configured routes. | |||
| 12 | type startupConfigService interface { | 14 | type startupConfigService interface { | ||
| 13 | GetRoutes(ctx context.Context) []domain.Route | 15 | GetRoutes(ctx context.Context) []domain.Route | ||
| 14 | } | 16 | } | ||
M internal/app/startup_recovery_test.go +6 -37
| @@ -11,27 +11,17 @@ import ( | |||
| 11 | 11 | ) | |
| 12 | 12 | ||
| 13 | 13 | type startupRecoveryFakeConfigService struct { | |
| 14 | - | calls *[]string | |
| 15 | - | routes []domain.Route | |
| 16 | - | autoRouteEnabled bool | |
| 17 | - | panicOnAutoRouteCheck bool | |
| 14 | + | calls *[]string | |
| 15 | + | routes []domain.Route | |
| 18 | 16 | } | |
| 19 | 17 | ||
| 18 | + | var _ startupConfigService = (*startupRecoveryFakeConfigService)(nil) | |
| 19 | + | ||
| 20 | 20 | func (f *startupRecoveryFakeConfigService) GetRoutes(_ context.Context) []domain.Route { | |
| 21 | 21 | *f.calls = append(*f.calls, "routes") | |
| 22 | 22 | return append([]domain.Route(nil), f.routes...) | |
| 23 | 23 | } | |
| 24 | 24 | ||
| 25 | - | func (f *startupRecoveryFakeConfigService) IsAutoRouteEnabled(_ context.Context) bool { | |
| 26 | - | if f.panicOnAutoRouteCheck { | |
| 27 | - | panic("unexpected auto-route gate during startup recovery") | |
| 28 | - | } | |
| 29 | - | if f.calls != nil { | |
| 30 | - | *f.calls = append(*f.calls, "auto-route-enabled") | |
| 31 | - | } | |
| 32 | - | return f.autoRouteEnabled | |
| 33 | - | } | |
| 34 | - | ||
| 35 | 25 | type startupRecoveryFakeContainerService struct { | |
| 36 | 26 | calls *[]string | |
| 37 | 27 | syncErr error | |
| @@ -41,6 +31,8 @@ type startupRecoveryFakeContainerService struct { | |||
| 41 | 31 | startMonitorCtx context.Context | |
| 42 | 32 | } | |
| 43 | 33 | ||
| 34 | + | var _ startupContainerService = (*startupRecoveryFakeContainerService)(nil) | |
| 35 | + | ||
| 44 | 36 | func (f *startupRecoveryFakeContainerService) SyncContainers(_ context.Context) error { | |
| 45 | 37 | *f.calls = append(*f.calls, "sync") | |
| 46 | 38 | return f.syncErr | |
| @@ -125,26 +117,3 @@ func TestSyncAndRecoverConfiguredRoutes_AutoStartFailureStillStartsMonitor(t *te | |||
| 125 | 117 | assert.True(t, domain.IsInternalDeploy(containerSvc.autoStartCtx)) | |
| 126 | 118 | assert.False(t, domain.IsInternalDeploy(containerSvc.startMonitorCtx)) | |
| 127 | 119 | } | |
| 128 | - | ||
| 129 | - | func TestSyncAndRecoverConfiguredRoutes_ProceedsEvenWhenAutoRouteDisabled(t *testing.T) { | |
| 130 | - | ctx := context.Background() | |
| 131 | - | routes := []domain.Route{{Domain: "app.example.com", Image: "reg.example.com/app:latest", HTTPS: true}} | |
| 132 | - | calls := make([]string, 0, 4) | |
| 133 | - | ||
| 134 | - | configSvc := &startupRecoveryFakeConfigService{ | |
| 135 | - | calls: &calls, | |
| 136 | - | routes: routes, | |
| 137 | - | autoRouteEnabled: false, | |
| 138 | - | panicOnAutoRouteCheck: true, | |
| 139 | - | } | |
| 140 | - | containerSvc := &startupRecoveryFakeContainerService{calls: &calls} | |
| 141 | - | ||
| 142 | - | assert.NotPanics(t, func() { | |
| 143 | - | syncAndRecoverConfiguredRoutes(ctx, configSvc, containerSvc, zerowrap.Default()) | |
| 144 | - | }) | |
| 145 | - | ||
| 146 | - | assert.Equal(t, []string{"sync", "routes", "autostart", "monitor"}, calls) | |
| 147 | - | assert.Equal(t, routes, containerSvc.autoStartRoutes) | |
| 148 | - | assert.True(t, domain.IsInternalDeploy(containerSvc.autoStartCtx)) | |
| 149 | - | assert.False(t, domain.IsInternalDeploy(containerSvc.startMonitorCtx)) | |
| 150 | - | } | |
| @@ -11,27 +11,17 @@ import ( | |||||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | type startupRecoveryFakeConfigService struct { | 13 | type startupRecoveryFakeConfigService struct { | ||
| 14 | - | calls *[]string | 14 | + | calls *[]string |
| 15 | - | routes []domain.Route | 15 | + | routes []domain.Route |
| 16 | - | autoRouteEnabled bool | |||
| 17 | - | panicOnAutoRouteCheck bool | |||
| 18 | } | 16 | } | ||
| 19 | 17 | ||||
| 18 | + | var _ startupConfigService = (*startupRecoveryFakeConfigService)(nil) | |||
| 19 | + | ||||
| 20 | func (f *startupRecoveryFakeConfigService) GetRoutes(_ context.Context) []domain.Route { | 20 | func (f *startupRecoveryFakeConfigService) GetRoutes(_ context.Context) []domain.Route { | ||
| 21 | *f.calls = append(*f.calls, "routes") | 21 | *f.calls = append(*f.calls, "routes") | ||
| 22 | return append([]domain.Route(nil), f.routes...) | 22 | return append([]domain.Route(nil), f.routes...) | ||
| 23 | } | 23 | } | ||
| 24 | 24 | ||||
| 25 | - | func (f *startupRecoveryFakeConfigService) IsAutoRouteEnabled(_ context.Context) bool { | |||
| 26 | - | if f.panicOnAutoRouteCheck { | |||
| 27 | - | panic("unexpected auto-route gate during startup recovery") | |||
| 28 | - | } | |||
| 29 | - | if f.calls != nil { | |||
| 30 | - | *f.calls = append(*f.calls, "auto-route-enabled") | |||
| 31 | - | } | |||
| 32 | - | return f.autoRouteEnabled | |||
| 33 | - | } | |||
| 34 | - | ||||
| 35 | type startupRecoveryFakeContainerService struct { | 25 | type startupRecoveryFakeContainerService struct { | ||
| 36 | calls *[]string | 26 | calls *[]string | ||
| 37 | syncErr error | 27 | syncErr error | ||
| @@ -41,6 +31,8 @@ type startupRecoveryFakeContainerService struct { | |||||
| 41 | startMonitorCtx context.Context | 31 | startMonitorCtx context.Context | ||
| 42 | } | 32 | } | ||
| 43 | 33 | ||||
| 34 | + | var _ startupContainerService = (*startupRecoveryFakeContainerService)(nil) | |||
| 35 | + | ||||
| 44 | func (f *startupRecoveryFakeContainerService) SyncContainers(_ context.Context) error { | 36 | func (f *startupRecoveryFakeContainerService) SyncContainers(_ context.Context) error { | ||
| 45 | *f.calls = append(*f.calls, "sync") | 37 | *f.calls = append(*f.calls, "sync") | ||
| 46 | return f.syncErr | 38 | return f.syncErr | ||
| @@ -125,26 +117,3 @@ func TestSyncAndRecoverConfiguredRoutes_AutoStartFailureStillStartsMonitor(t *te | |||||
| 125 | assert.True(t, domain.IsInternalDeploy(containerSvc.autoStartCtx)) | 117 | assert.True(t, domain.IsInternalDeploy(containerSvc.autoStartCtx)) | ||
| 126 | assert.False(t, domain.IsInternalDeploy(containerSvc.startMonitorCtx)) | 118 | assert.False(t, domain.IsInternalDeploy(containerSvc.startMonitorCtx)) | ||
| 127 | } | 119 | } | ||
| 128 | - | ||||
| 129 | - | func TestSyncAndRecoverConfiguredRoutes_ProceedsEvenWhenAutoRouteDisabled(t *testing.T) { | |||
| 130 | - | ctx := context.Background() | |||
| 131 | - | routes := []domain.Route{{Domain: "app.example.com", Image: "reg.example.com/app:latest", HTTPS: true}} | |||
| 132 | - | calls := make([]string, 0, 4) | |||
| 133 | - | ||||
| 134 | - | configSvc := &startupRecoveryFakeConfigService{ | |||
| 135 | - | calls: &calls, | |||
| 136 | - | routes: routes, | |||
| 137 | - | autoRouteEnabled: false, | |||
| 138 | - | panicOnAutoRouteCheck: true, | |||
| 139 | - | } | |||
| 140 | - | containerSvc := &startupRecoveryFakeContainerService{calls: &calls} | |||
| 141 | - | ||||
| 142 | - | assert.NotPanics(t, func() { | |||
| 143 | - | syncAndRecoverConfiguredRoutes(ctx, configSvc, containerSvc, zerowrap.Default()) | |||
| 144 | - | }) | |||
| 145 | - | ||||
| 146 | - | assert.Equal(t, []string{"sync", "routes", "autostart", "monitor"}, calls) | |||
| 147 | - | assert.Equal(t, routes, containerSvc.autoStartRoutes) | |||
| 148 | - | assert.True(t, domain.IsInternalDeploy(containerSvc.autoStartCtx)) | |||
| 149 | - | assert.False(t, domain.IsInternalDeploy(containerSvc.startMonitorCtx)) | |||
| 150 | - | } | |||
M internal/domain/registry_image_test.go +7 -16
| @@ -1,9 +1,10 @@ | |||
| 1 | 1 | package domain_test | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "reflect" | |
| 5 | 4 | "testing" | |
| 6 | 5 | ||
| 6 | + | "github.com/stretchr/testify/assert" | |
| 7 | + | ||
| 7 | 8 | "github.com/bnema/gordon/internal/domain" | |
| 8 | 9 | ) | |
| 9 | 10 | ||
| @@ -22,9 +23,7 @@ func TestRegistryImageKnownGordonRegistryDomains(t *testing.T) { | |||
| 22 | 23 | "old-registry.example.com:5000", | |
| 23 | 24 | } | |
| 24 | 25 | ||
| 25 | - | if !reflect.DeepEqual(got, want) { | |
| 26 | - | t.Fatalf("KnownGordonRegistryDomains() = %v, want %v", got, want) | |
| 27 | - | } | |
| 26 | + | assert.Equal(t, want, got) | |
| 28 | 27 | } | |
| 29 | 28 | ||
| 30 | 29 | func TestRegistryImageStripKnownGordonRegistry(t *testing.T) { | |
| @@ -48,9 +47,7 @@ func TestRegistryImageStripKnownGordonRegistry(t *testing.T) { | |||
| 48 | 47 | for _, tt := range tests { | |
| 49 | 48 | t.Run(tt.name, func(t *testing.T) { | |
| 50 | 49 | got := domain.StripKnownGordonRegistry(tt.imageRef, current, legacy) | |
| 51 | - | if got != tt.want { | |
| 52 | - | t.Fatalf("StripKnownGordonRegistry(%q) = %q, want %q", tt.imageRef, got, tt.want) | |
| 53 | - | } | |
| 50 | + | assert.Equal(t, tt.want, got) | |
| 54 | 51 | }) | |
| 55 | 52 | } | |
| 56 | 53 | } | |
| @@ -75,9 +72,7 @@ func TestRegistryImageExtractGordonRepoName(t *testing.T) { | |||
| 75 | 72 | for _, tt := range tests { | |
| 76 | 73 | t.Run(tt.name, func(t *testing.T) { | |
| 77 | 74 | got := domain.ExtractGordonRepoName(tt.imageRef, current, legacy) | |
| 78 | - | if got != tt.want { | |
| 79 | - | t.Fatalf("ExtractGordonRepoName(%q) = %q, want %q", tt.imageRef, got, tt.want) | |
| 80 | - | } | |
| 75 | + | assert.Equal(t, tt.want, got) | |
| 81 | 76 | }) | |
| 82 | 77 | } | |
| 83 | 78 | } | |
| @@ -101,9 +96,7 @@ func TestRegistryImageCanonicalizeGordonImageRef(t *testing.T) { | |||
| 101 | 96 | for _, tt := range tests { | |
| 102 | 97 | t.Run(tt.name, func(t *testing.T) { | |
| 103 | 98 | got := domain.CanonicalizeGordonImageRef(tt.imageRef, tt.current, legacy) | |
| 104 | - | if got != tt.want { | |
| 105 | - | t.Fatalf("CanonicalizeGordonImageRef(%q) = %q, want %q", tt.imageRef, got, tt.want) | |
| 106 | - | } | |
| 99 | + | assert.Equal(t, tt.want, got) | |
| 107 | 100 | }) | |
| 108 | 101 | } | |
| 109 | 102 | } | |
| @@ -128,9 +121,7 @@ func TestRegistryImageIsGordonRegistryImageRef(t *testing.T) { | |||
| 128 | 121 | for _, tt := range tests { | |
| 129 | 122 | t.Run(tt.name, func(t *testing.T) { | |
| 130 | 123 | got := domain.IsGordonRegistryImageRef(tt.imageRef, current, legacy) | |
| 131 | - | if got != tt.want { | |
| 132 | - | t.Fatalf("IsGordonRegistryImageRef(%q) = %t, want %t", tt.imageRef, got, tt.want) | |
| 133 | - | } | |
| 124 | + | assert.Equal(t, tt.want, got) | |
| 134 | 125 | }) | |
| 135 | 126 | } | |
| 136 | 127 | } | |
| @@ -1,9 +1,10 @@ | |||||
| 1 | package domain_test | 1 | package domain_test | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "reflect" | |||
| 5 | "testing" | 4 | "testing" | ||
| 6 | 5 | ||||
| 6 | + | "github.com/stretchr/testify/assert" | |||
| 7 | + | ||||
| 7 | "github.com/bnema/gordon/internal/domain" | 8 | "github.com/bnema/gordon/internal/domain" | ||
| 8 | ) | 9 | ) | ||
| 9 | 10 | ||||
| @@ -22,9 +23,7 @@ func TestRegistryImageKnownGordonRegistryDomains(t *testing.T) { | |||||
| 22 | "old-registry.example.com:5000", | 23 | "old-registry.example.com:5000", | ||
| 23 | } | 24 | } | ||
| 24 | 25 | ||||
| 25 | - | if !reflect.DeepEqual(got, want) { | 26 | + | assert.Equal(t, want, got) |
| 26 | - | t.Fatalf("KnownGordonRegistryDomains() = %v, want %v", got, want) | |||
| 27 | - | } | |||
| 28 | } | 27 | } | ||
| 29 | 28 | ||||
| 30 | func TestRegistryImageStripKnownGordonRegistry(t *testing.T) { | 29 | func TestRegistryImageStripKnownGordonRegistry(t *testing.T) { | ||
| @@ -48,9 +47,7 @@ func TestRegistryImageStripKnownGordonRegistry(t *testing.T) { | |||||
| 48 | for _, tt := range tests { | 47 | for _, tt := range tests { | ||
| 49 | t.Run(tt.name, func(t *testing.T) { | 48 | t.Run(tt.name, func(t *testing.T) { | ||
| 50 | got := domain.StripKnownGordonRegistry(tt.imageRef, current, legacy) | 49 | got := domain.StripKnownGordonRegistry(tt.imageRef, current, legacy) | ||
| 51 | - | if got != tt.want { | 50 | + | assert.Equal(t, tt.want, got) |
| 52 | - | t.Fatalf("StripKnownGordonRegistry(%q) = %q, want %q", tt.imageRef, got, tt.want) | |||
| 53 | - | } | |||
| 54 | }) | 51 | }) | ||
| 55 | } | 52 | } | ||
| 56 | } | 53 | } | ||
| @@ -75,9 +72,7 @@ func TestRegistryImageExtractGordonRepoName(t *testing.T) { | |||||
| 75 | for _, tt := range tests { | 72 | for _, tt := range tests { | ||
| 76 | t.Run(tt.name, func(t *testing.T) { | 73 | t.Run(tt.name, func(t *testing.T) { | ||
| 77 | got := domain.ExtractGordonRepoName(tt.imageRef, current, legacy) | 74 | got := domain.ExtractGordonRepoName(tt.imageRef, current, legacy) | ||
| 78 | - | if got != tt.want { | 75 | + | assert.Equal(t, tt.want, got) |
| 79 | - | t.Fatalf("ExtractGordonRepoName(%q) = %q, want %q", tt.imageRef, got, tt.want) | |||
| 80 | - | } | |||
| 81 | }) | 76 | }) | ||
| 82 | } | 77 | } | ||
| 83 | } | 78 | } | ||
| @@ -101,9 +96,7 @@ func TestRegistryImageCanonicalizeGordonImageRef(t *testing.T) { | |||||
| 101 | for _, tt := range tests { | 96 | for _, tt := range tests { | ||
| 102 | t.Run(tt.name, func(t *testing.T) { | 97 | t.Run(tt.name, func(t *testing.T) { | ||
| 103 | got := domain.CanonicalizeGordonImageRef(tt.imageRef, tt.current, legacy) | 98 | got := domain.CanonicalizeGordonImageRef(tt.imageRef, tt.current, legacy) | ||
| 104 | - | if got != tt.want { | 99 | + | assert.Equal(t, tt.want, got) |
| 105 | - | t.Fatalf("CanonicalizeGordonImageRef(%q) = %q, want %q", tt.imageRef, got, tt.want) | |||
| 106 | - | } | |||
| 107 | }) | 100 | }) | ||
| 108 | } | 101 | } | ||
| 109 | } | 102 | } | ||
| @@ -128,9 +121,7 @@ func TestRegistryImageIsGordonRegistryImageRef(t *testing.T) { | |||||
| 128 | for _, tt := range tests { | 121 | for _, tt := range tests { | ||
| 129 | t.Run(tt.name, func(t *testing.T) { | 122 | t.Run(tt.name, func(t *testing.T) { | ||
| 130 | got := domain.IsGordonRegistryImageRef(tt.imageRef, current, legacy) | 123 | got := domain.IsGordonRegistryImageRef(tt.imageRef, current, legacy) | ||
| 131 | - | if got != tt.want { | 124 | + | assert.Equal(t, tt.want, got) |
| 132 | - | t.Fatalf("IsGordonRegistryImageRef(%q) = %t, want %t", tt.imageRef, got, tt.want) | |||
| 133 | - | } | |||
| 134 | }) | 125 | }) | ||
| 135 | } | 126 | } | ||
| 136 | } | 127 | } | ||