Add reusable CI preparation operations
This commit is contained in:
@@ -3,13 +3,167 @@ package gotool
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type goPreparationRecord struct {
|
||||
Arguments []string
|
||||
Directory string
|
||||
Workspace string
|
||||
Toolchain string
|
||||
Proxy string
|
||||
Private string
|
||||
NoSumDB string
|
||||
ModuleCache string
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if os.Getenv("RELEASEKIT_GO_FIXTURE") == "1" {
|
||||
os.Exit(runGoPreparationFixture())
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func runGoPreparationFixture() int {
|
||||
record := goPreparationRecord{
|
||||
Arguments: os.Args[1:], Directory: mustGetwd(),
|
||||
Workspace: os.Getenv("GOWORK"), Toolchain: os.Getenv("GOTOOLCHAIN"),
|
||||
Proxy: os.Getenv("GOPROXY"), Private: os.Getenv("GOPRIVATE"),
|
||||
NoSumDB: os.Getenv("GONOSUMDB"), ModuleCache: os.Getenv("GOMODCACHE"),
|
||||
}
|
||||
contents, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
return 10
|
||||
}
|
||||
if err := os.WriteFile(os.Getenv("RELEASEKIT_GO_FIXTURE_RECORD"), contents, 0o600); err != nil {
|
||||
return 11
|
||||
}
|
||||
switch os.Getenv("RELEASEKIT_GO_FIXTURE_MODE") {
|
||||
case "fail":
|
||||
fmt.Fprintln(os.Stderr, "fixture dependency failure")
|
||||
return 23
|
||||
case "wait":
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func mustGetwd() string {
|
||||
directory, err := os.Getwd()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return directory
|
||||
}
|
||||
|
||||
func installGoPreparationFixture(t *testing.T) (repository, recordPath string) {
|
||||
t.Helper()
|
||||
directory := t.TempDir()
|
||||
repository = t.TempDir()
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
contents, err := os.ReadFile(executable)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name := "go"
|
||||
if runtime.GOOS == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(directory, name), contents, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
recordPath = filepath.Join(t.TempDir(), "go-command.json")
|
||||
t.Setenv("PATH", directory+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
t.Setenv("RELEASEKIT_GO_FIXTURE", "1")
|
||||
t.Setenv("RELEASEKIT_GO_FIXTURE_RECORD", recordPath)
|
||||
return repository, recordPath
|
||||
}
|
||||
|
||||
func TestPrepareBuildDependenciesPreservesPreparationEnvironment(t *testing.T) {
|
||||
repository, recordPath := installGoPreparationFixture(t)
|
||||
moduleCache := filepath.Join(t.TempDir(), "module-cache")
|
||||
t.Setenv("GOWORK", filepath.Join(t.TempDir(), "go.work"))
|
||||
t.Setenv("GOTOOLCHAIN", "auto")
|
||||
t.Setenv("GOPROXY", "https://proxy.example.invalid")
|
||||
t.Setenv("GOPRIVATE", "example.invalid/private")
|
||||
t.Setenv("GONOSUMDB", "example.invalid/private")
|
||||
t.Setenv("GOMODCACHE", moduleCache)
|
||||
|
||||
if err := PrepareBuildDependencies(context.Background(), repository); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
contents, err := os.ReadFile(recordPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var record goPreparationRecord
|
||||
if err := json.Unmarshal(contents, &record); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Join(record.Arguments, " ") != "mod download" || record.Directory != repository {
|
||||
t.Fatalf("Go invocation = %#v", record)
|
||||
}
|
||||
if record.Workspace != "off" || record.Toolchain != "local" {
|
||||
t.Fatalf("preparation Go settings = %#v", record)
|
||||
}
|
||||
if record.Proxy != "https://proxy.example.invalid" || record.Private != "example.invalid/private" || record.NoSumDB != "example.invalid/private" || record.ModuleCache != moduleCache {
|
||||
t.Fatalf("caller dependency settings were not preserved: %#v", record)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareBuildDependenciesPropagatesCommandFailure(t *testing.T) {
|
||||
repository, _ := installGoPreparationFixture(t)
|
||||
t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "fail")
|
||||
err := PrepareBuildDependencies(context.Background(), repository)
|
||||
var exitError *exec.ExitError
|
||||
if !errors.As(err, &exitError) || exitError.ExitCode() != 23 || !strings.Contains(err.Error(), "fixture dependency failure") {
|
||||
t.Fatalf("PrepareBuildDependencies() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareBuildDependenciesPropagatesCancellation(t *testing.T) {
|
||||
repository, recordPath := installGoPreparationFixture(t)
|
||||
t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "wait")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- PrepareBuildDependencies(ctx, repository) }()
|
||||
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for {
|
||||
if _, err := os.Stat(recordPath); err == nil {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("Go fixture did not start")
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
cancel()
|
||||
select {
|
||||
case err := <-done:
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("PrepareBuildDependencies() error = %v; want cancellation", err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Go fixture did not stop after cancellation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeAndStaticCommandsUseOwnedBaseline(t *testing.T) {
|
||||
clearGoSettings(t)
|
||||
t.Setenv("GOENV", "off")
|
||||
|
||||
Reference in New Issue
Block a user