diff --git a/pkg/workflow/compiler_custom_job_memory_test.go b/pkg/workflow/compiler_custom_job_memory_test.go index 1023115dbf5..de2cdcf9388 100644 --- a/pkg/workflow/compiler_custom_job_memory_test.go +++ b/pkg/workflow/compiler_custom_job_memory_test.go @@ -81,6 +81,103 @@ Reads cache memory and dispatches tasks. assert.NotContains(t, yamlStr, "update_cache_memory:", "update_cache_memory job should not be created without threat detection") } +// TestCustomJobRestoreMemoryUsesDefaultRunsOn verifies that custom restore-memory +// jobs inherit the workflow runner when runs-on is omitted. +func TestCustomJobRestoreMemoryUsesDefaultRunsOn(t *testing.T) { + tmpDir := testutil.TempDir(t, "custom-job-restore-default-runs-on") + + frontmatter := `--- +name: Orchestrator +on: workflow_dispatch +permissions: + contents: read +engine: copilot +strict: false +tools: + cache-memory: true +jobs: + setup: + restore-memory: true + steps: + - name: Verify setup + run: echo "ok" +--- + +# Orchestrator Workflow +` + + testFile := filepath.Join(tmpDir, "test.md") + require.NoError(t, os.WriteFile(testFile, []byte(frontmatter), 0644)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(testFile)) + + lockFile := filepath.Join(tmpDir, "test.lock.yml") + content, err := os.ReadFile(lockFile) + require.NoError(t, err) + + section := extractJobSection(string(content), "setup") + require.NotEmpty(t, section, "Expected setup job section in lock file") + assert.Contains(t, section, "runs-on: ubuntu-latest", "custom job should inherit default workflow runner when runs-on is omitted") + assert.Contains(t, section, "Restore cache-memory", "restore-memory steps should still be present") +} + +// TestCustomJobRestoreMemoryInheritsArrayRunsOn verifies that a custom job with +// restore-memory: true and no explicit runs-on properly inherits a workflow-level +// array/object runs-on, with continuation lines correctly indented. +func TestCustomJobRestoreMemoryInheritsArrayRunsOn(t *testing.T) { + tmpDir := testutil.TempDir(t, "custom-job-restore-array-runs-on") + + frontmatter := `--- +name: Orchestrator +on: workflow_dispatch +permissions: + contents: read +engine: copilot +strict: false +runs-on: + group: ubuntu-runners + labels: [self-hosted] +tools: + cache-memory: true +jobs: + setup: + restore-memory: true + steps: + - name: Verify setup + run: echo "ok" +--- + +# Orchestrator Workflow +` + + testFile := filepath.Join(tmpDir, "test.md") + require.NoError(t, os.WriteFile(testFile, []byte(frontmatter), 0644)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(testFile)) + + lockFile := filepath.Join(tmpDir, "test.lock.yml") + content, err := os.ReadFile(lockFile) + require.NoError(t, err) + + section := extractJobSection(string(content), "setup") + require.NotEmpty(t, section, "Expected setup job section in lock file") + assert.Contains(t, section, "runs-on:", "custom job should inherit workflow-level runs-on") + assert.Contains(t, section, "group: ubuntu-runners", "custom job should include group from inherited runs-on") + // Verify continuation lines are properly indented (4 spaces) inside the job section. + for line := range strings.SplitSeq(section, "\n") { + if strings.TrimSpace(line) == "" || strings.HasPrefix(strings.TrimSpace(line), "setup:") { + continue + } + if strings.TrimSpace(line) != "" { + assert.True(t, strings.HasPrefix(line, " "), + "all non-empty lines inside job section should be indented with at least 4 spaces, got: %q", line) + } + } + assert.Contains(t, section, "Restore cache-memory", "restore-memory steps should still be present") +} + // TestCustomJobRestoreMemoryRepoMemory verifies that a custom job with // restore-memory: true gets repo-memory clone steps injected when repo-memory is configured. func TestCustomJobRestoreMemoryRepoMemory(t *testing.T) { diff --git a/pkg/workflow/compiler_custom_jobs.go b/pkg/workflow/compiler_custom_jobs.go index 5ba478129f5..ec80f26e5c5 100644 --- a/pkg/workflow/compiler_custom_jobs.go +++ b/pkg/workflow/compiler_custom_jobs.go @@ -477,6 +477,13 @@ func configureCustomReusableWorkflow(job *Job, jobName string, usesStr string, c } func (c *Compiler) configureCustomJobSteps(job *Job, jobName string, configMap map[string]any, data *WorkflowData) error { + if job.RunsOn == "" { + job.RunsOn = c.indentYAMLLines(data.RunsOn, " ") + if job.RunsOn == "" { + job.RunsOn = "runs-on: ubuntu-latest" + } + } + // Add basic steps if specified (only for non-reusable workflow jobs). // `setup-steps` and `pre-steps` stay distinct so setup-steps can remain the // first injected steps in the job, followed by compiler scaffolding,