flags_test.go
Rama main · réplica de lectura del repositorio original.
package main
import (
"errors"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/sophotechlabs/spinoza/internal/debugcontainer"
"github.com/sophotechlabs/spinoza/internal/version"
)
func TestDefaultsMatchTheDocumentedOnes(t *testing.T) {
opts, err := parseFlags(nil)
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.addr != "127.0.0.1:34115" {
t.Fatalf("addr = %q", opts.addr)
}
if opts.cluster.DebugImage != debugcontainer.DefaultImage {
t.Fatalf("debug image = %q", opts.cluster.DebugImage)
}
if opts.cluster.KubectlBinary != debugcontainer.DefaultBinary {
t.Fatalf("kubectl = %q", opts.cluster.KubectlBinary)
}
if opts.cluster.PromSpec != "" {
t.Fatalf("prometheus = %q, want discovery", opts.cluster.PromSpec)
}
if opts.tokenFile != "" {
t.Fatalf("token file = %q, want none unless a script asked for one", opts.tokenFile)
}
}
func TestEveryFlagIsCarriedThrough(t *testing.T) {
opts, err := parseFlags([]string{
"-addr", "127.0.0.1:9999",
"-open",
"-debug-image", "ghcr.io/acme/toolbox:2.1",
"-kubectl", "/usr/local/bin/kubectl",
"-prometheus", "monitoring/prom:9090",
"-token-file", "/tmp/spinoza.token",
})
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.addr != "127.0.0.1:9999" {
t.Fatalf("addr = %q", opts.addr)
}
if !opts.openBrowser {
t.Fatal("open was dropped")
}
if opts.tokenFile != "/tmp/spinoza.token" {
t.Fatalf("token file = %q", opts.tokenFile)
}
if opts.cluster.DebugImage != "ghcr.io/acme/toolbox:2.1" {
t.Fatalf("debug image = %q", opts.cluster.DebugImage)
}
if opts.cluster.KubectlBinary != "/usr/local/bin/kubectl" {
t.Fatalf("kubectl = %q", opts.cluster.KubectlBinary)
}
if opts.cluster.PromSpec != "monitoring/prom:9090" {
t.Fatalf("prometheus = %q", opts.cluster.PromSpec)
}
}
func TestNodeShellFlagRequiresAPinnedImage(t *testing.T) {
_, err := parseFlags([]string{"-node-shell", "-node-shell-image", "busybox:1.37"})
if err == nil || err.Error() != "node-shell-image must be pinned by sha256 digest" {
t.Fatalf("error = %v", err)
}
}
func TestTheTokenFileIsWrittenForScriptsOnly(t *testing.T) {
path := filepath.Join(t.TempDir(), "token")
err := writeTokenFile(path, "s3cret")
if err != nil {
t.Fatalf("write: %v", err)
}
data, readErr := os.ReadFile(path)
if readErr != nil {
t.Fatalf("read: %v", readErr)
}
if string(data) != "s3cret\n" {
t.Fatalf("contents = %q", data)
}
info, statErr := os.Stat(path)
if statErr != nil {
t.Fatalf("stat: %v", statErr)
}
if info.Mode().Perm() != 0o600 {
t.Fatalf("mode = %v, want the token readable by its owner only", info.Mode().Perm())
}
}
func TestNoTokenFileMeansNoFile(t *testing.T) {
err := writeTokenFile("", "s3cret")
if err != nil {
t.Fatalf("write: %v", err)
}
}
func TestAnUnwritableTokenFileIsReported(t *testing.T) {
path := filepath.Join(t.TempDir(), "missing", "token")
err := writeTokenFile(path, "s3cret")
if err == nil {
t.Fatal("a token file that cannot be written was reported as fine")
}
}
func TestEveryLogLevelIsAccepted(t *testing.T) {
cases := map[string]slog.Level{
"debug": slog.LevelDebug,
"info": slog.LevelInfo,
"warn": slog.LevelWarn,
"error": slog.LevelError,
}
for name, want := range cases {
t.Run(name, func(t *testing.T) {
opts, err := parseFlags([]string{"-log-level", name})
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.logLevel != want {
t.Fatalf("level = %v, want %v", opts.logLevel, want)
}
})
}
}
func TestAnUnknownLogLevelIsRefused(t *testing.T) {
_, err := parseFlags([]string{"-log-level", "chatty"})
if err == nil {
t.Fatal("an unknown log level was accepted")
}
if !strings.Contains(err.Error(), "debug, info, warn, error") {
t.Fatalf("err = %v, want it to name the levels", err)
}
}
func TestTheVersionFlagIsCarriedThrough(t *testing.T) {
opts, err := parseFlags([]string{"-version"})
if err != nil {
t.Fatalf("parse: %v", err)
}
if !opts.showVersion {
t.Fatal("version was dropped")
}
}
func TestTheLicenseFlagIsCarriedThrough(t *testing.T) {
opts, err := parseFlags([]string{"-license"})
if err != nil {
t.Fatalf("parse: %v", err)
}
if !opts.showLicense {
t.Fatal("license was dropped")
}
}
func TestTheEnvironmentFillsInWhatTheDesktopBundleCannotPass(t *testing.T) {
t.Setenv("SPINOZA_ADDR", "127.0.0.1:7777")
t.Setenv("SPINOZA_OPEN", "true")
t.Setenv("SPINOZA_TOKEN_FILE", "/run/spinoza.token")
t.Setenv("SPINOZA_LOG_LEVEL", "debug")
t.Setenv("SPINOZA_DEBUG_IMAGE", "ghcr.io/acme/toolbox:9")
t.Setenv("SPINOZA_KUBECTL", "/opt/kubectl")
t.Setenv("SPINOZA_PROMETHEUS", "obs/prom:9090")
opts, err := parseFlags(nil)
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.addr != "127.0.0.1:7777" {
t.Fatalf("addr = %q", opts.addr)
}
if !opts.openBrowser {
t.Fatal("open was not read from the environment")
}
if opts.tokenFile != "/run/spinoza.token" {
t.Fatalf("token file = %q", opts.tokenFile)
}
if opts.logLevel != slog.LevelDebug {
t.Fatalf("log level = %v", opts.logLevel)
}
if opts.cluster.DebugImage != "ghcr.io/acme/toolbox:9" {
t.Fatalf("debug image = %q", opts.cluster.DebugImage)
}
if opts.cluster.KubectlBinary != "/opt/kubectl" {
t.Fatalf("kubectl = %q", opts.cluster.KubectlBinary)
}
if opts.cluster.PromSpec != "obs/prom:9090" {
t.Fatalf("prometheus = %q", opts.cluster.PromSpec)
}
}
func TestAFlagBeatsTheEnvironment(t *testing.T) {
t.Setenv("SPINOZA_ADDR", "127.0.0.1:7777")
opts, err := parseFlags([]string{"-addr", "127.0.0.1:8888"})
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.addr != "127.0.0.1:8888" {
t.Fatalf("addr = %q, want the flag to win", opts.addr)
}
}
func TestAnUnreadableBooleanInTheEnvironmentIsIgnored(t *testing.T) {
cases := map[string]bool{
"true": true,
"1": true,
"false": false,
"yes": false,
}
for value, want := range cases {
t.Run(value, func(t *testing.T) {
t.Setenv("SPINOZA_OPEN", value)
opts, err := parseFlags(nil)
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.openBrowser != want {
t.Fatalf("open = %v for %q, want %v", opts.openBrowser, value, want)
}
})
}
}
func TestTheBigClusterKnobsKeepTodaysValuesByDefault(t *testing.T) {
opts, err := parseFlags(nil)
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.cluster.Kubeconfig != "" {
t.Fatalf("kubeconfig = %q, want the usual lookup rules", opts.cluster.Kubeconfig)
}
if opts.cluster.ClientQPS != defaultQPS {
t.Fatalf("qps = %v, want %v", opts.cluster.ClientQPS, float32(defaultQPS))
}
if opts.cluster.ClientBurst != defaultBurst {
t.Fatalf("burst = %d, want %d", opts.cluster.ClientBurst, defaultBurst)
}
if opts.cluster.SyncTimeout != defaultSync {
t.Fatalf("sync timeout = %s, want %s", opts.cluster.SyncTimeout, defaultSync)
}
if opts.cluster.WarmConcurrency != defaultWarm {
t.Fatalf("warm concurrency = %d, want %d", opts.cluster.WarmConcurrency, defaultWarm)
}
if opts.cluster.CountBudget != defaultCountBudget {
t.Fatalf("count budget = %s, want %s", opts.cluster.CountBudget, defaultCountBudget)
}
if opts.cluster.CountPerType != defaultCountPerType {
t.Fatalf("count timeout = %s, want %s", opts.cluster.CountPerType, defaultCountPerType)
}
if opts.cluster.CountConcurrency != defaultCountConcurrency {
t.Fatalf("count concurrency = %d, want %d", opts.cluster.CountConcurrency, defaultCountConcurrency)
}
}
func TestABigClusterCanRaiseEveryLimit(t *testing.T) {
opts, err := parseFlags([]string{
"-kubeconfig", "/etc/spinoza/kubeconfig",
"-qps", "200",
"-burst", "400",
"-sync-timeout", "3m",
"-warm-concurrency", "24",
"-count-budget", "90s",
"-count-timeout", "20s",
"-count-concurrency", "48",
})
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.cluster.Kubeconfig != "/etc/spinoza/kubeconfig" {
t.Fatalf("kubeconfig = %q", opts.cluster.Kubeconfig)
}
if opts.cluster.ClientQPS != 200 {
t.Fatalf("qps = %v", opts.cluster.ClientQPS)
}
if opts.cluster.ClientBurst != 400 {
t.Fatalf("burst = %d", opts.cluster.ClientBurst)
}
if opts.cluster.SyncTimeout != 3*time.Minute {
t.Fatalf("sync timeout = %s", opts.cluster.SyncTimeout)
}
if opts.cluster.WarmConcurrency != 24 {
t.Fatalf("warm concurrency = %d", opts.cluster.WarmConcurrency)
}
if opts.cluster.CountBudget != 90*time.Second {
t.Fatalf("count budget = %s", opts.cluster.CountBudget)
}
if opts.cluster.CountPerType != 20*time.Second {
t.Fatalf("count timeout = %s", opts.cluster.CountPerType)
}
if opts.cluster.CountConcurrency != 48 {
t.Fatalf("count concurrency = %d", opts.cluster.CountConcurrency)
}
}
func TestNonPositiveBigClusterLimitsAreRefused(t *testing.T) {
cases := [][]string{
{"-qps", "0"},
{"-qps", "NaN"},
{"-qps", "+Inf"},
{"-burst", "0"},
{"-sync-timeout", "0s"},
{"-warm-concurrency", "-1"},
{"-count-budget", "-1s"},
{"-count-timeout", "0s"},
{"-count-concurrency", "-1"},
}
for _, args := range cases {
t.Run(strings.Join(args, "="), func(t *testing.T) {
if _, err := parseFlags(args); err == nil {
t.Fatalf("arguments %v were accepted", args)
}
})
}
}
func TestTheEnvironmentCanRaiseTheLimitsToo(t *testing.T) {
t.Setenv("SPINOZA_KUBECONFIG", "/tmp/kubeconfig")
t.Setenv("SPINOZA_QPS", "150")
t.Setenv("SPINOZA_BURST", "300")
t.Setenv("SPINOZA_SYNC_TIMEOUT", "2m")
t.Setenv("SPINOZA_COUNT_CONCURRENCY", "36")
opts, err := parseFlags(nil)
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.cluster.Kubeconfig != "/tmp/kubeconfig" {
t.Fatalf("kubeconfig = %q", opts.cluster.Kubeconfig)
}
if opts.cluster.ClientQPS != 150 {
t.Fatalf("qps = %v", opts.cluster.ClientQPS)
}
if opts.cluster.ClientBurst != 300 {
t.Fatalf("burst = %d", opts.cluster.ClientBurst)
}
if opts.cluster.SyncTimeout != 2*time.Minute {
t.Fatalf("sync timeout = %s", opts.cluster.SyncTimeout)
}
if opts.cluster.CountConcurrency != 36 {
t.Fatalf("count concurrency = %d", opts.cluster.CountConcurrency)
}
}
func TestUnreadableLimitsInTheEnvironmentFallBack(t *testing.T) {
t.Setenv("SPINOZA_QPS", "fast")
t.Setenv("SPINOZA_BURST", "lots")
t.Setenv("SPINOZA_SYNC_TIMEOUT", "ages")
opts, err := parseFlags(nil)
if err != nil {
t.Fatalf("parse: %v", err)
}
if opts.cluster.ClientQPS != defaultQPS {
t.Fatalf("qps = %v, want the default", opts.cluster.ClientQPS)
}
if opts.cluster.ClientBurst != defaultBurst {
t.Fatalf("burst = %d, want the default", opts.cluster.ClientBurst)
}
if opts.cluster.SyncTimeout != defaultSync {
t.Fatalf("sync timeout = %s, want the default", opts.cluster.SyncTimeout)
}
}
func TestAnUnknownFlagIsAnError(t *testing.T) {
_, err := parseFlags([]string{"-nope"})
if err == nil {
t.Fatal("an unknown flag was accepted")
}
}
func TestHelpIsNotAFailure(t *testing.T) {
_, err := parseFlags([]string{"-h"})
if !errors.Is(err, errHelp) {
t.Fatalf("err = %v, want the help sentinel so the process can exit 0", err)
}
}
func TestABadFlagIsStillAFailure(t *testing.T) {
_, err := parseFlags([]string{"-nonsense"})
if err == nil {
t.Fatal("an unknown flag was accepted")
}
if errors.Is(err, errHelp) {
t.Fatal("an unknown flag was treated as a help request")
}
}
func TestSettingsComeFromTheProcessArguments(t *testing.T) {
original := os.Args
t.Cleanup(func() { os.Args = original })
os.Args = []string{"spinoza", "--addr", "127.0.0.1:9999", "--log-level", "debug"}
result, err := settingsFromArgs()
if err != nil {
t.Fatalf("settingsFromArgs: %v", err)
}
if result.addr != "127.0.0.1:9999" {
t.Fatalf("addr = %q, want the one the arguments named", result.addr)
}
if result.logLevel != slog.LevelDebug {
t.Fatalf("log level = %v, want debug", result.logLevel)
}
}
func TestSettingsRefuseAnArgumentTheyDoNotKnow(t *testing.T) {
original := os.Args
t.Cleanup(func() { os.Args = original })
os.Args = []string{"spinoza", "--nonsense"}
_, err := settingsFromArgs()
if err == nil {
t.Fatal("settingsFromArgs returned nil error for an unknown flag")
}
}
func TestTheCheckerCarriesThisBuildsVersion(t *testing.T) {
checker := updateChecker()
if got := checker.Status(t.Context()).Current; got != version.String() {
t.Fatalf("current = %q, want this build's version", got)
}
}