diff --git a/pkg/container/docker_run.go b/pkg/container/docker_run.go index af39c991e54..a505726387e 100644 --- a/pkg/container/docker_run.go +++ b/pkg/container/docker_run.go @@ -170,8 +170,26 @@ func (cr *containerReference) Remove() common.Executor { } func (cr *containerReference) GetHealth(ctx context.Context) Health { - resp, err := cr.cli.ContainerInspect(ctx, cr.id) logger := common.Logger(ctx) + + // In dry-run mode, containers are not actually created, so return healthy status + if common.Dryrun(ctx) { + logger.Debugf("Dry-run mode: returning healthy status for container health check") + return HealthHealthy + } + + // Add safety checks for nil Docker client and empty container ID + if cr.cli == nil { + logger.Errorf("Docker client is nil, cannot inspect container health") + return HealthUnHealthy + } + + if cr.id == "" { + logger.Debugf("Container ID is empty, cannot inspect container health") + return HealthUnHealthy + } + + resp, err := cr.cli.ContainerInspect(ctx, cr.id) if err != nil { logger.Errorf("failed to query container health %s", err) return HealthUnHealthy diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index f6e4dec2755..3334f155ced 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -577,6 +577,14 @@ func (rc *RunContext) startServiceContainers(_ string) common.Executor { func (rc *RunContext) waitForServiceContainer(c container.ExecutionsEnvironment) common.Executor { return func(ctx context.Context) error { + logger := common.Logger(ctx) + + // In dry-run mode, service containers are not actually created, so skip health checks + if common.Dryrun(ctx) { + logger.Debugf("Dry-run mode: skipping service container health check") + return nil + } + sctx, cancel := context.WithTimeout(ctx, time.Minute*5) defer cancel() health := container.HealthStarting diff --git a/pkg/runner/run_context_dryrun_test.go b/pkg/runner/run_context_dryrun_test.go new file mode 100644 index 00000000000..f7ef863a947 --- /dev/null +++ b/pkg/runner/run_context_dryrun_test.go @@ -0,0 +1,48 @@ +package runner + +import ( + "context" + "testing" + + "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/container" + "github.com/stretchr/testify/assert" +) + +func TestServiceContainerWorkflowDryRun(t *testing.T) { + // Test the actual functionality: service containers should work in dry-run mode + // This reproduces the exact scenario that was causing segmentation faults + + ctx := common.WithDryrun(context.Background(), true) + + // Create service containers like the workflow that was failing + serviceContainers := []container.ExecutionsEnvironment{ + container.NewContainer(&container.NewContainerInput{ + Image: "postgres:15-alpine", + Name: "postgres-service", + }), + } + + // Create RunContext with service containers (the actual failing scenario) + rc := &RunContext{ + ServiceContainers: serviceContainers, + } + + // Test the actual workflow that was crashing: + // 1. waitForServiceContainers calls waitForServiceContainer for each service + // 2. waitForServiceContainer calls GetHealth() in a timeout loop + // 3. GetHealth() should handle the case where Docker client isn't available (dry-run) + + // This should complete without segmentation fault + err := rc.waitForServiceContainers()(ctx) + assert.NoError(t, err, "Service container workflow should work in dry-run mode") + + // Verify that individual health checks also work (the core of the bug) + for _, serviceContainer := range serviceContainers { + health := serviceContainer.GetHealth(ctx) + // In dry-run mode, we expect containers to report as healthy + // (since they're not actually running, we mock them as ready) + assert.Equal(t, container.HealthHealthy, health, + "Service containers should report healthy in dry-run mode") + } +}