5cbe3020d3
scripts/orca-aggregate.sh: 10s aggregator, cluster.json merge + drift-events rsync + remediation trigger (P10b stub). scripts/orca- watchdog.sh: C-11 starvation detection. internal/cli/collector.go: orca collector start/stop/status. Tests: CLI + bats. ---ci--- project: orca phase: 09 milestone: v0.11 status: execute ---/ci---
161 lines
4.3 KiB
Go
161 lines
4.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestCollectorCmdRegistered(t *testing.T) {
|
|
found := false
|
|
for _, cmd := range rootCmd.Commands() {
|
|
if cmd.Name() == "collector" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("collector command not registered on root")
|
|
}
|
|
}
|
|
|
|
func TestCollectorSubcommandsRegistered(t *testing.T) {
|
|
want := map[string]bool{"start": false, "stop": false, "status": false}
|
|
for _, cmd := range collectorCmd.Commands() {
|
|
if _, ok := want[cmd.Name()]; ok {
|
|
want[cmd.Name()] = true
|
|
}
|
|
}
|
|
for name, found := range want {
|
|
if !found {
|
|
t.Errorf("collector subcommand %q not registered", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func runCollectorCmd(t *testing.T, root string, dryRun bool, args ...string) (string, error) {
|
|
t.Helper()
|
|
resetRootFlags(t)
|
|
full := append([]string{"collector"}, args...)
|
|
if root != "" {
|
|
full = append(full, "--root", root)
|
|
}
|
|
if dryRun && (len(args) > 0 && (args[0] == "start" || args[0] == "stop")) {
|
|
full = append(full, "--dry-run")
|
|
}
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs(full)
|
|
err := rootCmd.Execute()
|
|
return buf.String(), err
|
|
}
|
|
|
|
func TestCollectorStartEmitsArtifacts(t *testing.T) {
|
|
root := t.TempDir()
|
|
out, err := runCollectorCmd(t, root, true, "start")
|
|
if err != nil {
|
|
t.Fatalf("orca collector start: %v\n%s", err, out)
|
|
}
|
|
wantFiles := []string{
|
|
filepath.Join(root, collectorScriptDir, collectorAggregateSh),
|
|
filepath.Join(root, collectorScriptDir, collectorWatchdogSh),
|
|
filepath.Join(root, collectorUnitDir, collectorAggregateSvc),
|
|
filepath.Join(root, collectorUnitDir, collectorAggregateTmr),
|
|
filepath.Join(root, collectorUnitDir, collectorWatchdogSvc),
|
|
filepath.Join(root, collectorUnitDir, collectorWatchdogTmr),
|
|
}
|
|
for _, p := range wantFiles {
|
|
if _, err := os.Stat(p); err != nil {
|
|
t.Errorf("expected emitted file %s: %v", p, err)
|
|
}
|
|
}
|
|
for _, p := range []string{
|
|
filepath.Join(root, collectorScriptDir, collectorAggregateSh),
|
|
filepath.Join(root, collectorScriptDir, collectorWatchdogSh),
|
|
} {
|
|
info, err := os.Stat(p)
|
|
if err != nil {
|
|
t.Fatalf("stat %s: %v", p, err)
|
|
}
|
|
if perm := info.Mode().Perm(); perm&0o111 == 0 {
|
|
t.Errorf("expected executable bit on %s, got %o", p, perm)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCollectorStartStatusRunning(t *testing.T) {
|
|
root := t.TempDir()
|
|
if _, err := runCollectorCmd(t, root, true, "start"); err != nil {
|
|
t.Fatalf("start: %v", err)
|
|
}
|
|
agg, wd := collectorRunning(root)
|
|
if !agg || !wd {
|
|
t.Errorf("expected both running, got agg=%t wd=%t", agg, wd)
|
|
}
|
|
out, err := runCollectorCmd(t, root, true, "status")
|
|
if err != nil {
|
|
t.Fatalf("status: %v", err)
|
|
}
|
|
if !contains(out, "running") {
|
|
t.Errorf("status output should say running, got %q", out)
|
|
}
|
|
}
|
|
|
|
func TestCollectorStopRemovesArtifacts(t *testing.T) {
|
|
root := t.TempDir()
|
|
if _, err := runCollectorCmd(t, root, true, "start"); err != nil {
|
|
t.Fatalf("start: %v", err)
|
|
}
|
|
out, err := runCollectorCmd(t, root, true, "stop")
|
|
if err != nil {
|
|
t.Fatalf("stop: %v\n%s", err, out)
|
|
}
|
|
wantFiles := []string{
|
|
filepath.Join(root, collectorScriptDir, collectorAggregateSh),
|
|
filepath.Join(root, collectorScriptDir, collectorWatchdogSh),
|
|
filepath.Join(root, collectorUnitDir, collectorAggregateSvc),
|
|
filepath.Join(root, collectorUnitDir, collectorAggregateTmr),
|
|
filepath.Join(root, collectorUnitDir, collectorWatchdogSvc),
|
|
filepath.Join(root, collectorUnitDir, collectorWatchdogTmr),
|
|
}
|
|
for _, p := range wantFiles {
|
|
if _, err := os.Stat(p); !os.IsNotExist(err) {
|
|
t.Errorf("expected %s removed, got %v", p, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCollectorStatusWhenStopped(t *testing.T) {
|
|
root := t.TempDir()
|
|
out, err := runCollectorCmd(t, root, true, "status")
|
|
if err != nil {
|
|
t.Fatalf("status: %v", err)
|
|
}
|
|
if !contains(out, "stopped") {
|
|
t.Errorf("expected stopped, got %q", out)
|
|
}
|
|
agg, wd := collectorRunning(root)
|
|
if agg || wd {
|
|
t.Errorf("expected neither running, got agg=%t wd=%t", agg, wd)
|
|
}
|
|
}
|
|
|
|
func TestCollectorResolveRootRejectsRelative(t *testing.T) {
|
|
collectorRoot = "tmp/relative"
|
|
_, err := collectorResolveRoot()
|
|
if err == nil {
|
|
t.Error("expected error for relative root")
|
|
}
|
|
collectorRoot = "/"
|
|
r, err := collectorResolveRoot()
|
|
if err != nil || r != "/" {
|
|
t.Errorf("expected / for default, got %q err=%v", r, err)
|
|
}
|
|
}
|
|
|
|
func contains(haystack, needle string) bool {
|
|
return bytes.Contains([]byte(haystack), []byte(needle))
|
|
}
|