test(bonfire): goldens for list, help and errors

- list, list --all, list --format=json and an --ansi styled list (D-13, D-16)
- admin:token:create help and a passthrough help; help <cmd> equals --help (D-14)
- error blocks: hint with {bin}, unknown command, -v chain on stderr (D-18)
- CheckDocs gaps in order and sorted; the version header and BuildVersion
- goldens render with color detection cleared; -update rewrites them
This commit is contained in:
Jakub Zych
2026-10-21 19:22:00 +02:00
parent 9641a1be59
commit 7b58ad5fa5
12 changed files with 827 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
package bonfire
import (
"reflect"
"testing"
)
// TestCheckDocs: every missing item is reported in a fixed order, blank
// text counts as missing, results are sorted by command name and a fully
// documented command reports nothing.
func TestCheckDocs(t *testing.T) {
complete := Command{
Name: "acme:sync", Description: "Sync", Long: "Syncs.",
Args: []Arg{{Name: "target", Description: "Where to"}},
Flags: []Flag{{Name: "dry-run", Description: "Only print", Bare: true}},
Examples: []Example{{Command: "acme:sync prod"}},
}
if got := CheckDocs([]Command{complete}); len(got) != 0 {
t.Fatalf("complete command reported: %v", got)
}
if got := CheckDocs(nil); len(got) != 0 {
t.Fatalf("no commands reported: %v", got)
}
blank := complete
blank.Name, blank.Description, blank.Long = "acme:blank", " ", "\n\t"
noExamples := complete
noExamples.Name, noExamples.Examples = "acme:noex", nil
undocumented := Command{
Name: "acme:bare",
Args: []Arg{{Name: "a"}, {Name: "b", Description: "B"}},
Flags: []Flag{{Name: "x"}, {Name: "y", Description: "Y"}, {Name: "z", Description: " "}},
}
got := CheckDocs([]Command{noExamples, complete, undocumented, blank})
want := []DocProblem{
{Command: "acme:bare", Missing: []string{"description", "long help", "examples", "argument a description", "option --x description", "option --z description"}},
{Command: "acme:blank", Missing: []string{"description", "long help"}},
{Command: "acme:noex", Missing: []string{"examples"}},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("CheckDocs =\n %v\nwant\n %v", got, want)
}
if s := got[1].String(); s != "acme:blank: missing description, long help" {
t.Errorf("String() = %q", s)
}
// The builtins list and help are documented.
if got := CheckDocs(builtinCommands()); len(got) != 0 {
t.Errorf("builtin commands reported: %v", got)
}
}

View File

