Speed up first use of repo backed VMs by bootstrapping obvious tools before the best effort LLM harness runs. Add a host side tooling plan for pinned Go, Node, Python, and Rust versions, summarize that plan in the uploaded prompt, and run repo mise install plus guest global mise use -g --pin steps before the bounded opencode inspection. Keep the harness non fatal, prefer host opencode attach when the client supports it, fall back to guest opencode over SSH for older clients, and cover the new flow with CLI plus planner tests. Validation: - go test ./internal/cli ./internal/toolingplan - GOCACHE=/tmp/banger-gocache go test ./... - make build
137 lines
4.3 KiB
Go
137 lines
4.3 KiB
Go
package toolingplan
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestBuildDetectsGoVersionFromGoMod(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
writePlanFile(t, repoRoot, "go.mod", "module example.com/demo\n\ngo 1.25.0\n")
|
|
|
|
plan := Build(context.Background(), repoRoot)
|
|
|
|
if len(plan.Steps) != 1 {
|
|
t.Fatalf("steps = %#v, want one step", plan.Steps)
|
|
}
|
|
step := plan.Steps[0]
|
|
if step.Tool != "go" || step.Version != "1.25.0" || step.Source != "go.mod" {
|
|
t.Fatalf("step = %#v, want go@1.25.0 from go.mod", step)
|
|
}
|
|
}
|
|
|
|
func TestBuildSkipsGoWhenRepoMiseAlreadyDeclaresIt(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
writePlanFile(t, repoRoot, ".mise.toml", "[tools]\ngo = '1.25.0'\n")
|
|
writePlanFile(t, repoRoot, "go.mod", "module example.com/demo\n\ngo 1.25.0\n")
|
|
|
|
plan := Build(context.Background(), repoRoot)
|
|
|
|
if len(plan.Steps) != 0 {
|
|
t.Fatalf("steps = %#v, want no deterministic go install", plan.Steps)
|
|
}
|
|
if !containsSkip(plan.Skips, "go", "already managed by repo mise declarations") {
|
|
t.Fatalf("skips = %#v, want managed go skip", plan.Skips)
|
|
}
|
|
if len(plan.RepoManagedTools) != 1 || plan.RepoManagedTools[0] != "go" {
|
|
t.Fatalf("repo managed tools = %#v, want [go]", plan.RepoManagedTools)
|
|
}
|
|
}
|
|
|
|
func TestBuildDetectsNodeAndPackageManager(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
writePlanFile(t, repoRoot, ".node-version", "v22.14.0\n")
|
|
writePlanFile(t, repoRoot, "package.json", `{"packageManager":"pnpm@9.15.2"}`)
|
|
|
|
plan := Build(context.Background(), repoRoot)
|
|
|
|
if !containsStep(plan.Steps, "node", "22.14.0", ".node-version") {
|
|
t.Fatalf("steps = %#v, want node step", plan.Steps)
|
|
}
|
|
if !containsStep(plan.Steps, "pnpm", "9.15.2", "package.json#packageManager") {
|
|
t.Fatalf("steps = %#v, want pnpm step", plan.Steps)
|
|
}
|
|
}
|
|
|
|
func TestBuildSkipsPackageManagerWhenNodeIsNotPinned(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
writePlanFile(t, repoRoot, "package.json", `{"packageManager":"pnpm@9.15.2"}`)
|
|
|
|
plan := Build(context.Background(), repoRoot)
|
|
|
|
if containsStep(plan.Steps, "pnpm", "9.15.2", "package.json#packageManager") {
|
|
t.Fatalf("steps = %#v, want no package manager install", plan.Steps)
|
|
}
|
|
if !containsSkip(plan.Skips, "node package manager", "packageManager is pinned but node is not pinned") {
|
|
t.Fatalf("skips = %#v, want node package manager skip", plan.Skips)
|
|
}
|
|
}
|
|
|
|
func TestBuildDetectsPythonAndRust(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
writePlanFile(t, repoRoot, ".python-version", "3.12.9\n")
|
|
writePlanFile(t, repoRoot, "rust-toolchain.toml", "[toolchain]\nchannel = '1.86.0'\n")
|
|
|
|
plan := Build(context.Background(), repoRoot)
|
|
|
|
if !containsStep(plan.Steps, "python", "3.12.9", ".python-version") {
|
|
t.Fatalf("steps = %#v, want python step", plan.Steps)
|
|
}
|
|
if !containsStep(plan.Steps, "rust", "1.86.0", "rust-toolchain.toml") {
|
|
t.Fatalf("steps = %#v, want rust step", plan.Steps)
|
|
}
|
|
}
|
|
|
|
func TestBuildSkipsRustChannelNames(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
writePlanFile(t, repoRoot, "rust-toolchain.toml", "[toolchain]\nchannel = 'stable'\n")
|
|
|
|
plan := Build(context.Background(), repoRoot)
|
|
|
|
if !containsSkip(plan.Skips, "rust", "rust-toolchain.toml channel is not an exact version") {
|
|
t.Fatalf("skips = %#v, want rust exact-version skip", plan.Skips)
|
|
}
|
|
}
|
|
|
|
func TestBuildReportsMalformedMiseTomlAsSkip(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
writePlanFile(t, repoRoot, ".mise.toml", "[tools\nbroken")
|
|
|
|
plan := Build(context.Background(), repoRoot)
|
|
|
|
if !containsSkip(plan.Skips, "repo mise declarations", "could not parse .mise.toml") {
|
|
t.Fatalf("skips = %#v, want malformed .mise.toml skip", plan.Skips)
|
|
}
|
|
}
|
|
|
|
func writePlanFile(t *testing.T, repoRoot, relativePath, contents string) {
|
|
t.Helper()
|
|
path := filepath.Join(repoRoot, relativePath)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll(%s): %v", filepath.Dir(path), err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(%s): %v", relativePath, err)
|
|
}
|
|
}
|
|
|
|
func containsStep(steps []InstallStep, tool, version, source string) bool {
|
|
for _, step := range steps {
|
|
if step.Tool == tool && step.Version == version && step.Source == source {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func containsSkip(skips []SkipNote, target, reasonContains string) bool {
|
|
for _, skip := range skips {
|
|
if skip.Target == target && strings.Contains(skip.Reason, reasonContains) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|