fix(cmd): long-form flags, honest counts and safer inference
Assisted-by: GLM 5.3
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -217,7 +219,7 @@ func TestTaggedHelper(t *testing.T) {
|
||||
func TestValidateStdinAcceptsValidDocument(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
in := bytes.NewReader([]byte("title = \"ok\"\n"))
|
||||
if code := Run([]string{"-validate"}, in, &stdout, &stderr); code != 0 {
|
||||
if code := Run([]string{"--validate"}, in, &stdout, &stderr); code != 0 {
|
||||
t.Fatalf("Run returned %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
if stdout.Len() != 0 || stderr.Len() != 0 {
|
||||
@@ -228,7 +230,7 @@ func TestValidateStdinAcceptsValidDocument(t *testing.T) {
|
||||
func TestValidateStdinRejectsInvalidDocument(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
in := bytes.NewReader([]byte("title = \"unterminated\n"))
|
||||
if code := Run([]string{"-validate"}, in, &stdout, &stderr); code != 1 {
|
||||
if code := Run([]string{"--validate"}, in, &stdout, &stderr); code != 1 {
|
||||
t.Fatalf("Run returned %d, want 1; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "<stdin>") || !strings.Contains(stderr.String(), "line 1") {
|
||||
@@ -250,10 +252,10 @@ func TestValidateFiles(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
if code := Run([]string{"-validate", good}, nil, &stdout, &stderr); code != 0 {
|
||||
if code := Run([]string{"--validate", good}, nil, &stdout, &stderr); code != 0 {
|
||||
t.Fatalf("one valid file: Run returned %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
if code := Run([]string{"-validate", good, bad}, nil, &stdout, &stderr); code != 1 {
|
||||
if code := Run([]string{"--validate", good, bad}, nil, &stdout, &stderr); code != 1 {
|
||||
t.Fatalf("valid plus invalid: Run returned %d, want 1; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), bad) || !strings.Contains(stderr.String(), "line 1") {
|
||||
@@ -263,7 +265,7 @@ func TestValidateFiles(t *testing.T) {
|
||||
|
||||
func TestValidateMissingFileReturnsTwo(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
if code := Run([]string{"-validate", "no-such-file.toml"}, nil, &stdout, &stderr); code != 2 {
|
||||
if code := Run([]string{"--validate", "no-such-file.toml"}, nil, &stdout, &stderr); code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2; stderr = %q", code, stderr.String())
|
||||
}
|
||||
}
|
||||
@@ -274,14 +276,14 @@ func TestAdapterModeRejectsPositionalArgument(t *testing.T) {
|
||||
if code := Run([]string{"file.toml"}, in, &stdout, &stderr); code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "-validate") {
|
||||
t.Fatalf("stderr = %q, want it to point at -validate", stderr.String())
|
||||
if !strings.Contains(stderr.String(), "--validate") {
|
||||
t.Fatalf("stderr = %q, want it to point at --validate", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownFlagReturnsTwo(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
if code := Run([]string{"-nope"}, nil, &stdout, &stderr); code != 2 {
|
||||
if code := Run([]string{"--nope"}, nil, &stdout, &stderr); code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2; stderr = %q", code, stderr.String())
|
||||
}
|
||||
}
|
||||
@@ -303,7 +305,7 @@ func TestRunEncoderScalars(t *testing.T) {
|
||||
}
|
||||
`
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"-encode"}, strings.NewReader(in), &stdout, &stderr)
|
||||
code := Run([]string{"--encode"}, strings.NewReader(in), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -334,7 +336,7 @@ func TestRunEncoderNested(t *testing.T) {
|
||||
}
|
||||
`
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"-encode"}, strings.NewReader(in), &stdout, &stderr)
|
||||
code := Run([]string{"--encode"}, strings.NewReader(in), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -355,7 +357,7 @@ func TestRunEncoderFloatTagDecides(t *testing.T) {
|
||||
// tag decides the type; the output must stay a float.
|
||||
var stdout, stderr bytes.Buffer
|
||||
in := `{"whole": {"type": "float", "value": "1"}, "exp": {"type": "float", "value": "5e+22"}}`
|
||||
code := Run([]string{"-encode"}, strings.NewReader(in), &stdout, &stderr)
|
||||
code := Run([]string{"--encode"}, strings.NewReader(in), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -380,7 +382,7 @@ func TestRunEncoderRejectsBadInput(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"-encode"}, strings.NewReader(c.in), &stdout, &stderr)
|
||||
code := Run([]string{"--encode"}, strings.NewReader(c.in), &stdout, &stderr)
|
||||
if code != 2 {
|
||||
t.Errorf("%s: Run returned %d, want 2; stderr = %q", c.name, code, stderr.String())
|
||||
continue
|
||||
@@ -396,7 +398,7 @@ func TestRunEncoderRejectsBadInput(t *testing.T) {
|
||||
|
||||
func TestRunEncoderFlagConflicts(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
if code := Run([]string{"-encode", "-validate"}, strings.NewReader(""), &stdout, &stderr); code != 2 {
|
||||
if code := Run([]string{"--encode", "--validate"}, strings.NewReader(""), &stdout, &stderr); code != 2 {
|
||||
t.Errorf("Run returned %d, want 2 for the two modes together", code)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "cannot be combined") {
|
||||
@@ -405,7 +407,7 @@ func TestRunEncoderFlagConflicts(t *testing.T) {
|
||||
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
if code := Run([]string{"-encode", "file.json"}, strings.NewReader(""), &stdout, &stderr); code != 2 {
|
||||
if code := Run([]string{"--encode", "file.json"}, strings.NewReader(""), &stdout, &stderr); code != 2 {
|
||||
t.Errorf("Run returned %d, want 2 for an argument", code)
|
||||
}
|
||||
}
|
||||
@@ -432,7 +434,7 @@ n = "a"
|
||||
t.Fatalf("decode returned %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
var out bytes.Buffer
|
||||
if code := Run([]string{"-encode"}, bytes.NewReader(tagged.Bytes()), &out, &stderr); code != 0 {
|
||||
if code := Run([]string{"--encode"}, bytes.NewReader(tagged.Bytes()), &out, &stderr); code != 0 {
|
||||
t.Fatalf("encode returned %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
want, err := interpres.ParseMap([]byte(doc))
|
||||
@@ -450,7 +452,7 @@ n = "a"
|
||||
|
||||
func TestRunVersion(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"-version"}, strings.NewReader(""), &stdout, &stderr)
|
||||
code := Run([]string{"--version"}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -463,7 +465,7 @@ func TestRunVersion(t *testing.T) {
|
||||
func TestRunPlainJSON(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
in := strings.NewReader("host = \"db\"\nwhen = 1979-05-27T07:32:00-07:00\nitems = [1, 2]\n")
|
||||
code := Run([]string{"-json"}, in, &stdout, &stderr)
|
||||
code := Run([]string{"--json"}, in, &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -481,18 +483,31 @@ func TestRunPlainJSON(t *testing.T) {
|
||||
|
||||
func TestValidateDirectorySummary(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.WriteFile(filepath.Join(dir, "good.toml"), []byte("a = 1\n"), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "bad.toml"), []byte("a =\n"), 0o644)
|
||||
if err := os.WriteFile(filepath.Join(dir, "good.toml"), []byte("a = 1\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "bad.toml"), []byte("a =\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sub := filepath.Join(dir, "nested")
|
||||
os.Mkdir(sub, 0o755)
|
||||
os.WriteFile(filepath.Join(sub, "deep.toml"), []byte("b = true\n"), 0o644)
|
||||
if err := os.Mkdir(sub, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(sub, "deep.toml"), []byte("b = true\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A second invalid document, so the summary's invalid count is
|
||||
// exercised beyond the single failure the boolean tracked.
|
||||
if err := os.WriteFile(filepath.Join(sub, "worse.toml"), []byte("c =\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"-validate", dir}, strings.NewReader(""), &stdout, &stderr)
|
||||
code := Run([]string{"--validate", dir}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 1 {
|
||||
t.Fatalf("Run returned %d, want 1 for a directory with an invalid file", code)
|
||||
t.Fatalf("Run returned %d, want 1 for a directory with invalid files", code)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "checked 3 documents, 1 invalid") {
|
||||
if !strings.Contains(stderr.String(), "checked 4 documents, 2 invalid") {
|
||||
t.Errorf("stderr = %q, want the summary", stderr.String())
|
||||
}
|
||||
}
|
||||
@@ -500,7 +515,7 @@ func TestValidateDirectorySummary(t *testing.T) {
|
||||
func TestInferStruct(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
in := strings.NewReader("host = \"db\"\nport = 5432\ntags = [\"a\"]\n\n[server]\nname = \"edge\"\n\n[[items]]\nn = 1\n")
|
||||
code := Run([]string{"-struct"}, in, &stdout, &stderr)
|
||||
code := Run([]string{"--struct"}, in, &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -545,7 +560,7 @@ type Item struct {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"-schema", "Config", src}, strings.NewReader(""), &stdout, &stderr)
|
||||
code := Run([]string{"--schema", "Config", src}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -572,7 +587,7 @@ func TestRunPlainJSONShapes(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
in := strings.NewReader("when = 1979-05-27T07:32:00-07:00\nd = 1979-05-27\nt = 07:32:00\nwall = 1979-05-27T07:32:00\n" +
|
||||
"items = [1, \"two\"]\n\n[[tables]]\nx = true\n")
|
||||
code := Run([]string{"-json"}, in, &stdout, &stderr)
|
||||
code := Run([]string{"--json"}, in, &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -594,7 +609,7 @@ func TestRunPlainJSONShapes(t *testing.T) {
|
||||
func TestInferStructScalarShapes(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
in := strings.NewReader("f = 1.5\nb = true\nd = 1979-05-27\nldt = 1979-05-27T07:32:00\nlt = 07:32:00\nnums = [1, 2, 3]\nmixed = [1, \"a\"]\nempty = []\n")
|
||||
code := Run([]string{"-struct"}, in, &stdout, &stderr)
|
||||
code := Run([]string{"--struct"}, in, &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
@@ -614,3 +629,372 @@ func TestInferStructScalarShapes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunHelpPrintsLongFlags(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--help"}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
out := stdout.String()
|
||||
for _, flag := range []string{"--encode", "--help", "--json", "--schema", "--struct", "--validate", "--version"} {
|
||||
if !strings.Contains(out, flag) {
|
||||
t.Errorf("usage output missing %q:\n%s", flag, out)
|
||||
}
|
||||
}
|
||||
// Every flag line of the list names its flag in the two-dash long form
|
||||
// only, so no line opens with a single dash.
|
||||
for line := range strings.SplitSeq(strings.TrimRight(out, "\n"), "\n") {
|
||||
if after, ok := strings.CutPrefix(line, " -"); ok && !strings.HasPrefix(after, "-") {
|
||||
t.Errorf("usage line %q lists a flag with one dash", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoFieldName(t *testing.T) {
|
||||
cases := []struct{ key, want string }{
|
||||
{"host", "Host"},
|
||||
{"ab", "Ab"},
|
||||
{"http-host", "HttpHost"},
|
||||
{"3d", "Field3d"},
|
||||
{"", "Field"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := goFieldName(c.key, map[string]bool{}); got != c.want {
|
||||
t.Errorf("goFieldName(%q) = %q, want %q", c.key, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoFieldNameCollision(t *testing.T) {
|
||||
// Two keys that clean to the same name must not collide; the counter
|
||||
// keeps the fields apart and both stay exported.
|
||||
invented := map[string]bool{}
|
||||
cases := []struct{ key, want string }{
|
||||
{"a-b", "AB"},
|
||||
{"a b", "AB2"},
|
||||
{"a_b", "AB22"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := goFieldName(c.key, invented); got != c.want {
|
||||
t.Errorf("goFieldName(%q) = %q, want %q", c.key, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferStructDigitLeadingKey(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--struct"}, strings.NewReader("3d = true\n"), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "Field3d bool") {
|
||||
t.Errorf("output missing the exported Field3d field:\n%s", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferStructBacktickKey(t *testing.T) {
|
||||
// A backtick in the key would end a raw string literal early, so the
|
||||
// tag has to be rendered as an interpreted literal instead.
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--struct"}, strings.NewReader("\"a`b\" = 1\n"), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
out := stdout.String()
|
||||
if !strings.Contains(out, "AB int64 \"toml:\\\"a`b\\\"\"") {
|
||||
t.Errorf("output missing the quoted tag:\n%s", out)
|
||||
}
|
||||
// The printed definition has to compile; parsing it as Go is the
|
||||
// syntax half of that proof.
|
||||
if _, err := parser.ParseFile(token.NewFileSet(), "inferred.go", "package p\n\n"+out, 0); err != nil {
|
||||
t.Errorf("the printed definition does not parse: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferStructMergesArrayElements(t *testing.T) {
|
||||
// The second element carries a key the first lacks, so the slice type
|
||||
// has to be inferred from both.
|
||||
var stdout, stderr bytes.Buffer
|
||||
in := strings.NewReader("[[items]]\nn = 1\n\n[[items]]\nextra = \"late\"\n")
|
||||
code := Run([]string{"--struct"}, in, &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
out := stdout.String()
|
||||
for _, want := range []string{
|
||||
"Items []struct {",
|
||||
"N int64 `toml:\"n\"`",
|
||||
"Extra string `toml:\"extra\"`",
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("output missing %q:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferStructMixedArrayStaysValueArray(t *testing.T) {
|
||||
// One table element does not make the array an array of tables; a
|
||||
// struct slice would not decode the scalar element.
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--struct"}, strings.NewReader("arr = [1, {x = 1}]\n"), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
out := stdout.String()
|
||||
if !strings.Contains(out, "Arr []any") {
|
||||
t.Errorf("output = %q, want a value array typed []any", out)
|
||||
}
|
||||
if strings.Contains(out, "[]struct") {
|
||||
t.Errorf("output = %q, a mixed array must not become a struct slice", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunStructParseError(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--struct"}, strings.NewReader("a =\n"), &stdout, &stderr)
|
||||
if code != 1 {
|
||||
t.Fatalf("Run returned %d, want 1 (parse error); stderr = %q", code, stderr.String())
|
||||
}
|
||||
if stdout.Len() != 0 {
|
||||
t.Errorf("stdout should be empty on parse error, got %q", stdout.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "line 1") {
|
||||
t.Errorf("stderr = %q, want the library's line number", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunStructWriteFailure(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
code := Run([]string{"--struct"}, strings.NewReader("a = 1\n"), errorWriter{}, &stderr)
|
||||
if code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2 (write error); stderr = %q", code, stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSchemaNeedsTypeAndExactlyOneFile(t *testing.T) {
|
||||
for _, args := range [][]string{{"--schema", "Config"}, {"--schema", "Config", "a.go", "b.go"}} {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run(args, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 2 {
|
||||
t.Errorf("Run(%v) returned %d, want 2", args, code)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "--schema") {
|
||||
t.Errorf("Run(%v) stderr = %q, want it to name --schema", args, stderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSchemaUnparsableSource(t *testing.T) {
|
||||
src := filepath.Join(t.TempDir(), "broken.go")
|
||||
if err := os.WriteFile(src, []byte("this is not Go\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--schema", "Config", src}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "broken.go") {
|
||||
t.Errorf("stderr = %q, want it to name the source file", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSchemaUnknownType(t *testing.T) {
|
||||
src := filepath.Join(t.TempDir(), "config.go")
|
||||
body := "package cfg\n\ntype Config struct {\n\tA int `toml:\"a\"`\n}\n"
|
||||
if err := os.WriteFile(src, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--schema", "Missing", src}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), `no struct type "Missing"`) {
|
||||
t.Errorf("stderr = %q, want it to name the missing type", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSchemaRecursiveType(t *testing.T) {
|
||||
// A self-referential struct has no finite template; the generator has
|
||||
// to name the recursion instead of exhausting the stack.
|
||||
src := filepath.Join(t.TempDir(), "node.go")
|
||||
body := "package cfg\n\ntype Node struct {\n\tNext *Node `toml:\"next\"`\n}\n"
|
||||
if err := os.WriteFile(src, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--schema", "Node", src}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2; stderr = %q", code, stderr.String())
|
||||
}
|
||||
for _, want := range []string{"recursive", "Node"} {
|
||||
if !strings.Contains(stderr.String(), want) {
|
||||
t.Errorf("stderr = %q, want it to mention %q", stderr.String(), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSchemaMultiNameField(t *testing.T) {
|
||||
// A field list may name several fields of one type; each name is one
|
||||
// TOML key.
|
||||
src := filepath.Join(t.TempDir(), "range.go")
|
||||
if err := os.WriteFile(src, []byte("package cfg\n\ntype Range struct {\n\tMin, Max int\n}\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--schema", "Range", src}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
out := stdout.String()
|
||||
for _, want := range []string{"min = 0", "max = 0"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("output missing %q:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSchemaEmbeddedStructs(t *testing.T) {
|
||||
// The library inlines only untagged embedded structs; a tagged one
|
||||
// keeps its own section.
|
||||
src := filepath.Join(t.TempDir(), "embed.go")
|
||||
body := `package cfg
|
||||
|
||||
type Inner struct {
|
||||
X int ` + "`toml:\"x\"`" + `
|
||||
}
|
||||
|
||||
type Tagged struct {
|
||||
Inner ` + "`toml:\"inner\"`" + `
|
||||
Y int ` + "`toml:\"y\"`" + `
|
||||
}
|
||||
|
||||
type Flat struct {
|
||||
Inner
|
||||
Z int ` + "`toml:\"z\"`" + `
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(src, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--schema", "Tagged", src}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Tagged: Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
out := stdout.String()
|
||||
if !strings.Contains(out, "y = 0") || !strings.Contains(out, "[inner]") {
|
||||
t.Errorf("Tagged output = %q, want a y scalar and an [inner] section", out)
|
||||
}
|
||||
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
code = Run([]string{"--schema", "Flat", src}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Flat: Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
out = stdout.String()
|
||||
if !strings.Contains(out, "x = 0") || !strings.Contains(out, "z = 0") {
|
||||
t.Errorf("Flat output = %q, want x and z flattened as scalars", out)
|
||||
}
|
||||
if strings.Contains(out, "[inner]") {
|
||||
t.Errorf("Flat output = %q, an untagged embedded struct must not become a section", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunVersionWriteFailure(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
code := Run([]string{"--version"}, strings.NewReader(""), errorWriter{}, &stderr)
|
||||
if code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2 (write error); stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "write stdout") {
|
||||
t.Errorf("stderr = %q, want it to mention the failed write", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSchemaWriteFailure(t *testing.T) {
|
||||
src := filepath.Join(t.TempDir(), "config.go")
|
||||
body := "package cfg\n\ntype Config struct {\n\tA int `toml:\"a\"`\n}\n"
|
||||
if err := os.WriteFile(src, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stderr bytes.Buffer
|
||||
code := Run([]string{"--schema", "Config", src}, strings.NewReader(""), errorWriter{}, &stderr)
|
||||
if code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2 (write error); stderr = %q", code, stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunEncodeWriteFailure(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
in := `{"a": {"type": "integer", "value": "1"}}`
|
||||
code := Run([]string{"--encode"}, strings.NewReader(in), errorWriter{}, &stderr)
|
||||
if code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2 (write error); stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "write stdout") {
|
||||
t.Errorf("stderr = %q, want it to mention the failed write", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunEmptyInput(t *testing.T) {
|
||||
t.Run("default", func(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run(nil, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if strings.TrimSpace(stdout.String()) != "{}" {
|
||||
t.Errorf("stdout = %q, want an empty table", stdout.String())
|
||||
}
|
||||
})
|
||||
t.Run("encode", func(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--encode"}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 2 {
|
||||
t.Fatalf("Run returned %d, want 2, empty input is not JSON", code)
|
||||
}
|
||||
})
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--struct"}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "type inferred struct {\n}") {
|
||||
t.Errorf("stdout = %q, want an empty struct", stdout.String())
|
||||
}
|
||||
})
|
||||
t.Run("validate", func(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run([]string{"--validate"}, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("Run returned %d, want 0; stderr = %q", code, stderr.String())
|
||||
}
|
||||
if stdout.Len() != 0 || stderr.Len() != 0 {
|
||||
t.Errorf("validate should be quiet, stdout %q stderr %q", stdout.String(), stderr.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunJSONFlagConflicts(t *testing.T) {
|
||||
for _, args := range [][]string{
|
||||
{"--encode", "--json"},
|
||||
{"--struct", "--json"},
|
||||
{"--validate", "--json"},
|
||||
{"--json", "--schema", "Config", "config.go"},
|
||||
} {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Run(args, strings.NewReader(""), &stdout, &stderr)
|
||||
if code != 2 {
|
||||
t.Errorf("Run(%v) returned %d, want 2", args, code)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "--json") {
|
||||
t.Errorf("Run(%v) stderr = %q, want it to explain the --json conflict", args, stderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user