@@ -0,0 +1,268 @@
package bonfire
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
var update = flag.Bool("update", false, "rewrite golden files")
// goldenVersion is the fixed version line of every golden.
var goldenVersion = Version{App: "v1.4.0", Framework: "v0.3.2"}
// plainEnv clears the environment variables that force or detect color, so
// goldens render the same on every machine: no escape codes unless --ansi.
func plainEnv(t *testing.T) {
t.Helper()
for _, k := range []string{"FORCE_COLOR", "CLICOLOR_FORCE", "CLICOLOR", "COLORTERM", "NO_COLOR"} {
t.Setenv(k, "")
_ = os.Unsetenv(k)
}
t.Setenv("TERM", "dumb")
}
// goldenTokenCommand is the admin:token:create command of the D-14
// preview: value, bare and repeatable flags with defaults, long help and
// two examples. The values are fake.
func goldenTokenCommand() Command {
return Command{
Name: "admin:token:create",
Description: "Create a personal API token for a backend admin",
Long: "Creates a token for the admin with the given login and prints it once.\nStore it in the client right away; it cannot be shown again.",
Args: []Arg{{Name: "login", Description: "The admin's login", Required: true}},
Flags: []Flag{
{Name: "scopes", Description: "Comma-separated scopes", Default: "*"},
{Name: "expires", Description: "Days until expiry", Default: "90"},
{Name: "json", Description: "Print the token as JSON", Bare: true},
{Name: "tag", Shorthand: "t", Description: "A tag for the token", Repeatable: true},
},
Examples: []Example{
{Command: "admin:token:create jane --scopes=journal.*", Description: "Token for the MCP server, journal only"},
{Command: "admin:token:create jane --json", Description: "Script-friendly output"},
},
Run: func(context.Context, Input, Output) error { return nil },
}
}
// goldenCommands is the command set of the list goldens (D-13 preview plus
// a hidden and a passthrough command).
func goldenCommands() []Command {
ok := func(context.Context, Input, Output) error { return nil }
return []Command{
{Name: "migrate", Description: "Run the database migrations", Run: ok},
{Name: "serve", Description: "Serve the application", Run: ok},
{Name: "admin:create", Description: "Create a backend administrator", Run: ok},
goldenTokenCommand(),
{Name: "make:command", Description: "Create a new console command", Run: ok},
{Name: "make:model", Description: "Create a new model and migration", Run: ok},
{Name: "acme:secret", Description: "Rotate the internal signing secret", Hidden: true, Run: ok},
goldenPassthroughCommand(),
}
}
// goldenPassthroughCommand forwards everything after its name.
func goldenPassthroughCommand() Command {
return Command{
Name: "tool:exec",
Description: "Run the bundled tool with its own arguments",
Long: "Everything after the command name reaches the tool unchanged, global flags included.",
Passthrough: true,
Args: []Arg{{Name: "args", Description: "The tool's arguments"}},
Examples: []Example{{Command: "tool:exec --version", Description: "The tool's own version"}},
Run: func(context.Context, Input, Output) error { return nil },
}
}
// runGolden executes args on a fresh root over commands and returns stdout
// and stderr.
func runGolden(t *testing.T, commands []Command, args ...string) (string, string, int) {
t.Helper()
var out, errW bytes.Buffer
root, err := NewRootIO("acme", commands, strings.NewReader(""), &out, &errW, WithVersion(goldenVersion))
if err != nil {
t.Fatal(err)
}
code := Execute(context.Background(), root, args)
return out.String(), errW.String(), code
}
// assertGolden compares got with testdata/<name>.golden, rewriting the file
// first when -update is set.
func assertGolden(t *testing.T, name string, got []byte) {
t.Helper()
path := filepath.Join("testdata", name+".golden")
if *update {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, got, 0o644); err != nil {
t.Fatal(err)
}
}
want, err := os.ReadFile(path)
if err != nil {
t.Fatalf("%s: %v (run go test -run %s -update to create it)", name, err, t.Name())
}
if !bytes.Equal(got, want) {
t.Errorf("%s differs from %s\n%s", name, path, firstDiff(want, got))
}
}
// firstDiff prints the first differing line of want and got with a little
// context, in a unified-diff-like form.
func firstDiff(want, got []byte) string {
wl := strings.Split(string(want), "\n")
gl := strings.Split(string(got), "\n")
for i := 0; i < max(len(wl), len(gl)); i++ {
var w, g string
if i < len(wl) {
w = wl[i]
}
if i < len(gl) {
g = gl[i]
}
if w == g {
continue
}
var b strings.Builder
for j := max(0, i-2); j < i; j++ {
fmt.Fprintf(&b, " %q\n", wl[j])
}
fmt.Fprintf(&b, "- %q\n+ %q\n", w, g)
return b.String()
}
return "(same lines, different line endings)"
}
// TestListGolden: the bare binary prints the D-13 list.
func TestListGolden(t *testing.T) {
plainEnv(t)
out, errOut, code := runGolden(t, goldenCommands())
if code != ExitOK || errOut != "" {
t.Fatalf("exit %d, stderr %q", code, errOut)
}
assertGolden(t, "list", []byte(out))
for _, args := range [][]string{{"list"}, {"--help"}, {"-h"}, {"help"}, {"-v"}, {"--no-ansi"}} {
again, _, code := runGolden(t, goldenCommands(), args...)
if code != ExitOK || again != out {
t.Errorf("%v: exit %d, output differs from the bare list:\n%s", args, code, firstDiff([]byte(out), []byte(again)))
}
}
}
// TestListAllGolden: list --all adds the hidden command.
func TestListAllGolden(t *testing.T) {
plainEnv(t)
out, _, code := runGolden(t, goldenCommands(), "list", "--all")
if code != ExitOK {
t.Fatalf("exit %d", code)
}
assertGolden(t, "list_all", []byte(out))
}
// TestListJSONGolden: list --format=json prints the catalog.
func TestListJSONGolden(t *testing.T) {
plainEnv(t)
out, _, code := runGolden(t, goldenCommands(), "list", "--format=json")
if code != ExitOK {
t.Fatalf("exit %d", code)
}
assertGolden(t, "list.json", []byte(out))
}
// TestListStyledGolden: --ansi styles the list in the palette (ANSI256),
// so a palette change shows up in review.
func TestListStyledGolden(t *testing.T) {
plainEnv(t)
out, _, code := runGolden(t, goldenCommands(), "--ansi")
if code != ExitOK {
t.Fatalf("exit %d", code)
}
if !strings.Contains(out, "\x1b[") {
t.Fatalf("--ansi list has no escape codes:\n%q", out)
}
assertGolden(t, "list_styled", []byte(out))
}
// TestHelpGolden: admin:token:create --help in the D-14 layout.
func TestHelpGolden(t *testing.T) {
plainEnv(t)
out, _, code := runGolden(t, goldenCommands(), "admin:token:create", "--help")
if code != ExitOK {
t.Fatalf("exit %d", code)
}
assertGolden(t, "help_token_create", []byte(out))
}
// TestHelpPassthroughGolden: the help of a passthrough command, reached
// through help <command> because --help after its name is forwarded.
func TestHelpPassthroughGolden(t *testing.T) {
plainEnv(t)
out, _, code := runGolden(t, goldenCommands(), "help", "tool:exec")
if code != ExitOK {
t.Fatalf("exit %d", code)
}
assertGolden(t, "help_passthrough", []byte(out))
}
// TestHelpEqualsHelpFlag: help <command>, <command> --help and <command>
// -h print the same bytes, with or without global flags.
func TestHelpEqualsHelpFlag(t *testing.T) {
plainEnv(t)
for _, name := range []string{"admin:token:create", "migrate", "list", "help"} {
want, _, code := runGolden(t, goldenCommands(), "help", name)
if code != ExitOK || !strings.Contains(want, "Usage:\n") {
t.Fatalf("help %s: exit %d\n%s", name, code, want)
}
for _, args := range [][]string{{name, "--help"}, {name, "-h"}, {"-v", "help", name}, {"--no-ansi", name, "--help"}} {
got, _, code := runGolden(t, goldenCommands(), args...)
if code != ExitOK || got != want {
t.Errorf("%v: exit %d, differs from help %s:\n%s", args, code, name, firstDiff([]byte(want), []byte(got)))
}
}
}
}
// TestErrorGolden: the error block of D-18 for a hint with {bin}, an
// unknown command with a suggestion and the -v chain, on stderr.
func TestErrorGolden(t *testing.T) {
plainEnv(t)
hinted := Command{Name: "make:plugin", Description: "Create a plugin", Run: func(context.Context, Input, Output) error {
return WithHint(errors.New("No application found in /home/jane/acme/plugins."),
"summer make:command needs an application root (summer.yaml).",
"cd ..; {bin} make:command acme.blog:publish")
}}
chained := Command{Name: "db:check", Description: "Check the database", Run: func(context.Context, Input, Output) error {
return fmt.Errorf("db:check: %w", fmt.Errorf("connect to 127.0.0.1:5432: %w", errors.New("connection refused")))
}}
commands := append(goldenCommands(), hinted, chained)
cases := []struct {
golden string
args []string
code int
}{
{"error_hint", []string{"make:plugin"}, ExitFailure},
{"error_unknown_command", []string{"migrat"}, ExitUsage},
{"error_chain_verbose", []string{"-v", "db:check"}, ExitFailure},
}
for _, tc := range cases {
t.Run(tc.golden, func(t *testing.T) {
out, errOut, code := runGolden(t, commands, tc.args...)
if code != tc.code {
t.Fatalf("exit %d, want %d", code, tc.code)
}
if out != "" {
t.Errorf("the error block reached stdout:\n%s", out)
}
assertGolden(t, tc.golden, []byte(errOut))
})
}
}

