Skip to content

Commit

Permalink
Add tooling to check for missing local overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
illicitonion committed Jan 3, 2024
1 parent 2d07956 commit 871b9d3
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/check-consistency.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Check consistency

on:
pull_request:
push:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: 1.21
- name: Build tooling
run: (cd tooling/go && go build ./cmd/local-overrides-enforcer)
- name: Check consistency
run: ./tooling/go/local-overrides-enforcer
18 changes: 18 additions & 0 deletions .github/workflows/test-tooling.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Test tooling

on:
pull_request:
push:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: 1.21
- name: Run tooling tests
run: cd tooling/go && go test ./...
Binary file not shown.
74 changes: 74 additions & 0 deletions tooling/go/cmd/local-overrides-enforcer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package main

import (
"flag"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"strings"

"github.com/CodeYourFuture/curriculum-labs/tooling/go/internal/local-overrides-enforcer/checker"
)

func main() {
var rootDirectory string
flag.StringVar(&rootDirectory, "root-dir", ".", "Root directory to search for go.mod files in")
excludeDirectoriesFlag := flag.String("exclude", filepath.Join("tooling", "go"), "Directories to exclude from searches (comma-delimited)")
var parentModule string
flag.StringVar(&parentModule, "parent-module", "github.com/CodeYourFuture/curriculum-labs", "Parent module to search for missing overrides within")

flag.Parse()

var err error
rootDirectory, err = filepath.Abs(rootDirectory)
if err != nil {
log.Fatalf("Failed to get absolute path of root directory: %v", err)
}

var excludeDirectories []string
for _, excludeDirectory := range strings.Split(*excludeDirectoriesFlag, ",") {
excludeDirectory, err = filepath.Abs(excludeDirectory)
if err != nil {
log.Fatalf("Failed to get absolute path of exclude direectory: %v", err)
}
excludeDirectories = append(excludeDirectories, excludeDirectory)
}

sawBadFile := false

err = filepath.WalkDir(rootDirectory, func(path string, d fs.DirEntry, err error) error {
for _, excluded := range excludeDirectories {
if path == excluded {
return fs.SkipDir
}
}
if err != nil {
return err
}

if d.Name() != "go.mod" {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read %s: %w", path, err)
}
expectedContents, ok, err := checker.CheckFile(path, content, parentModule)
if err != nil {
return fmt.Errorf("failed to check %s: %w", path, err)
}
if !ok {
sawBadFile = true
fmt.Printf("⚠️ File at path %s didn't have some local overrides - its contents should be:\n%s\n", path, expectedContents)
}
return nil
})
if err != nil {
log.Fatalf("Error walking filesystem: %v", err)
}
if sawBadFile {
os.Exit(1)
}
}
11 changes: 11 additions & 0 deletions tooling/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/CodeYourFuture/curriculum-labs/tooling/go

go 1.21.5

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/mod v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
11 changes: 11 additions & 0 deletions tooling/go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
56 changes: 56 additions & 0 deletions tooling/go/internal/local-overrides-enforcer/checker/checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package checker

import (
"fmt"
"strings"

"golang.org/x/mod/modfile"
)

// CheckFile checks that a go.mod file has local overrides for all children of the passed parent module.
// It returns one of:
// - If the contents was correct: "", true, nil
// - If the contents was not correct: the expected contents, false, nil
// - If an error occurred: "", false, error
func CheckFile(goModPath string, contents []byte, parentModule string) (string, bool, error) {
gomodFile, err := modfile.Parse(goModPath, contents, nil)
if err != nil {
return "", false, fmt.Errorf("failed to parse %s as go.mod file: %w", goModPath, err)
}

parentModuleWithTrailingSlash := parentModule + "/"

if !strings.HasPrefix(gomodFile.Module.Mod.Path, parentModuleWithTrailingSlash) {
return "", false, fmt.Errorf("module at path %s was named %s which isn't a child of %s", goModPath, gomodFile.Module.Mod.Path, parentModule)
}
slashCount := strings.Count(gomodFile.Module.Mod.Path[len(parentModule):], "/")

replaces := make(map[string]struct{})
for _, replace := range gomodFile.Replace {
replaces[replace.Old.Path] = struct{}{}
}

missingReplaces := false
for _, require := range gomodFile.Require {
modPath := require.Mod.Path
if !strings.HasPrefix(modPath, parentModuleWithTrailingSlash) {
continue
}
if _, isReplaced := replaces[modPath]; isReplaced {
continue
}
missingReplaces = true
rel := modPath[len(parentModuleWithTrailingSlash):]
if err := gomodFile.AddReplace(modPath, "", strings.Repeat("../", slashCount)+rel, ""); err != nil {
return "", false, fmt.Errorf("failed to add replace: %w", err)
}
}
if missingReplaces {
formatted, err := gomodFile.Format()
if err != nil {
return "", false, fmt.Errorf("failed to serialize go.mod file: %w", err)
}
return string(formatted), false, nil
}
return "", true, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package checker_test

import (
"testing"

"github.com/CodeYourFuture/curriculum-labs/tooling/go/internal/local-overrides-enforcer/checker"
"github.com/stretchr/testify/require"
)

func TestCorrect(t *testing.T) {
goModContent := `module github.com/CodeYourFuture/curriculum-labs/org-cyf
go 1.21.3
replace github.com/CodeYourFuture/curriculum-labs/common-content => ../common-content
replace github.com/CodeYourFuture/curriculum-labs/common-theme => ../common-theme
require (
github.com/CodeYourFuture/curriculum-labs/common-content v0.0.0-20240103071042-5b2177342232 // indirect
github.com/CodeYourFuture/curriculum-labs/common-theme v0.0.0-20240103071042-5b2177342232 // indirect
)
`

newContent, ok, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum-labs")
require.NoError(t, err)
require.True(t, ok)
require.Equal(t, "", newContent)
}

func TestMissingReplace(t *testing.T) {
goModContent := `module github.com/CodeYourFuture/curriculum-labs/org-cyf
go 1.21.3
replace github.com/CodeYourFuture/curriculum-labs/common-theme => ../common-theme
require (
github.com/CodeYourFuture/curriculum-labs/common-content v0.0.0-20240103071042-5b2177342232 // indirect
github.com/CodeYourFuture/curriculum-labs/common-theme v0.0.0-20240103071042-5b2177342232 // indirect
)
`

newContent, ok, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum-labs")
require.NoError(t, err)
require.False(t, ok)
require.Contains(t, newContent, "replace github.com/CodeYourFuture/curriculum-labs/common-content => ../common-content")
}

func TestModuleNotChildOfParent(t *testing.T) {
goModContent := `module github.com/CodeYourFuture/wrong
go 1.21.3
replace github.com/CodeYourFuture/curriculum-labs/common-content => ../common-content
replace github.com/CodeYourFuture/curriculum-labs/common-theme => ../common-theme
require (
github.com/CodeYourFuture/curriculum-labs/common-content v0.0.0-20240103071042-5b2177342232 // indirect
github.com/CodeYourFuture/curriculum-labs/common-theme v0.0.0-20240103071042-5b2177342232 // indirect
)
`

_, _, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum-labs")
require.ErrorContains(t, err, "module at path /some/go.mod was named github.com/CodeYourFuture/wrong which isn't a child of github.com/CodeYourFuture/curriculum-labs")
}

func TestInvalidGoModFile(t *testing.T) {
_, _, err := checker.CheckFile("/some/go.mod", []byte("hello"), "github.com/CodeYourFuture/curriculum-labs")
require.ErrorContains(t, err, "failed to parse /some/go.mod as go.mod file: ")
}
Binary file added tooling/go/local-overrides-enforcer
Binary file not shown.

0 comments on commit 871b9d3

Please sign in to comment.