- helpOnly also counts no command word, list, help, cobra's shell completion requests and --help=<bool>, read the way pflag does - TestHelpOnlyAgreesWithCobra pins the classification against bonfire's real dispatch, with the passthrough --help case as the exception - console and logging docs describe which invocations boot quietly
198 lines
6.5 KiB
Go
198 lines
6.5 KiB
Go
package diary_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/bonfire"
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
"git.golem15.com/golem15/summercms/modules/diary"
|
|
"git.golem15.com/golem15/summercms/modules/party"
|
|
)
|
|
|
|
// The records the boot-logging test plugin writes.
|
|
const (
|
|
bootRegisterInfo = "operators registered"
|
|
bootBootInfo = "registries collected"
|
|
bootBootWarn = "registry without operators"
|
|
)
|
|
|
|
// bootLogPlugin logs from Register through slog.Default, the way a plugin
|
|
// written before diary does, and from Boot through a component logger.
|
|
type bootLogPlugin struct{}
|
|
|
|
func (bootLogPlugin) ID() string { return "acme.bootlog" }
|
|
func (bootLogPlugin) Requires() []string { return nil }
|
|
|
|
func (bootLogPlugin) Register(*backpack.App) error {
|
|
slog.Default().Info(bootRegisterInfo, "count", 2)
|
|
return nil
|
|
}
|
|
|
|
func (bootLogPlugin) Boot(*backpack.App) error {
|
|
log := diary.For("bootlog")
|
|
log.Info(bootBootInfo, "count", 3)
|
|
log.Warn(bootBootWarn, "registry", "refunds")
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
party.Register(bootLogPlugin{})
|
|
}
|
|
|
|
// bootLogYAML is a console plus a daily file at info, as in the tracer test.
|
|
const bootLogYAML = `default: stack
|
|
level: info
|
|
channels:
|
|
stack:
|
|
driver: stack
|
|
channels: [console, daily]
|
|
console:
|
|
driver: console
|
|
format: auto
|
|
daily:
|
|
driver: daily
|
|
path: storage/logs/summer.log
|
|
days: 14
|
|
format: json
|
|
level: info
|
|
`
|
|
|
|
// TestBootQuietSmoke runs the generated main's sequence (PeekGlobals,
|
|
// Bootstrap and Setup with FromGlobals, party.Activate, a root with
|
|
// OnGlobals(diary.ApplyGlobals), Execute) with a plugin that logs while it
|
|
// registers and boots: an invocation that only asks for help keeps the boot
|
|
// Info records off the console, every other one prints them, and the daily
|
|
// file and the console's warnings are never affected.
|
|
func TestBootQuietSmoke(t *testing.T) {
|
|
rows := []struct {
|
|
name string
|
|
args []string
|
|
literal bool // Options as a struct literal, without Args
|
|
wantInfo bool // the boot Info records reach the console
|
|
}{
|
|
{name: "serve --help", args: []string{"serve", "--help"}},
|
|
{name: "serve", args: []string{"serve"}, wantInfo: true},
|
|
{name: "literal options", args: []string{"serve", "--help"}, literal: true, wantInfo: true},
|
|
{name: "no args", args: []string{}},
|
|
{name: "list", args: []string{"list"}},
|
|
{name: "help serve", args: []string{"help", "serve"}},
|
|
{name: "-h", args: []string{"-h"}},
|
|
{name: "-v serve --help", args: []string{"-v", "serve", "--help"}, wantInfo: true},
|
|
{name: "queue:work", args: []string{"queue:work"}, wantInfo: true},
|
|
}
|
|
for _, row := range rows {
|
|
t.Run(row.name, func(t *testing.T) {
|
|
t.Cleanup(diary.Reset)
|
|
root := t.TempDir()
|
|
configDir := filepath.Join(root, "config")
|
|
if err := os.MkdirAll(configDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(configDir, "log.yaml"), []byte(bootLogYAML), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var console, stdout, stderr bytes.Buffer
|
|
now := func() time.Time { return time.Date(2026, 10, 28, 12, 0, 0, 0, time.Local) }
|
|
|
|
g := bonfire.PeekGlobals(row.args)
|
|
var opts diary.Options
|
|
if row.literal {
|
|
opts = diary.Options{Verbosity: g.Verbosity, Quiet: g.Quiet, Color: g.Color}
|
|
} else {
|
|
opts = diary.FromGlobals(g)
|
|
opts.Args = slices.Clone(row.args)
|
|
if opts.Args == nil {
|
|
opts.Args = []string{}
|
|
}
|
|
}
|
|
opts.Root, opts.Stderr, opts.Environ, opts.Now = root, &console, []string{}, now
|
|
|
|
diary.Bootstrap(&console, opts)
|
|
cfg, err := compass.Open(compass.Options{Dir: configDir, Env: g.Env, Environ: []string{}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := diary.Setup(cfg, opts); err != nil {
|
|
t.Fatalf("Setup: %v", err)
|
|
}
|
|
app := backpack.New(cfg)
|
|
if _, err := party.Activate(app, []string{"acme.bootlog"}); err != nil {
|
|
t.Fatalf("Activate: %v", err)
|
|
}
|
|
commands := []bonfire.Command{
|
|
{Name: "serve", Description: "Start the HTTP server", Run: func(context.Context, bonfire.Input, bonfire.Output) error { return nil }},
|
|
{Name: "queue:work", Description: "Run the job workers", Run: func(context.Context, bonfire.Input, bonfire.Output) error { return nil }},
|
|
}
|
|
rootCmd, err := bonfire.NewRootIO("acme", commands, strings.NewReader(""), &stdout, &stderr,
|
|
bonfire.WithVersion(bonfire.Version{App: "v1.0.0", Framework: "v1.0.0"}), bonfire.OnGlobals(diary.ApplyGlobals))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if code := bonfire.Execute(context.Background(), rootCmd, row.args); code != bonfire.ExitOK {
|
|
t.Fatalf("Execute(%q) = %d, want 0; stderr:\n%s", row.args, code, stderr.String())
|
|
}
|
|
if slices.Contains(row.args, "--help") && !strings.Contains(stdout.String(), "serve") {
|
|
t.Errorf("Execute(%q) printed no help:\n%s", row.args, stdout.String())
|
|
}
|
|
|
|
out := console.String()
|
|
for _, msg := range []string{bootRegisterInfo, bootBootInfo} {
|
|
if got := strings.Contains(out, msg); got != row.wantInfo {
|
|
t.Errorf("console holds %q = %v, want %v:\n%s", msg, got, row.wantInfo, out)
|
|
}
|
|
}
|
|
if !strings.Contains(out, bootBootWarn) {
|
|
t.Errorf("console lacks the boot warning %q:\n%s", bootBootWarn, out)
|
|
}
|
|
|
|
if err := diary.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
files, err := filepath.Glob(filepath.Join(root, "storage", "logs", "*.log"))
|
|
if err != nil || len(files) != 1 {
|
|
t.Fatalf("daily files = %v (%v), want one", files, err)
|
|
}
|
|
raw, err := os.ReadFile(files[0])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, msg := range []string{bootRegisterInfo, bootBootInfo, bootBootWarn} {
|
|
if !bytes.Contains(raw, []byte(msg)) {
|
|
t.Errorf("daily file lacks %q:\n%s", msg, raw)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFromGlobalsArgs: FromGlobals records a copy of the process arguments
|
|
// without the program name, never nil.
|
|
func TestFromGlobalsArgs(t *testing.T) {
|
|
saved := os.Args
|
|
t.Cleanup(func() { os.Args = saved })
|
|
|
|
os.Args = []string{"acme", "serve", "--help"}
|
|
opts := diary.FromGlobals(bonfire.Globals{})
|
|
if want := []string{"serve", "--help"}; !slices.Equal(opts.Args, want) {
|
|
t.Fatalf("Args = %q, want %q", opts.Args, want)
|
|
}
|
|
opts.Args[0] = "migrate"
|
|
if os.Args[1] != "serve" {
|
|
t.Errorf("changing Args changed os.Args to %q", os.Args)
|
|
}
|
|
|
|
os.Args = []string{"acme"}
|
|
if opts := diary.FromGlobals(bonfire.Globals{}); opts.Args == nil || len(opts.Args) != 0 {
|
|
t.Errorf("Args = %#v, want an empty non-nil slice", opts.Args)
|
|
}
|
|
}
|