158 lines
5.2 KiB
Go
158 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestMainNoArgsSubprocess runs one orchestration or CLI step.
|
|
// Signature: TestMainNoArgsSubprocess(t *testing.T).
|
|
// Why: covers usage exit path when no command is provided.
|
|
func TestMainNoArgsSubprocess(t *testing.T) {
|
|
if os.Getenv("ANANKE_TEST_MAIN_NO_ARGS") == "1" {
|
|
os.Args = []string{"ananke"}
|
|
main()
|
|
return
|
|
}
|
|
cmd := exec.Command(os.Args[0], "-test.run=TestMainNoArgsSubprocess")
|
|
cmd.Env = append(os.Environ(), "ANANKE_TEST_MAIN_NO_ARGS=1")
|
|
out, err := cmd.CombinedOutput()
|
|
if err == nil {
|
|
t.Fatalf("expected no-args subprocess to fail")
|
|
}
|
|
exitErr, ok := err.(*exec.ExitError)
|
|
if !ok {
|
|
t.Fatalf("expected exit error type, got %T (%v)", err, err)
|
|
}
|
|
if exitErr.ExitCode() != 2 {
|
|
t.Fatalf("expected exit code 2, got %d output=%s", exitErr.ExitCode(), string(out))
|
|
}
|
|
if !strings.Contains(string(out), "Usage:") {
|
|
t.Fatalf("expected usage output, got: %s", string(out))
|
|
}
|
|
}
|
|
|
|
// TestMainStatusSubprocess runs one orchestration or CLI step.
|
|
// Signature: TestMainStatusSubprocess(t *testing.T).
|
|
// Why: covers successful dispatch branch for status command.
|
|
func TestMainStatusSubprocess(t *testing.T) {
|
|
if os.Getenv("ANANKE_TEST_MAIN_STATUS") == "1" {
|
|
cfgPath := os.Getenv("ANANKE_TEST_CFG")
|
|
os.Args = []string{"ananke", "status", "--config", cfgPath}
|
|
main()
|
|
return
|
|
}
|
|
cfgPath := writeTestConfig(t)
|
|
cmd := exec.Command(os.Args[0], "-test.run=TestMainStatusSubprocess")
|
|
cmd.Env = append(os.Environ(),
|
|
"ANANKE_TEST_MAIN_STATUS=1",
|
|
"ANANKE_TEST_CFG="+cfgPath,
|
|
)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("expected status subprocess success: %v\n%s", err, string(out))
|
|
}
|
|
}
|
|
|
|
// TestRunMainDispatchSuccessPaths runs one orchestration or CLI step.
|
|
// Signature: TestRunMainDispatchSuccessPaths(t *testing.T).
|
|
// Why: unit-level dispatch coverage ensures every CLI command route remains wired.
|
|
func TestRunMainDispatchSuccessPaths(t *testing.T) {
|
|
logger := log.New(io.Discard, "", 0)
|
|
restore := stubDispatchCommands(
|
|
func(_ *log.Logger, _ []string) error { return nil },
|
|
func(_ *log.Logger, _ []string) error { return nil },
|
|
func(_ *log.Logger, _ []string) error { return nil },
|
|
func(_ *log.Logger, _ []string) error { return nil },
|
|
func(_ *log.Logger, _ []string) error { return nil },
|
|
func(_ *log.Logger, _ []string) error { return nil },
|
|
)
|
|
defer restore()
|
|
|
|
commands := []string{"startup", "shutdown", "etcd-restore", "daemon", "status", "intent", "help", "-h", "--help"}
|
|
for _, command := range commands {
|
|
code := runMain(logger, []string{"ananke", command})
|
|
if code != 0 {
|
|
t.Fatalf("expected exit code 0 for command %q, got %d", command, code)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRunMainDispatchErrorPaths runs one orchestration or CLI step.
|
|
// Signature: TestRunMainDispatchErrorPaths(t *testing.T).
|
|
// Why: command failures must consistently map to exit code 1 for automation safety.
|
|
func TestRunMainDispatchErrorPaths(t *testing.T) {
|
|
logger := log.New(io.Discard, "", 0)
|
|
fail := errors.New("boom")
|
|
restore := stubDispatchCommands(
|
|
func(_ *log.Logger, _ []string) error { return fail },
|
|
func(_ *log.Logger, _ []string) error { return fail },
|
|
func(_ *log.Logger, _ []string) error { return fail },
|
|
func(_ *log.Logger, _ []string) error { return fail },
|
|
func(_ *log.Logger, _ []string) error { return fail },
|
|
func(_ *log.Logger, _ []string) error { return fail },
|
|
)
|
|
defer restore()
|
|
|
|
commands := []string{"startup", "shutdown", "etcd-restore", "daemon", "status", "intent"}
|
|
for _, command := range commands {
|
|
code := runMain(logger, []string{"ananke", command})
|
|
if code != 1 {
|
|
t.Fatalf("expected exit code 1 for failing command %q, got %d", command, code)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRunMainUsageAndUnknownPaths runs one orchestration or CLI step.
|
|
// Signature: TestRunMainUsageAndUnknownPaths(t *testing.T).
|
|
// Why: no-arg and unknown-command branches should stay deterministic for shell callers.
|
|
func TestRunMainUsageAndUnknownPaths(t *testing.T) {
|
|
logger := log.New(io.Discard, "", 0)
|
|
if code := runMain(logger, []string{"ananke"}); code != 2 {
|
|
t.Fatalf("expected exit code 2 for no-arg usage path, got %d", code)
|
|
}
|
|
if code := runMain(logger, []string{"ananke", "definitely-unknown"}); code != 2 {
|
|
t.Fatalf("expected exit code 2 for unknown command path, got %d", code)
|
|
}
|
|
}
|
|
|
|
// stubDispatchCommands runs one orchestration or CLI step.
|
|
// Signature: stubDispatchCommands(startup, shutdown, etcdRestore, daemon, status, intent func(*log.Logger, []string) error) func().
|
|
// Why: keeps command-var overrides scoped and safely restorable across tests.
|
|
func stubDispatchCommands(
|
|
startup,
|
|
shutdown,
|
|
etcdRestore,
|
|
daemon,
|
|
status,
|
|
intent func(*log.Logger, []string) error,
|
|
) func() {
|
|
prevStartup := startupCommand
|
|
prevShutdown := shutdownCommand
|
|
prevEtcdRestore := etcdRestoreCommand
|
|
prevDaemon := daemonCommand
|
|
prevStatus := statusCommand
|
|
prevIntent := intentCommand
|
|
|
|
startupCommand = startup
|
|
shutdownCommand = shutdown
|
|
etcdRestoreCommand = etcdRestore
|
|
daemonCommand = daemon
|
|
statusCommand = status
|
|
intentCommand = intent
|
|
|
|
return func() {
|
|
startupCommand = prevStartup
|
|
shutdownCommand = prevShutdown
|
|
etcdRestoreCommand = prevEtcdRestore
|
|
daemonCommand = prevDaemon
|
|
statusCommand = prevStatus
|
|
intentCommand = prevIntent
|
|
}
|
|
}
|