View File

@@ -0,0 +1,7 @@
ERROR db:check: connect to 127.0.0.1:5432: connection refused
Caused by:
- connect to 127.0.0.1:5432: connection refused
- connection refused

View File

@@ -0,0 +1,10 @@
ERROR No application found in /home/jane/acme/plugins.
summer make:command needs an application root (summer.yaml).
Try:
cd ..; bin/acme make:command acme.blog:publish
Run with -v for details.

View File

@@ -0,0 +1,8 @@
ERROR Command "migrat" is not defined.
Try:
bin/acme migrate
Run with -v for details.

View File

@@ -0,0 +1,23 @@
Description:
Run the bundled tool with its own arguments
Usage:
tool:exec [args] [options] [--] <args...>
Arguments:
args The tool's arguments
Global options:
-h, --help Display help for the given command
-q, --quiet Do not output any message
-v|vv|vvv Increase verbosity
--ansi|no-ansi Force (or disable) ANSI output
-n, --no-interaction Do not ask any interactive question
--env The environment the command runs under
Help:
Everything after the command name reaches the tool unchanged, global flags included.
Examples:
# The tool's own version
bin/acme tool:exec --version

View File

@@ -0,0 +1,33 @@
Description:
Create a personal API token for a backend admin
Usage:
admin:token:create <login> [options]
Arguments:
login The admin's login
Options:
--scopes=SCOPES Comma-separated scopes [default: "*"]
--expires=EXPIRES Days until expiry [default: 90]
--json Print the token as JSON
-t, --tag=TAG A tag for the token (multiple values allowed)
Global options:
-h, --help Display help for the given command
-q, --quiet Do not output any message
-v|vv|vvv Increase verbosity
--ansi|no-ansi Force (or disable) ANSI output
-n, --no-interaction Do not ask any interactive question
--env The environment the command runs under
Help:
Creates a token for the admin with the given login and prints it once.
Store it in the client right away; it cannot be shown again.
Examples:
# Token for the MCP server, journal only
bin/acme admin:token:create jane --scopes=journal.*
# Script-friendly output
bin/acme admin:token:create jane --json

26
modules/bonfire/testdata/list.golden vendored Normal file
View File

@@ -0,0 +1,26 @@
SummerCMS acme v1.4.0 (framework v0.3.2)
Usage:
bin/acme <command> [options] [arguments]
Options:
-h, --help Display help for the given command
-q, --quiet Do not output any message
-v|vv|vvv Increase verbosity
--ansi|no-ansi Force (or disable) ANSI output
-n, --no-interaction Do not ask any interactive question
--env The environment the command runs under
Available commands:
help Display help for a command
list List commands
migrate Run the database migrations
serve Serve the application
admin
admin:create Create a backend administrator
admin:token:create Create a personal API token for a backend admin
make
make:command Create a new console command
make:model Create a new model and migration
tool
tool:exec Run the bundled tool with its own arguments

View File

@@ -0,0 +1,273 @@
{
"application": {
"name": "acme",
"version": "v1.4.0",
"framework": "v0.3.2"
},
"global_options": [
{
"name": "help",
"shorthand": "h",
"description": "Display help for the given command",
"default": "",
"bare": true,
"repeatable": false
},
{
"name": "quiet",
"shorthand": "q",
"description": "Do not output any message",
"default": "",
"bare": true,
"repeatable": false
},
{
"name": "verbose",
"shorthand": "v",
"description": "Increase verbosity (-v, -vv or -vvv)",
"default": "",
"bare": true,
"repeatable": true
},
{
"name": "ansi",
"shorthand": "",
"description": "Force ANSI output",
"default": "",
"bare": true,
"repeatable": false
},
{
"name": "no-ansi",
"shorthand": "",
"description": "Disable ANSI output",
"default": "",
"bare": true,
"repeatable": false
},
{
"name": "no-interaction",
"shorthand": "n",
"description": "Do not ask any interactive question",
"default": "",
"bare": true,
"repeatable": false
},
{
"name": "env",
"shorthand": "",
"description": "The environment the command runs under",
"default": "",
"bare": false,
"repeatable": false
}
],
"commands": [
{
"name": "acme:secret",
"description": "Rotate the internal signing secret",
"long": "",
"usage": "acme:secret [options]",
"hidden": true,
"passthrough": false,
"arguments": [],
"options": [],
"examples": []
},
{
"name": "admin:create",
"description": "Create a backend administrator",
"long": "",
"usage": "admin:create [options]",
"hidden": false,
"passthrough": false,
"arguments": [],
"options": [],
"examples": []
},
{
"name": "admin:token:create",
"description": "Create a personal API token for a backend admin",
"long": "Creates a token for the admin with the given login and prints it once.\nStore it in the client right away; it cannot be shown again.",
"usage": "admin:token:create \u003clogin\u003e [options]",
"hidden": false,
"passthrough": false,
"arguments": [
{
"name": "login",
"description": "The admin's login",
"required": true
}
],
"options": [
{
"name": "scopes",
"shorthand": "",
"description": "Comma-separated scopes",
"default": "*",
"bare": false,
"repeatable": false
},
{
"name": "expires",
"shorthand": "",
"description": "Days until expiry",
"default": "90",
"bare": false,
"repeatable": false
},
{
"name": "json",
"shorthand": "",
"description": "Print the token as JSON",
"default": "",
"bare": true,
"repeatable": false
},
{
"name": "tag",
"shorthand": "t",
"description": "A tag for the token",
"default": "",
"bare": false,
"repeatable": true
}
],
"examples": [
{
"command": "admin:token:create jane --scopes=journal.*",
"description": "Token for the MCP server, journal only"
},
{
"command": "admin:token:create jane --json",
"description": "Script-friendly output"
}
]
},
{
"name": "help",
"description": "Display help for a command",
"long": "Prints the description, usage, arguments, options, long help and examples of a command. Without a command name it prints the command list.",
"usage": "help [command_name] [options]",
"hidden": false,
"passthrough": false,
"arguments": [
{
"name": "command_name",
"description": "The command name",
"required": false
}
],
"options": [],
"examples": [
{
"command": "help migrate",
"description": "Help for the migrate command"
}
]
},
{
"name": "list",
"description": "List commands",
"long": "Prints the commands grouped by namespace. --all includes hidden commands; --format=json prints every command with its arguments, options and examples.",
"usage": "list [options]",
"hidden": false,
"passthrough": false,
"arguments": [],
"options": [
{
"name": "all",
"shorthand": "",
"description": "Show hidden commands too",
"default": "",
"bare": true,
"repeatable": false
},
{
"name": "format",
"shorthand": "",
"description": "The output format (txt or json)",
"default": "txt",
"bare": false,
"repeatable": false
}
],
"examples": [
{
"command": "list --all",
"description": "Every command, hidden ones included"
},
{
"command": "list --format=json",
"description": "The catalog for scripts and agents"
}
]
},
{
"name": "make:command",
"description": "Create a new console command",
"long": "",
"usage": "make:command [options]",
"hidden": false,
"passthrough": false,
"arguments": [],
"options": [],
"examples": []
},
{
"name": "make:model",
"description": "Create a new model and migration",
"long": "",
"usage": "make:model [options]",
"hidden": false,
"passthrough": false,
"arguments": [],
"options": [],
"examples": []
},
{
"name": "migrate",
"description": "Run the database migrations",
"long": "",
"usage": "migrate [options]",
"hidden": false,
"passthrough": false,
"arguments": [],
"options": [],
"examples": []
},
{
"name": "serve",
"description": "Serve the application",
"long": "",
"usage": "serve [options]",
"hidden": false,
"passthrough": false,
"arguments": [],
"options": [],
"examples": []
},
{
"name": "tool:exec",
"description": "Run the bundled tool with its own arguments",
"long": "Everything after the command name reaches the tool unchanged, global flags included.",
"usage": "tool:exec [args] [options] [--] \u003cargs...\u003e",
"hidden": false,
"passthrough": true,
"arguments": [
{
"name": "args",
"description": "The tool's arguments",
"required": false
}
],
"options": [],
"examples": [
{
"command": "tool:exec --version",
"description": "The tool's own version"
}
]
}
]
}

View File

@@ -0,0 +1,28 @@
SummerCMS acme v1.4.0 (framework v0.3.2)
Usage:
bin/acme <command> [options] [arguments]
Options:
-h, --help Display help for the given command
-q, --quiet Do not output any message
-v|vv|vvv Increase verbosity
--ansi|no-ansi Force (or disable) ANSI output
-n, --no-interaction Do not ask any interactive question
--env The environment the command runs under
Available commands:
help Display help for a command
list List commands
migrate Run the database migrations
serve Serve the application
acme
acme:secret Rotate the internal signing secret
admin
admin:create Create a backend administrator
admin:token:create Create a personal API token for a backend admin
make
make:command Create a new console command
make:model Create a new model and migration
tool
tool:exec Run the bundled tool with its own arguments

View File

@@ -0,0 +1,26 @@
SummerCMS acme v1.4.0 (framework v0.3.2)
Usage:
bin/acme <command> [options] [arguments]
Options:
-h, --help Display help for the given command
-q, --quiet Do not output any message
-v|vv|vvv Increase verbosity
 --ansi|no-ansi Force (or disable) ANSI output
-n, --no-interaction Do not ask any interactive question
 --env The environment the command runs under
Available commands:
help Display help for a command
list List commands
migrate Run the database migrations
serve Serve the application
admin
admin:create Create a backend administrator
admin:token:create Create a personal API token for a backend admin
make
make:command Create a new console command
make:model Create a new model and migration
tool
tool:exec Run the bundled tool with its own arguments

View File

@@ -0,0 +1,75 @@
package bonfire
import (
"runtime/debug"
"testing"
)
// TestVersionHeader: the list header and BuildVersion for a tagged
// framework dependency, a replaced (local) one, a go.work build (devel)
// and the summer tool's own build, read through an injected build info.
func TestVersionHeader(t *testing.T) {
headers := []struct {
v Version
name string
want string
}{
{Version{App: "v1.4.0", Framework: "v0.3.2"}, "acme", "SummerCMS acme v1.4.0 (framework v0.3.2)"},
{Version{App: "devel", Framework: "v0.4.0", Local: true}, "acme", "SummerCMS acme devel (framework v0.4.0, local)"},
{Version{App: "v1.4.1-0.20261021120000-abcdef123456+dirty", Framework: "(devel)"}, "acme", "SummerCMS acme v1.4.1-0.20261021120000-abcdef123456+dirty (framework devel)"},
// Equal versions collapse to one, as for the summer tool's own build.
{Version{App: "(devel)", Framework: "(devel)"}, "acme", "SummerCMS acme devel"},
{Version{App: "v0.4.0", Framework: "v0.4.0"}, "summer", "SummerCMS summer v0.4.0"},
{Version{App: "v0.4.0", Framework: "v0.4.0", Local: true}, "acme", "SummerCMS acme v0.4.0 (framework v0.4.0, local)"},
{Version{}, "acme", "SummerCMS acme devel"},
}
for _, tc := range headers {
if got := tc.v.Header(tc.name); got != tc.want {
t.Errorf("%+v.Header(%q) = %q, want %q", tc.v, tc.name, got, tc.want)
}
}
dep := func(version string, replaced bool) *debug.Module {
m := &debug.Module{Path: frameworkModule, Version: version}
if replaced {
m.Replace = &debug.Module{Path: "../summercms.go", Version: "(devel)"}
}
return m
}
builds := []struct {
name string
info *debug.BuildInfo
ok bool
want Version
}{
{"tagged", &debug.BuildInfo{Main: debug.Module{Path: "example.com/acme", Version: "v1.4.0"},
Deps: []*debug.Module{{Path: "example.com/other", Version: "v9.9.9"}, dep("v0.3.2", false)}},
true, Version{App: "v1.4.0", Framework: "v0.3.2"}},
{"replaced", &debug.BuildInfo{Main: debug.Module{Path: "example.com/acme", Version: "(devel)"},
Deps: []*debug.Module{dep("v0.4.0", true)}},
true, Version{App: "devel", Framework: "v0.4.0", Local: true}},
{"go.work", &debug.BuildInfo{Main: debug.Module{Path: "example.com/acme", Version: "v1.4.1-0.20261021120000-abcdef123456+dirty"},
Deps: []*debug.Module{dep("(devel)", false)}},
true, Version{App: "v1.4.1-0.20261021120000-abcdef123456+dirty", Framework: "devel"}},
{"no framework dep", &debug.BuildInfo{Main: debug.Module{Path: "example.com/acme", Version: "v1.0.0"}},
true, Version{App: "v1.0.0", Framework: "devel"}},
{"summer", &debug.BuildInfo{Main: debug.Module{Path: frameworkModule, Version: "v0.4.0"}},
true, Version{App: "v0.4.0", Framework: "v0.4.0"}},
{"no build info", nil, false, Version{App: "devel", Framework: "devel"}},
}
for _, tc := range builds {
t.Run(tc.name, func(t *testing.T) {
prev := readBuildInfo
t.Cleanup(func() { readBuildInfo = prev })
readBuildInfo = func() (*debug.BuildInfo, bool) { return tc.info, tc.ok }
if got := BuildVersion(); got != tc.want {
t.Errorf("BuildVersion() = %+v, want %+v", got, tc.want)
}
})
}
// The test binary is built from the framework module itself.
if got := BuildVersion(); got.App != got.Framework || got.Local {
t.Errorf("the framework's own build = %+v, want App == Framework", got)
}
}