Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pkg/workflow/call_workflow_permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,9 @@ jobs:
_, err := compiler.buildCallWorkflowJobs(workflowData, markdownPath)
require.NoError(t, err, "Should build jobs without error")

yamlOutput := compiler.jobManager.RenderToYAML()
var yamlBuf strings.Builder
compiler.jobManager.WriteJobsYAML(&yamlBuf)
yamlOutput := yamlBuf.String()

assert.Contains(t, yamlOutput, "uses: ./.github/workflows/worker-a.lock.yml", "Should contain uses directive")
assert.Contains(t, yamlOutput, "secrets: inherit", "Should inherit secrets")
Expand Down
4 changes: 3 additions & 1 deletion pkg/workflow/compiler_jobs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3287,7 +3287,9 @@ func TestBuildCustomJobsAllNewFieldsViaWorkflowData(t *testing.T) {
if err := jm.AddJob(job); err != nil {
t.Fatalf("AddJob() error: %v", err)
}
rendered := jm.RenderToYAML()
var renderedBuf strings.Builder
jm.WriteJobsYAML(&renderedBuf)
rendered := renderedBuf.String()

renderedChecks := []string{
"name: My Display Name",
Expand Down
16 changes: 12 additions & 4 deletions pkg/workflow/if_expression_clean_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ github.event_name == 'issue_comment'`,
t.Fatalf("Failed to add job: %v", err)
}

yaml := jm.renderJob(job)
var yamlBuf strings.Builder
jm.renderJobTo(&yamlBuf, job)
yaml := yamlBuf.String()

if tt.expression == "" {
// Empty expressions should not render if condition
Expand Down Expand Up @@ -126,7 +128,9 @@ func TestMultilineExpressionYAMLFolding(t *testing.T) {
t.Fatalf("Failed to add job: %v", err)
}

yaml := jm.renderJob(job)
var yamlBuf strings.Builder
jm.renderJobTo(&yamlBuf, job)
yaml := yamlBuf.String()

// Should use folded style for multiline
if !strings.Contains(yaml, "if: >") {
Expand Down Expand Up @@ -232,7 +236,9 @@ func TestCustomJobIfConditionHandling(t *testing.T) {
t.Fatalf("Failed to add job: %v", err)
}

yaml := jm.renderJob(job)
var yamlBuf strings.Builder
jm.renderJobTo(&yamlBuf, job)
yaml := yamlBuf.String()

// Ensure we don't have double prefixes in the rendered YAML
if strings.Contains(yaml, "if: if:") {
Expand Down Expand Up @@ -313,7 +319,9 @@ func TestLongExpressionBreaking(t *testing.T) {
t.Fatalf("Failed to add job: %v", err)
}

yaml := jm.renderJob(job)
var yamlBuf strings.Builder
jm.renderJobTo(&yamlBuf, job)
yaml := yamlBuf.String()
t.Logf("Generated YAML:\n%s", yaml)

// Check expected content
Expand Down
21 changes: 0 additions & 21 deletions pkg/workflow/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,18 +173,6 @@ func (jm *JobManager) WriteJobsYAML(b *strings.Builder) {
}
}

// RenderToYAML generates the jobs section of a GitHub Actions workflow.
// Prefer WriteJobsYAML when an existing *strings.Builder is available to avoid
// an extra allocation.
func (jm *JobManager) RenderToYAML() string {
var yaml strings.Builder
// Pre-size so the builder avoids reallocation for typical workflows.
// All jobs combined are usually ~48–60 KB; 64 KB covers that range.
yaml.Grow(64 * 1024)
jm.WriteJobsYAML(&yaml)
return yaml.String()
}

// renderJobTo writes a single job to b directly, with no intermediate string allocation.
func (jm *JobManager) renderJobTo(b *strings.Builder, job *Job) {
jobLog.Printf("Rendering job: %s (steps=%d, needs=%d, reusable=%t)", job.Name, len(job.Steps), len(job.Needs), job.Uses != "")
Expand Down Expand Up @@ -388,12 +376,3 @@ func (jm *JobManager) renderJobTo(b *strings.Builder, job *Job) {
// Add newline after each job for proper formatting
b.WriteString("\n")
}

// renderJob renders a single job to a new string.
// Prefer renderJobTo when an existing *strings.Builder is available to avoid
// the intermediate allocation.
func (jm *JobManager) renderJob(job *Job) string {
var b strings.Builder
jm.renderJobTo(&b, job)
return b.String()
}
196 changes: 1 addition & 195 deletions pkg/workflow/jobs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,197 +153,8 @@ func TestJobManager_ValidateDependencies(t *testing.T) {
}
}

func TestJobManager_RenderToYAML(t *testing.T) {
tests := []struct {
name string
jobs []*Job
expected []string // Strings that should be present in the output
}{
{
name: "empty job manager",
jobs: []*Job{},
expected: []string{
"jobs:",
},
},
{
name: "single simple job",
jobs: []*Job{
{
Name: "test-job",
RunsOn: "runs-on: ubuntu-latest",
Steps: []string{" - name: Test\n run: echo hello\n"},
},
},
expected: []string{
"jobs:",
" test-job:",
" runs-on: ubuntu-latest",
" steps:",
" - name: Test",
" run: echo hello",
},
},
{
name: "job with dependencies",
jobs: []*Job{
{
Name: "job1",
RunsOn: "runs-on: ubuntu-latest",
Needs: []string{"job2"},
Steps: []string{" - name: Step1\n run: echo step1\n"},
},
{
Name: "job2",
RunsOn: "runs-on: ubuntu-latest",
Steps: []string{" - name: Step2\n run: echo step2\n"},
},
},
expected: []string{
"jobs:",
" job1:",
" needs: job2",
" runs-on: ubuntu-latest",
" job2:",
" runs-on: ubuntu-latest",
},
},
{
name: "job with multiple dependencies",
jobs: []*Job{
{
Name: "deploy",
RunsOn: "runs-on: ubuntu-latest",
Needs: []string{"build", "test"},
Steps: []string{" - name: Deploy\n run: echo deploy\n"},
},
},
expected: []string{
"jobs:",
" deploy:",
" needs:",
" - build",
" - test",
" runs-on: ubuntu-latest",
},
},
{
name: "job with if condition",
jobs: []*Job{
{
Name: "conditional-job",
RunsOn: "runs-on: ubuntu-latest",
If: "github.event_name == 'push'",
Steps: []string{" - name: Conditional Step\n run: echo conditional\n"},
},
},
expected: []string{
"jobs:",
" conditional-job:",
" if: github.event_name == 'push'",
" runs-on: ubuntu-latest",
},
},
{
name: "job with outputs",
jobs: []*Job{
{
Name: "output-job",
RunsOn: "runs-on: ubuntu-latest",
Outputs: map[string]string{
"result": "${{ steps.test.outputs.result }}",
"version": "${{ steps.version.outputs.version }}",
},
Steps: []string{" - name: Generate Output\n run: echo output\n"},
},
},
expected: []string{
"jobs:",
" output-job:",
" runs-on: ubuntu-latest",
" outputs:",
" result: ${{ steps.test.outputs.result }}",
" version: ${{ steps.version.outputs.version }}",
},
},
{
name: "jobs sorted alphabetically regardless of insertion order",
jobs: []*Job{
{
Name: "zebra-job",
RunsOn: "runs-on: ubuntu-latest",
Steps: []string{" - name: Zebra\n run: echo zebra\n"},
},
{
Name: "alpha-job",
RunsOn: "runs-on: ubuntu-latest",
Steps: []string{" - name: Alpha\n run: echo alpha\n"},
},
{
Name: "charlie-job",
RunsOn: "runs-on: ubuntu-latest",
Steps: []string{" - name: Charlie\n run: echo charlie\n"},
},
{
Name: "beta-job",
RunsOn: "runs-on: ubuntu-latest",
Steps: []string{" - name: Beta\n run: echo beta\n"},
},
},
expected: []string{
"jobs:",
" alpha-job:",
" beta-job:",
" charlie-job:",
" zebra-job:",
},
},
{
name: "job with multiple dependencies sorted alphabetically",
jobs: []*Job{
{
Name: "deploy",
RunsOn: "runs-on: ubuntu-latest",
Needs: []string{"test", "activation", "build", "lint"},
Steps: []string{" - name: Deploy\n run: echo deploy\n"},
},
},
expected: []string{
"jobs:",
" deploy:",
" needs:",
" - activation",
" - build",
" - lint",
" - test",
" runs-on: ubuntu-latest",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jm := NewJobManager()
for _, job := range tt.jobs {
if err := jm.AddJob(job); err != nil {
t.Fatalf("Failed to add job %s: %v", job.Name, err)
}
}

result := jm.RenderToYAML()

for _, expected := range tt.expected {
if !strings.Contains(result, expected) {
t.Errorf("RenderToYAML() result does not contain expected string: %s\nFull result:\n%s", expected, result)
}
}
})
}
}

// TestJobManager_WriteJobsYAML verifies that WriteJobsYAML correctly appends
// the jobs section to an already-populated builder (mimicking generateWorkflowBody)
// and that the output is identical to RenderToYAML() prepended with the same prefix.
// the jobs section to an already-populated builder (mimicking generateWorkflowBody).
func TestJobManager_WriteJobsYAML(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -426,11 +237,6 @@ func TestJobManager_WriteJobsYAML(t *testing.T) {
t.Errorf("WriteJobsYAML() result does not contain %q\nFull result:\n%s", expected, result)
}
}

// WriteJobsYAML and RenderToYAML must produce the same jobs section.
if !strings.HasSuffix(result, jm.RenderToYAML()) {
t.Errorf("WriteJobsYAML() jobs section differs from RenderToYAML().\nRenderToYAML:\n%s\nWriteJobsYAML result (full):\n%s", jm.RenderToYAML(), result)
}
})
}
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/workflow/main_job_env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ func TestMainJobEnvironmentVariables(t *testing.T) {
t.Fatalf("Failed to add job to manager: %v", err)
}

yamlOutput := jobManager.RenderToYAML()
var yamlBuf strings.Builder
jobManager.WriteJobsYAML(&yamlBuf)
yamlOutput := yamlBuf.String()
t.Logf("Generated YAML:\n%s", yamlOutput)

// Check that env section exists in YAML
Expand Down
4 changes: 3 additions & 1 deletion pkg/workflow/safe_outputs_call_workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,9 @@ func TestCallWorkflowJobYAMLOutput(t *testing.T) {
require.True(t, exists, "Job should exist")

// Render all jobs to YAML
yamlOutput := compiler.jobManager.RenderToYAML()
var yamlBuf strings.Builder
compiler.jobManager.WriteJobsYAML(&yamlBuf)
yamlOutput := yamlBuf.String()

assert.Contains(t, yamlOutput, "uses: ./.github/workflows/worker-a.lock.yml", "Should contain uses directive")
assert.Contains(t, yamlOutput, "secrets: inherit", "Should inherit secrets")
Expand Down
4 changes: 3 additions & 1 deletion pkg/workflow/threat_detection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,9 @@ func TestDetectionJobPermissionsIndentation(t *testing.T) {
t.Fatalf("AddJob() error: %v", err)
}

yamlOutput := compiler.jobManager.RenderToYAML()
var yamlBuf strings.Builder
compiler.jobManager.WriteJobsYAML(&yamlBuf)
yamlOutput := yamlBuf.String()

for _, expected := range tt.wantContains {
if !strings.Contains(yamlOutput, expected) {
Expand Down
4 changes: 3 additions & 1 deletion pkg/workflow/zizmor_annotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ func TestJobHasWorkflowRunSafetyChecks(t *testing.T) {
// Test that the field is present in rendered YAML when true
jm := NewJobManager()
jm.AddJob(tt.job)
yaml := jm.RenderToYAML()
var yamlBuf strings.Builder
jm.WriteJobsYAML(&yamlBuf)
yaml := yamlBuf.String()

if tt.expectField {
if !strings.Contains(yaml, "# zizmor: ignore[dangerous-triggers]") {
Expand Down
Loading