-
Notifications
You must be signed in to change notification settings - Fork 498
Consolidate duplicate runs-on normalization helpers across config parsers #52975
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+244
−106
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
7344dc5
Initial plan
Copilot 5ac572c
Consolidate duplicate runs-on normalization helpers into runs_on_snip…
Copilot 47402d5
docs(adr): add draft ADR-52975 for runs-on normalization consolidation
github-actions[bot] f393f21
Merge branch 'main' into copilot/duplicate-code-runs-on-normalization
github-actions[bot] b6ac0ed
Add unit tests for formatSafeJobRunsOn
Copilot 8b98b5c
Clarify test case name after review feedback
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
55 changes: 55 additions & 0 deletions
55
docs/adr/52975-consolidate-runs-on-normalization-helpers.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| # ADR-52975: Consolidate runs-on Normalization Helpers into runs_on_snippet.go | ||
|
|
||
| **Date**: 2026-08-15 | ||
| **Status**: Draft | ||
| **Deciders**: pelikhan (via copilot-swe-agent, PR #52975) | ||
|
|
||
| --- | ||
|
|
||
| ### Context | ||
|
|
||
| The `pkg/workflow` package contained three separate locations that independently implemented overlapping `runs-on` parsing and rendering logic: | ||
|
|
||
| - `repo_config.go` defined `RunsOnValue`, `UnmarshalJSON`, `toRunsOnValue`, `isRunsOnArrayValue`, and `FormatRunsOn` | ||
| - `safe_jobs.go` contained inline branching logic for array-vs-scalar `runs-on` rendering | ||
| - `runs_on_snippet.go` already held `renderRunsOnSnippet`/`normalizeRunsOnSnippet` helpers | ||
|
|
||
| This duplication meant that a bug fix to runner-shape handling could be applied in one parser while silently missing the others. The `runs-on` field accepts both a single string and an array of strings (e.g., for self-hosted runners with labels), and the normalization and YAML rendering must behave consistently across all callers. | ||
|
|
||
| ### Decision | ||
|
|
||
| We will consolidate all `runs-on` type definitions and helpers into `runs_on_snippet.go`, which already houses the core snippet rendering logic. Specifically: | ||
|
|
||
| - `RunsOnValue`, `UnmarshalJSON`, `toRunsOnValue`, `isRunsOnArrayValue`, and `FormatRunsOn` are moved from `repo_config.go` into `runs_on_snippet.go`. | ||
| - A new `formatSafeJobRunsOn` helper is added to `runs_on_snippet.go`, replacing the inline array-vs-scalar branching in `safe_jobs.go`'s job-building loop. | ||
| - `safe_jobs.go` is simplified to a single call to `formatSafeJobRunsOn`. | ||
|
|
||
| This is a pure code-organization refactor with no change to supported `runs-on` shapes or rendered output. | ||
|
|
||
| ### Alternatives Considered | ||
|
|
||
| #### Alternative 1: Keep Duplication As-Is | ||
|
|
||
| Leave `RunsOnValue` and its helpers in `repo_config.go` and retain the inline branching in `safe_jobs.go`. Simple in the short term and zero risk of behavioral regression, but perpetuates the maintenance hazard: the next `runs-on` bug fix must be applied in multiple places, and there is no structural enforcement ensuring all parsers stay in sync. | ||
|
|
||
| #### Alternative 2: Create a Dedicated `runs_on.go` File | ||
|
|
||
| Move all `runs-on` types and helpers into a new `pkg/workflow/runs_on.go` file rather than expanding `runs_on_snippet.go`. This would produce a cleaner name-to-responsibility mapping. The trade-off is an additional file that must be discovered, and `runs_on_snippet.go`'s snippet rendering helpers would remain separated from the type that drives them. Given the functions are tightly coupled (they all operate on `RunsOnValue` and produce YAML fragments), co-location in one file is preferred over splitting across two. | ||
|
|
||
| ### Consequences | ||
|
|
||
| #### Positive | ||
| - Single source of truth for all `runs-on` normalization and YAML rendering; future bug fixes or new runner-shape support apply uniformly across `aw.json` and `safe-outputs.jobs` parsing. | ||
| - `safe_jobs.go` call site is reduced from 13 lines of branching logic to 1 line, improving readability and reducing cognitive overhead for future maintainers. | ||
|
|
||
| #### Negative | ||
| - `runs_on_snippet.go` now covers a broader scope than its filename implies (it holds the `RunsOnValue` type, JSON unmarshaling, and YAML rendering helpers, not just snippet generation). Readers may be surprised to find the type definition there rather than in a file named `runs_on.go`. | ||
| - As a pure refactor, behavioral parity must be verified by existing tests. Any gap in test coverage of `runs-on` edge cases (empty arrays, single empty-string elements, multi-label arrays) could mask an unintended regression introduced during the move. | ||
|
|
||
| #### Neutral | ||
| - The `encoding/json` and `fmt` imports are added to `runs_on_snippet.go` (previously only in `repo_config.go`) as a direct consequence of moving the type and its JSON unmarshaler. | ||
| - No public API surface changes: `RunsOnValue`, `FormatRunsOn`, and related helpers remain exported at the same package level. | ||
|
|
||
| --- | ||
|
|
||
| *ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package workflow | ||
|
|
||
| import "testing" | ||
|
|
||
| func TestFormatSafeJobRunsOn(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| runsOn RunsOnValue | ||
| runsOnArray bool | ||
| defaultRunsOn string | ||
| want string | ||
| }{ | ||
| { | ||
| name: "nil value defaults", | ||
| runsOn: nil, | ||
| runsOnArray: false, | ||
| defaultRunsOn: "ubuntu-latest", | ||
| want: "runs-on: ubuntu-latest", | ||
| }, | ||
| { | ||
| name: "empty array-shaped value defaults", | ||
| runsOn: RunsOnValue{}, | ||
| runsOnArray: true, | ||
| defaultRunsOn: "ubuntu-latest", | ||
| want: "runs-on: ubuntu-latest", | ||
| }, | ||
| { | ||
| name: "single empty-string element treated as unset", | ||
| runsOn: RunsOnValue{""}, | ||
| runsOnArray: true, | ||
| defaultRunsOn: "ubuntu-latest", | ||
| want: "runs-on: ubuntu-latest", | ||
| }, | ||
| { | ||
| name: "scalar value rendered inline", | ||
| runsOn: RunsOnValue{"self-hosted"}, | ||
| runsOnArray: false, | ||
| defaultRunsOn: "ubuntu-latest", | ||
| want: "runs-on: self-hosted", | ||
| }, | ||
| { | ||
| name: "single-element array shape renders as YAML sequence", | ||
| runsOn: RunsOnValue{"self-hosted"}, | ||
| runsOnArray: true, | ||
| defaultRunsOn: "ubuntu-latest", | ||
| want: "runs-on:\n - self-hosted", | ||
| }, | ||
| { | ||
| name: "multi-element array shape renders YAML sequence", | ||
| runsOn: RunsOnValue{"self-hosted", "linux"}, | ||
| runsOnArray: true, | ||
| defaultRunsOn: "ubuntu-latest", | ||
| want: "runs-on:\n - self-hosted\n - linux", | ||
| }, | ||
| { | ||
| name: "multi-element scalar-mode renders as JSON array inline", | ||
| runsOn: RunsOnValue{"self-hosted", "linux"}, | ||
| runsOnArray: false, | ||
| defaultRunsOn: "ubuntu-latest", | ||
| want: `runs-on: ["self-hosted","linux"]`, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got := formatSafeJobRunsOn(tt.runsOn, tt.runsOnArray, tt.defaultRunsOn) | ||
| if got != tt.want { | ||
| t.Errorf("formatSafeJobRunsOn(%#v, %v, %q) = %q, want %q", tt.runsOn, tt.runsOnArray, tt.defaultRunsOn, got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/tdd]
formatSafeJobRunsOnhas no dedicated unit test — the previous inline logic was exercised through integration tests, but the new helper isn't tested in isolation, which reduces confidence in future changes.💡 Suggested test skeleton
This mirrors the existing
TestFormatRunsOnpattern inrepo_config_test.go.@copilot please address this.