194 lines
5.6 KiB
Go
194 lines
5.6 KiB
Go
package dispatch
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestVersion(t *testing.T) {
|
|
var out, errw bytes.Buffer
|
|
for _, arg := range []string{"version", "-V", "--version"} {
|
|
out.Reset()
|
|
errw.Reset()
|
|
if code := run([]string{arg}, &out, &errw); code != exitOK {
|
|
t.Fatalf("%s: exit=%d want %d", arg, code, exitOK)
|
|
}
|
|
if !strings.HasPrefix(out.String(), "crucible ") {
|
|
t.Fatalf("%s: version line %q missing prefix", arg, out.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHelpAndNoArgs(t *testing.T) {
|
|
var out, errw bytes.Buffer
|
|
if code := run([]string{"help"}, &out, &errw); code != exitOK {
|
|
t.Fatalf("help exit=%d want %d", code, exitOK)
|
|
}
|
|
for _, builder := range Registry {
|
|
if !strings.Contains(out.String(), builder.Name) {
|
|
t.Errorf("help output missing builder %q:\n%s", builder.Name, out.String())
|
|
}
|
|
}
|
|
// No args is a usage error (exit 64) but still prints help.
|
|
out.Reset()
|
|
if code := run(nil, &out, &errw); code != exitUsage {
|
|
t.Fatalf("no-args exit=%d want %d", code, exitUsage)
|
|
}
|
|
if out.Len() == 0 {
|
|
t.Fatal("no-args usage error should include help")
|
|
}
|
|
}
|
|
|
|
func TestListCoversRegistry(t *testing.T) {
|
|
var out bytes.Buffer
|
|
list(&out)
|
|
lines := strings.Split(strings.TrimSpace(out.String()), "\n")
|
|
if len(lines) != len(Registry) {
|
|
t.Fatalf("list returned %d rows for %d builders:\n%s", len(lines), len(Registry), out.String())
|
|
}
|
|
builders := make(map[string]Builder, len(Registry))
|
|
for _, builder := range Registry {
|
|
builders[builder.Name] = builder
|
|
}
|
|
seen := make(map[string]bool, len(lines))
|
|
for _, line := range lines {
|
|
fields := strings.Split(strings.TrimSpace(line), "\t")
|
|
if len(fields) != 3 {
|
|
t.Fatalf("list row must be name<TAB>bin<TAB>summary, got %q", line)
|
|
}
|
|
builder, ok := builders[fields[0]]
|
|
if !ok {
|
|
t.Errorf("list returned unregistered builder %q", fields[0])
|
|
continue
|
|
}
|
|
if fields[1] != builder.Bin {
|
|
t.Errorf("builder %q listed binary %q, want %q", builder.Name, fields[1], builder.Bin)
|
|
}
|
|
if strings.TrimSpace(fields[2]) == "" {
|
|
t.Errorf("builder %q listed an empty summary", builder.Name)
|
|
}
|
|
seen[fields[0]] = true
|
|
}
|
|
for _, builder := range Registry {
|
|
if !seen[builder.Name] {
|
|
t.Errorf("list missing builder %q", builder.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnknownBuilderFailsUsage(t *testing.T) {
|
|
var out, errw bytes.Buffer
|
|
if code := run([]string{"frobnicate"}, &out, &errw); code != exitUsage {
|
|
t.Fatalf("unknown builder exit=%d want %d", code, exitUsage)
|
|
}
|
|
if errw.Len() == 0 {
|
|
t.Fatal("unknown builder should explain the usage error")
|
|
}
|
|
}
|
|
|
|
func TestUnwiredBuilderFailsClosed(t *testing.T) {
|
|
for _, b := range Registry {
|
|
if b.Wired {
|
|
continue
|
|
}
|
|
var out, errw bytes.Buffer
|
|
// Via dispatcher.
|
|
if code := run([]string{b.Name}, &out, &errw); code != exitUnwired {
|
|
t.Errorf("crucible %s: exit=%d want %d (must fail closed)", b.Name, code, exitUnwired)
|
|
}
|
|
if errw.Len() == 0 {
|
|
t.Errorf("crucible %s: missing fail-closed explanation", b.Name)
|
|
}
|
|
// Via standalone shim path.
|
|
out.Reset()
|
|
errw.Reset()
|
|
if code := runBuilder(b.Name, nil, &out, &errw); code != exitUnwired {
|
|
t.Errorf("%s: exit=%d want %d (must fail closed)", b.Bin, code, exitUnwired)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWiredBuilderRejectsBadInvocation(t *testing.T) {
|
|
for _, b := range Registry {
|
|
if !b.Wired {
|
|
continue
|
|
}
|
|
// No subcommand is a usage error (it must never silently delegate).
|
|
var out, errw bytes.Buffer
|
|
if code := runBuilder(b.Name, nil, &out, &errw); code != exitUsage {
|
|
t.Errorf("crucible %s (no subcommand): exit=%d want %d", b.Name, code, exitUsage)
|
|
}
|
|
// Unknown subcommand is a usage error, not a delegate.
|
|
out.Reset()
|
|
errw.Reset()
|
|
if code := runBuilder(b.Name, []string{"frobnicate"}, &out, &errw); code != exitUsage {
|
|
t.Errorf("crucible %s frobnicate: exit=%d want %d", b.Name, code, exitUsage)
|
|
}
|
|
if errw.Len() == 0 {
|
|
t.Errorf("crucible %s frobnicate: missing usage explanation", b.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuilderHelpIsOK(t *testing.T) {
|
|
for _, b := range Registry {
|
|
var out, errw bytes.Buffer
|
|
if code := runBuilder(b.Name, []string{"--help"}, &out, &errw); code != exitOK {
|
|
t.Errorf("%s --help: exit=%d want %d", b.Name, code, exitOK)
|
|
}
|
|
if !strings.Contains(out.String(), b.Name) || !strings.Contains(out.String(), b.Bin) {
|
|
t.Errorf("%s --help: missing builder identity", b.Name)
|
|
}
|
|
for _, subcommand := range b.subcommands() {
|
|
if !strings.Contains(out.String(), subcommand) {
|
|
t.Errorf("%s --help: missing subcommand %q", b.Name, subcommand)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every legacy command named in command-surface.md must have exactly one
|
|
// canonical home (Builder.Legacy), so the migrated surface has no gaps or
|
|
// ambiguous homes.
|
|
func TestLegacyCommandsHaveExactlyOneHome(t *testing.T) {
|
|
legacy := []string{
|
|
"build", "build-module", "extract", "validate", "compare",
|
|
"build-haks", "apply-hak-manifest",
|
|
"validate-topdata", "build-topdata", "build-top-package", "compare-topdata", "convert-topdata",
|
|
"build-wiki", "deploy-wiki",
|
|
}
|
|
for _, cmd := range legacy {
|
|
homes := 0
|
|
for _, b := range Registry {
|
|
for _, c := range b.Legacy {
|
|
if c == cmd {
|
|
homes++
|
|
}
|
|
}
|
|
}
|
|
if homes != 1 {
|
|
t.Errorf("legacy command %q has %d canonical homes, want 1", cmd, homes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMenuItemsSkipsUnwiredIncludesWired(t *testing.T) {
|
|
items := menuItems()
|
|
if len(items) == 0 {
|
|
t.Fatal("expected menu items")
|
|
}
|
|
sawModuleBuild := false
|
|
for _, it := range items {
|
|
if it.Args[0] == "depot" {
|
|
t.Errorf("unwired builder %q must not appear in the menu", it.Args[0])
|
|
}
|
|
if len(it.Args) == 2 && it.Args[0] == "module" && it.Args[1] == "build" {
|
|
sawModuleBuild = true
|
|
}
|
|
}
|
|
if !sawModuleBuild {
|
|
t.Error("expected 'module build' in the menu")
|
|
}
|
|
}
|