From 97f949bebdefcf4ac7ef3c9b6b922b740fb7e3b7 Mon Sep 17 00:00:00 2001 From: David Ahmann Date: Tue, 10 Mar 2026 22:43:36 -0400 Subject: [PATCH 1/2] Fix syncframeworks main CI gates --- internal/tools/syncframeworks/main.go | 59 ++++++--- internal/tools/syncframeworks/main_test.go | 141 +++++++++++++++++++++ 2 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 internal/tools/syncframeworks/main_test.go diff --git a/internal/tools/syncframeworks/main.go b/internal/tools/syncframeworks/main.go index 7c3048d..69fbf98 100644 --- a/internal/tools/syncframeworks/main.go +++ b/internal/tools/syncframeworks/main.go @@ -10,16 +10,28 @@ import ( "strings" ) +var ( + repoRootFunc = repoRoot + syncFrameworksFunc = syncFrameworks + runFunc = run + exitFunc = os.Exit + stderrWriter io.Writer = os.Stderr +) + func main() { - root, err := repoRoot() - if err != nil { + if err := runFunc(); err != nil { fail(err) } +} + +func run() error { + root, err := repoRootFunc() + if err != nil { + return err + } srcDir := filepath.Join(root, "core", "framework") dstDir := filepath.Join(root, "frameworks") - if err := syncFrameworks(srcDir, dstDir); err != nil { - fail(err) - } + return syncFrameworksFunc(srcDir, dstDir) } func repoRoot() (string, error) { @@ -35,7 +47,7 @@ func syncFrameworks(srcDir, dstDir string) error { if err != nil { return err } - if err := os.MkdirAll(dstDir, 0o755); err != nil { + if err := os.MkdirAll(dstDir, 0o750); err != nil { return err } @@ -51,9 +63,7 @@ func syncFrameworks(srcDir, dstDir string) error { sort.Strings(names) for _, name := range names { - srcPath := filepath.Join(srcDir, name) - dstPath := filepath.Join(dstDir, name) - if err := copyFile(srcPath, dstPath); err != nil { + if err := copyFile(srcDir, dstDir, name); err != nil { return err } } @@ -76,8 +86,24 @@ func syncFrameworks(srcDir, dstDir string) error { return nil } -func copyFile(srcPath, dstPath string) error { - src, err := os.Open(srcPath) +func copyFile(srcDir, dstDir, name string) error { + srcRoot, err := os.OpenRoot(srcDir) + if err != nil { + return err + } + defer func() { + _ = srcRoot.Close() + }() + + dstRoot, err := os.OpenRoot(dstDir) + if err != nil { + return err + } + defer func() { + _ = dstRoot.Close() + }() + + src, err := srcRoot.Open(name) if err != nil { return err } @@ -85,7 +111,7 @@ func copyFile(srcPath, dstPath string) error { _ = src.Close() }() - dst, err := os.Create(dstPath) + dst, err := dstRoot.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) if err != nil { return err } @@ -93,13 +119,10 @@ func copyFile(srcPath, dstPath string) error { _ = dst.Close() return err } - if err := dst.Close(); err != nil { - return err - } - return os.Chmod(dstPath, 0o644) + return dst.Close() } func fail(err error) { - _, _ = fmt.Fprintf(os.Stderr, "sync frameworks: %s\n", strings.TrimSpace(err.Error())) - os.Exit(1) + _, _ = fmt.Fprintf(stderrWriter, "sync frameworks: %s\n", strings.TrimSpace(err.Error())) + exitFunc(1) } diff --git a/internal/tools/syncframeworks/main_test.go b/internal/tools/syncframeworks/main_test.go new file mode 100644 index 0000000..f0429b8 --- /dev/null +++ b/internal/tools/syncframeworks/main_test.go @@ -0,0 +1,141 @@ +package main + +import ( + "bytes" + "errors" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +type exitPanic struct { + code int +} + +func restoreGlobals() { + repoRootFunc = repoRoot + syncFrameworksFunc = syncFrameworks + runFunc = run + exitFunc = os.Exit + stderrWriter = os.Stderr +} + +func TestRunUsesRepoRoot(t *testing.T) { + t.Cleanup(restoreGlobals) + + tempRoot := t.TempDir() + repoRootFunc = func() (string, error) { + return tempRoot, nil + } + + called := false + syncFrameworksFunc = func(srcDir, dstDir string) error { + called = true + require.Equal(t, filepath.Join(tempRoot, "core", "framework"), srcDir) + require.Equal(t, filepath.Join(tempRoot, "frameworks"), dstDir) + return nil + } + + require.NoError(t, run()) + require.True(t, called) +} + +func TestRunPropagatesRepoRootError(t *testing.T) { + t.Cleanup(restoreGlobals) + + repoRootFunc = func() (string, error) { + return "", errors.New("no root") + } + + err := run() + require.EqualError(t, err, "no root") +} + +func TestMainCallsFailOnRunError(t *testing.T) { + t.Cleanup(restoreGlobals) + + runFunc = func() error { + return errors.New("boom") + } + + var stderr bytes.Buffer + stderrWriter = &stderr + exitFunc = func(code int) { + panic(exitPanic{code: code}) + } + + require.PanicsWithValue(t, exitPanic{code: 1}, func() { + main() + }) + require.Equal(t, "sync frameworks: boom\n", stderr.String()) +} + +func TestRepoRoot(t *testing.T) { + root, err := repoRoot() + require.NoError(t, err) + + info, err := os.Stat(filepath.Join(root, "go.mod")) + require.NoError(t, err) + require.False(t, info.IsDir()) +} + +func TestCopyFile(t *testing.T) { + srcDir := t.TempDir() + dstDir := t.TempDir() + + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "soc2.yaml"), []byte("framework: soc2\n"), 0o644)) + + require.NoError(t, copyFile(srcDir, dstDir, "soc2.yaml")) + + raw, err := os.ReadFile(filepath.Join(dstDir, "soc2.yaml")) + require.NoError(t, err) + require.Equal(t, "framework: soc2\n", string(raw)) +} + +func TestSyncFrameworksCopiesAndRemovesYAML(t *testing.T) { + srcDir := t.TempDir() + dstDir := t.TempDir() + + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "soc2.yaml"), []byte("soc2"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "notes.txt"), []byte("ignore"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(dstDir, "old.yaml"), []byte("stale"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(dstDir, "keep.txt"), []byte("keep"), 0o644)) + + require.NoError(t, syncFrameworks(srcDir, dstDir)) + + raw, err := os.ReadFile(filepath.Join(dstDir, "soc2.yaml")) + require.NoError(t, err) + require.Equal(t, "soc2", string(raw)) + + _, err = os.Stat(filepath.Join(dstDir, "old.yaml")) + require.ErrorIs(t, err, os.ErrNotExist) + + raw, err = os.ReadFile(filepath.Join(dstDir, "keep.txt")) + require.NoError(t, err) + require.Equal(t, "keep", string(raw)) + + _, err = os.Stat(filepath.Join(dstDir, "notes.txt")) + require.ErrorIs(t, err, os.ErrNotExist) +} + +func TestSyncFrameworksMissingSourceDir(t *testing.T) { + err := syncFrameworks(filepath.Join(t.TempDir(), "missing"), t.TempDir()) + require.Error(t, err) +} + +func TestCopyFileErrors(t *testing.T) { + t.Run("missing source file", func(t *testing.T) { + err := copyFile(t.TempDir(), t.TempDir(), "missing.yaml") + require.Error(t, err) + }) + + t.Run("missing destination dir", func(t *testing.T) { + srcDir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "soc2.yaml"), []byte("soc2"), 0o644)) + + err := copyFile(srcDir, filepath.Join(t.TempDir(), "missing"), "soc2.yaml") + require.Error(t, err) + }) +} From 6b65b1b0b080a23756463d3be1d6c60e8fec1219 Mon Sep 17 00:00:00 2001 From: David Ahmann Date: Tue, 10 Mar 2026 22:45:42 -0400 Subject: [PATCH 2/2] Make PR workflow concurrency unique per source --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index df1aa67..6790f96 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -7,7 +7,7 @@ on: - main concurrency: - group: pr-${{ github.workflow }}-${{ github.event.pull_request.head.ref || github.ref_name }} + group: pr-${{ github.workflow }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.number || github.ref_name }} cancel-in-progress: true jobs: