Check committed Go formatting
This commit is contained in:
@@ -78,6 +78,36 @@ func TestCheckCapturesCandidateAndCallsAdditionalChecksOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckStopsBeforeProductAndBuildCallbacksWhenGoFormatFails(t *testing.T) {
|
||||
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
|
||||
writeTestFile(t, repository, "unformatted.go", "package candidate\nfunc Value( ) int{return 1}\n")
|
||||
gitRun(t, repository, "add", "unformatted.go")
|
||||
gitRun(t, repository, "commit", "-m", "add unformatted Go source")
|
||||
productCalls := 0
|
||||
buildCalls := 0
|
||||
_, err := Check(context.Background(), Request{
|
||||
RepositoryDir: repository,
|
||||
Product: "Example Product",
|
||||
Version: testVersion,
|
||||
Validation: validation.Settings{GoFormat: true},
|
||||
AdditionalChecks: func(context.Context, Candidate) error {
|
||||
productCalls++
|
||||
return nil
|
||||
},
|
||||
AfterValidate: func(context.Context, Candidate) error {
|
||||
buildCalls++
|
||||
return nil
|
||||
},
|
||||
})
|
||||
var failure *validation.Failure
|
||||
if !errors.As(err, &failure) || failure.Check != validation.GoFormat {
|
||||
t.Fatalf("Check error = %v, want go-format failure", err)
|
||||
}
|
||||
if productCalls != 0 || buildCalls != 0 {
|
||||
t.Fatalf("product callback calls = %d, build callback calls = %d; want neither after formatting failure", productCalls, buildCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRunsDefaultOfflineGoValidation(t *testing.T) {
|
||||
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
|
||||
var output bytes.Buffer
|
||||
|
||||
@@ -15,6 +15,8 @@ const (
|
||||
GoVet Check = "go-vet"
|
||||
// PackageCompile identifies selected-target package compilation.
|
||||
PackageCompile Check = "package-compile"
|
||||
// GoFormat identifies committed Go source formatting.
|
||||
GoFormat Check = "go-format"
|
||||
)
|
||||
|
||||
// Finding describes a validation observation at an optional source location.
|
||||
|
||||
133
internal/validation/format_test.go
Normal file
133
internal/validation/format_test.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
|
||||
)
|
||||
|
||||
func TestCheckGoFormatUsesCapturedRegularGoBlobs(t *testing.T) {
|
||||
repository := newTreeRepository(t)
|
||||
writeTreeFile(t, repository, "main.go", []byte("package main\n\nfunc main() {}\n"))
|
||||
writeTreeFile(t, repository, "vendor/example/format.go", []byte("package example\nfunc Value( ){return 1}\n"))
|
||||
writeTreeFile(t, repository, "nested/module/odd name.go", []byte("package nested\nfunc Value( ){return 2}\n"))
|
||||
writeTreeFile(t, repository, "notes.txt", []byte("not Go source\n"))
|
||||
if err := os.Symlink("main.go", filepath.Join(repository, "link.go")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gitTree(t, repository, "add", ".")
|
||||
gitTree(t, repository, "commit", "-m", "capture Go files")
|
||||
commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD"))
|
||||
|
||||
// Worktree edits must not affect the formatting result for the captured commit.
|
||||
writeTreeFile(t, repository, "main.go", []byte("package main\nfunc main( ){}\n"))
|
||||
writeTreeFile(t, repository, "vendor/example/format.go", []byte("package example\n\nfunc Value() { return 1 }\n"))
|
||||
worktreeMain, err := os.ReadFile(filepath.Join(repository, "main.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tree, err := ReadCandidateTree(context.Background(), repository, commit)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = checkGoFormat(context.Background(), tree)
|
||||
var failure *Failure
|
||||
if !errors.As(err, &failure) || failure.Check != GoFormat {
|
||||
t.Fatalf("checkGoFormat error = %v, want go-format failure", err)
|
||||
}
|
||||
wantPaths := []string{"nested/module/odd name.go", "vendor/example/format.go"}
|
||||
if len(failure.Findings) != len(wantPaths) {
|
||||
t.Fatalf("format findings = %#v, want paths %q", failure.Findings, wantPaths)
|
||||
}
|
||||
for index, path := range wantPaths {
|
||||
if failure.Findings[index].Path != path {
|
||||
t.Errorf("format finding %d path = %q, want %q", index, failure.Findings[index].Path, path)
|
||||
}
|
||||
}
|
||||
worktreeMainAfter, err := os.ReadFile(filepath.Join(repository, "main.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(worktreeMainAfter) != string(worktreeMain) {
|
||||
t.Fatalf("format check changed worktree source: got %q, want %q", worktreeMainAfter, worktreeMain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckGoFormatPreservesParserAndInspectionCauses(t *testing.T) {
|
||||
t.Run("parser", func(t *testing.T) {
|
||||
repository := newTreeRepository(t)
|
||||
writeTreeFile(t, repository, "broken.go", []byte("package broken\nfunc ( {\n"))
|
||||
gitTree(t, repository, "add", ".")
|
||||
gitTree(t, repository, "commit", "-m", "capture invalid Go source")
|
||||
commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD"))
|
||||
tree, err := ReadCandidateTree(context.Background(), repository, commit)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = checkGoFormat(context.Background(), tree)
|
||||
var failure *Failure
|
||||
if !errors.As(err, &failure) || failure.Check != GoFormat || failure.Cause == nil {
|
||||
t.Fatalf("parser failure = %#v, want go-format failure with cause", err)
|
||||
}
|
||||
if len(failure.Findings) != 1 || failure.Findings[0].Path != "broken.go" {
|
||||
t.Fatalf("parser findings = %#v, want broken.go", failure.Findings)
|
||||
}
|
||||
if !strings.Contains(failure.Cause.Error(), "broken.go") {
|
||||
t.Fatalf("parser cause = %v, want committed filename context", failure.Cause)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inspection", func(t *testing.T) {
|
||||
tree := &CandidateTree{
|
||||
repositoryDir: t.TempDir(),
|
||||
entries: []git.TreeEntry{{Path: "missing.go", ObjectID: strings.Repeat("0", 40), Mode: "100644", Type: "blob"}},
|
||||
byPath: map[string]int{"missing.go": 0},
|
||||
blobs: make(map[string][]byte),
|
||||
}
|
||||
err := checkGoFormat(context.Background(), tree)
|
||||
var failure *Failure
|
||||
if !errors.As(err, &failure) || failure.Check != GoFormat || failure.Cause == nil {
|
||||
t.Fatalf("inspection failure = %#v, want go-format failure with cause", err)
|
||||
}
|
||||
if len(failure.Findings) != 1 || failure.Findings[0].Path != "missing.go" {
|
||||
t.Fatalf("inspection findings = %#v, want missing.go", failure.Findings)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunGoFormatIsOptionalAndStopsAfterFinding(t *testing.T) {
|
||||
repository := newTreeRepository(t)
|
||||
writeTreeFile(t, repository, "unformatted.go", []byte("package fixture\nfunc Value( ){return 1}\n"))
|
||||
gitTree(t, repository, "add", ".")
|
||||
gitTree(t, repository, "commit", "-m", "capture unformatted Go source")
|
||||
commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD"))
|
||||
|
||||
logPath := installGoShim(t, "", false)
|
||||
if err := Run(context.Background(), Request{RepositoryDir: repository, Commit: commit}); err != nil {
|
||||
t.Fatalf("default validation = %v, want success with formatting disabled", err)
|
||||
}
|
||||
if got := strings.Join(readGoShimLog(t, logPath), "\n"); !strings.Contains(got, "test -mod=readonly -count=1 ./...") {
|
||||
t.Fatalf("default Go commands = %q, want standard suite", got)
|
||||
}
|
||||
|
||||
logPath = installGoShim(t, "", false)
|
||||
err := Run(context.Background(), Request{
|
||||
RepositoryDir: repository,
|
||||
Commit: commit,
|
||||
Settings: Settings{GoFormat: true},
|
||||
})
|
||||
var failure *Failure
|
||||
if !errors.As(err, &failure) || failure.Check != GoFormat {
|
||||
t.Fatalf("selected formatting error = %v, want go-format failure", err)
|
||||
}
|
||||
commands := strings.Join(readGoShimLog(t, logPath), "\n")
|
||||
if strings.Contains(commands, "build ") || strings.Contains(commands, "vet ") {
|
||||
t.Fatalf("later Go commands ran after formatting failure: %q", commands)
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -16,6 +18,7 @@ type Settings struct {
|
||||
Race bool
|
||||
FullVet bool
|
||||
CompileTargets []Target
|
||||
GoFormat bool
|
||||
}
|
||||
|
||||
// Target selects one package-compilation target.
|
||||
@@ -88,5 +91,55 @@ func Run(ctx context.Context, request Request) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if request.Settings.GoFormat {
|
||||
tree, err := request.ReadCandidateTree(ctx)
|
||||
if err != nil {
|
||||
return &Failure{Check: GoFormat, Cause: err}
|
||||
}
|
||||
if err := checkGoFormat(ctx, tree); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkGoFormat(ctx context.Context, tree *CandidateTree) error {
|
||||
var findings []Finding
|
||||
for _, entry := range tree.Entries() {
|
||||
if entry.Type != "blob" || (entry.Mode != "100644" && entry.Mode != "100755") || !strings.HasSuffix(entry.Path, ".go") {
|
||||
continue
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return &Failure{Check: GoFormat, Cause: err}
|
||||
}
|
||||
contents, err := tree.ReadFile(ctx, entry.Path)
|
||||
if err != nil {
|
||||
return &Failure{
|
||||
Check: GoFormat,
|
||||
Findings: []Finding{{
|
||||
Path: entry.Path,
|
||||
Message: "unable to inspect committed Go source",
|
||||
}},
|
||||
Cause: fmt.Errorf("inspect committed Go file %q: %w", entry.Path, err),
|
||||
}
|
||||
}
|
||||
formatted, err := format.Source(contents)
|
||||
if err != nil {
|
||||
return &Failure{
|
||||
Check: GoFormat,
|
||||
Findings: []Finding{{
|
||||
Path: entry.Path,
|
||||
Message: "committed Go source cannot be formatted",
|
||||
}},
|
||||
Cause: fmt.Errorf("format committed Go file %q: %w", entry.Path, err),
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(contents, formatted) {
|
||||
findings = append(findings, Finding{Path: entry.Path, Message: "file is not gofmt-formatted"})
|
||||
}
|
||||
}
|
||||
if len(findings) != 0 {
|
||||
return &Failure{Check: GoFormat, Findings: findings}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user