Files
ctrld/cmd/cli/main_test.go
T

77 lines
2.3 KiB
Go

package cli
import (
"fmt"
"os"
"os/exec"
"strings"
"sync"
"testing"
"github.com/rs/zerolog"
)
// logOutput is the log sink for the whole test binary. Tests share it with any
// background goroutine the code under test starts (watchdogs, timers), so it
// must tolerate concurrent writes.
var logOutput syncBuffer
// syncBuffer is a strings.Builder guarded by a mutex.
type syncBuffer struct {
mu sync.Mutex
sb strings.Builder
}
func (b *syncBuffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.sb.Write(p)
}
func (b *syncBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.sb.String()
}
// envFakeVersionOutput makes this test binary impersonate a ctrld executable: when
// set, the process writes the value to stdout and exits without running any test, so
// binaryVersion() can be exercised on every platform without building or shipping a
// fixture binary. The value envFakeVersionSilent produces no output at all, which
// reproduces a ctrld.exe_previous that exists but reports no version.
//
// This must be handled before m.Run(), which is what parses the test flags: the child
// is invoked as "<binary> --version" and would otherwise die on an unknown flag.
const (
envFakeVersionOutput = "CTRLD_TEST_FAKE_VERSION_OUTPUT"
envFakeVersionSilent = "<silent>"
)
func TestMain(m *testing.M) {
if out := os.Getenv(envFakeVersionOutput); out != "" {
if out != envFakeVersionSilent {
fmt.Println(out)
}
os.Exit(0)
}
l := zerolog.New(&logOutput)
mainLog.Store(&l)
// Stub the self-upgrade command builder for the whole test binary. The real
// builder execs os.Executable() — which under `go test` IS this test binary
// — with positional args ("upgrade", ...). `go test` stops flag parsing at
// the first positional arg and ignores the rest, so the child just re-runs
// the entire suite, hits the upgrade tests again, and spawns more children:
// a fork bomb of detached processes that stalls the host and (on Windows)
// holds the test binary's image locked, breaking CI artifact cleanup.
// Point it at the test binary with a no-match -test.run so any test that
// reaches performUpgrade still exercises the cmd.Start() success path while
// the child exits immediately without recursing.
newUpgradeCmd = func(exe string) *exec.Cmd {
return exec.Command(exe, "-test.run=^$")
}
os.Exit(m.Run())
}