diff --git a/cmd/fejkdata/main_test.go b/cmd/fejkdata/main_test.go index 1924d31..ec58949 100644 --- a/cmd/fejkdata/main_test.go +++ b/cmd/fejkdata/main_test.go @@ -17,14 +17,21 @@ const ( misc = "../../data/misc" ) -func runOut(args ...string) (int, string, string) { +func runOut(stdin string, args ...string) (int, string, string) { var out, errb bytes.Buffer - code := run(args, &out, &errb) + code := run(args, strings.NewReader(stdin), &out, &errb) + return code, out.String(), errb.String() +} + +// runTerminal runs as from a shell with nothing piped in. +func runTerminal(args ...string) (int, string, string) { + var out, errb bytes.Buffer + code := run(args, nil, &out, &errb) return code, out.String(), errb.String() } func TestRunOutputsValue(t *testing.T) { - code, out, errb := runOut("--data-path", svSE, "person") + code, out, errb := runOut("{/person}", "--data-path", svSE) if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -37,11 +44,11 @@ func TestRunOutputsValue(t *testing.T) { } func TestRunDotPath(t *testing.T) { - code, full, _ := runOut("--seed", "7", "--data-path", svSE, "person") + code, full, _ := runOut("{/person}", "--seed", "7", "--data-path", svSE) if code != 0 { t.Fatalf("person run = %d", code) } - code, last, errb := runOut("--seed", "7", "--data-path", svSE, "person.last") + code, last, errb := runOut("{/person.last}", "--seed", "7", "--data-path", svSE) if code != 0 { t.Fatalf("person.last run = %d, stderr=%q", code, errb) } @@ -54,13 +61,13 @@ func TestRunDotPath(t *testing.T) { } func TestRunSeedSpellings(t *testing.T) { - _, want, _ := runOut("--seed", "42", "--data-path", svSE, "address") + _, want, _ := runOut("{/address}", "--seed", "42", "--data-path", svSE) for _, args := range [][]string{ - {"--seed=42", "--data-path", svSE, "address"}, - {"-s", "42", "-d", svSE, "address"}, - {"--data-path", svSE, "address", "--seed", "42"}, + {"--seed=42", "--data-path", svSE}, + {"-s", "42", "-d", svSE}, + {"--data-path", svSE, "--seed", "42"}, } { - code, got, errb := runOut(args...) + code, got, errb := runOut("{/address}", args...) if code != 0 { t.Fatalf("run(%v) = %d, stderr=%q", args, code, errb) } @@ -71,13 +78,13 @@ func TestRunSeedSpellings(t *testing.T) { } func TestRunShortFlagValues(t *testing.T) { - _, want, _ := runOut("--seed", "42", "--data-path", svSE, "address") + _, want, _ := runOut("{/address}", "--seed", "42", "--data-path", svSE) for _, args := range [][]string{ - {"-s42", "-d", svSE, "address"}, - {"-s", "42", "-d" + svSE, "address"}, - {"-d", svSE, "address", "-s42"}, + {"-s42", "-d", svSE}, + {"-s", "42", "-d" + svSE}, + {"-d", svSE, "-s42"}, } { - code, got, errb := runOut(args...) + code, got, errb := runOut("{/address}", args...) if code != 0 { t.Fatalf("run(%v) = %d, stderr=%q", args, code, errb) } @@ -85,7 +92,7 @@ func TestRunShortFlagValues(t *testing.T) { t.Errorf("run(%v) = %q, want %q", args, got, want) } } - _, three, _ := runOut("-s", "1", "-n3", "-d", svSE, "word") + _, three, _ := runOut("{/word}", "-s", "1", "-n3", "-d", svSE) if lines := strings.Split(strings.TrimRight(three, "\n"), "\n"); len(lines) != 3 { t.Errorf("-n3 gave %d lines: %q", len(lines), three) } @@ -93,13 +100,13 @@ func TestRunShortFlagValues(t *testing.T) { args []string want string }{ - {[]string{"-s=42", "-d", svSE, "address"}, "-s42"}, - {[]string{"-s=42", "-d", svSE, "address"}, "--seed=42"}, - {[]string{"-d=" + svSE, "address"}, "--data-path="}, - {[]string{"-nd", "3", "-d", svSE, "word"}, `--repeat needs an integer in 1..1048576, got "d"`}, - {[]string{"--seed=", "-d", svSE, "word"}, `--seed needs an unsigned integer, got ""`}, + {[]string{"-s=42", "-d", svSE}, "-s42"}, + {[]string{"-s=42", "-d", svSE}, "--seed=42"}, + {[]string{"-d=" + svSE}, "--data-path="}, + {[]string{"-nd", "3", "-d", svSE}, `--repeat needs an integer in 1..1048576, got "d"`}, + {[]string{"--seed=", "-d", svSE}, `--seed needs an unsigned integer, got ""`}, } { - code, out, errb := runOut(c.args...) + code, out, errb := runOut("{/word}", c.args...) if code != 2 || out != "" { t.Errorf("run(%v) = %d, stdout %q, want misuse", c.args, code, out) } @@ -107,20 +114,20 @@ func TestRunShortFlagValues(t *testing.T) { t.Errorf("run(%v) stderr = %q, want %q", c.args, errb, c.want) } } - code, out, _ := runOut("-hd", svSE) + code, out, _ := runOut("", "-hd", svSE) if code != 0 || !strings.Contains(out, "Usage") { t.Errorf("-hd (bundled help) = %d, %q, want usage", code, out) } } func TestRunDoubleDashEndsFlags(t *testing.T) { - code, out, errb := runOut("--data-path", svSE, "--", "person") + code, out, errb := runOut("{/person}", "--data-path", svSE, "--") if code != 0 || strings.TrimSpace(out) == "" { t.Fatalf("run = %d, out=%q, stderr=%q", code, out, errb) } - code, _, errb = runOut("--data-path", svSE, "--", "--list") - if code != 1 || !strings.Contains(errb, "--list") { - t.Errorf("after --, --list should be a path: code %d, stderr %q", code, errb) + code, _, errb = runOut("{/person}", "--data-path", svSE, "--", "--list") + if code != 2 || !strings.Contains(errb, "stdin") { + t.Errorf("after --, --list should be an argument: code %d, stderr %q", code, errb) } } @@ -129,14 +136,14 @@ func TestRunSingleDashLongIsRejected(t *testing.T) { args []string want string }{ - {[]string{"-seed", "42", "-d", svSE, "person"}, "use --seed"}, - {[]string{"-seed=42", "-d", svSE, "person"}, "use --seed"}, - {[]string{"-data-path", svSE, "person"}, "use --data-path"}, + {[]string{"-seed", "42", "-d", svSE}, "use --seed"}, + {[]string{"-seed=42", "-d", svSE}, "use --seed"}, + {[]string{"-data-path", svSE}, "use --data-path"}, {[]string{"-list", "-d", svSE}, "use --list"}, - {[]string{"--nope", "-d", svSE, "person"}, "unknown flag --nope"}, - {[]string{"-x", "-d", svSE, "person"}, "unknown flag -x"}, + {[]string{"--nope", "-d", svSE}, "unknown flag --nope"}, + {[]string{"-x", "-d", svSE}, "unknown flag -x"}, } { - code, _, errb := runOut(c.args...) + code, _, errb := runOut("{/person}", c.args...) if code != 2 { t.Errorf("run(%v) = %d, want 2", c.args, code) } @@ -151,14 +158,14 @@ func TestRunFlagValues(t *testing.T) { args []string want string }{ - {[]string{"--seed", "-d", svSE, "person"}, `--seed needs an unsigned integer, got "-d"`}, - {[]string{"-d", svSE, "person", "--seed"}, "--seed needs a value"}, - {[]string{"--seed", "x", "-d", svSE, "person"}, "--seed"}, + {[]string{"--seed", "-d", svSE}, `--seed needs an unsigned integer, got "-d"`}, + {[]string{"-d", svSE, "--seed"}, "--seed needs a value"}, + {[]string{"--seed", "x", "-d", svSE}, "--seed"}, {[]string{"--list=1", "-d", svSE}, "--list takes no value"}, - {[]string{"--repeat", "0", "-d", svSE, "person"}, "--repeat"}, - {[]string{"-n", "-1", "-d", svSE, "person"}, "--repeat"}, + {[]string{"--repeat", "0", "-d", svSE}, "--repeat"}, + {[]string{"-n", "-1", "-d", svSE}, "--repeat"}, } { - code, _, errb := runOut(c.args...) + code, _, errb := runOut("{/person}", c.args...) if code != 2 { t.Errorf("run(%v) = %d, want 2", c.args, code) } @@ -169,7 +176,7 @@ func TestRunFlagValues(t *testing.T) { } func TestRunRepeat(t *testing.T) { - code, out, errb := runOut("--seed", "1", "--repeat", "3", "--data-path", svSE, "word") + code, out, errb := runOut("{/word}", "--seed", "1", "--repeat", "3", "--data-path", svSE) if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -177,14 +184,14 @@ func TestRunRepeat(t *testing.T) { if len(lines) != 3 { t.Errorf("repeat=3 gave %d lines: %q", len(lines), out) } - _, short, _ := runOut("--seed", "1", "-n", "3", "-d", svSE, "word") + _, short, _ := runOut("{/word}", "--seed", "1", "-n", "3", "-d", svSE) if short != out { t.Errorf("-n 3 = %q, want the same as --repeat 3 %q", short, out) } } func TestRunSeparator(t *testing.T) { - code, out, errb := runOut("--repeat", "3", "--separator", ",", "--data-path", svSE, "word") + code, out, errb := runOut("{/word}", "--repeat", "3", "--separator", ",", "--data-path", svSE) if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -197,7 +204,7 @@ func TestRunSeparator(t *testing.T) { } func TestRunRepeatAdvancesRNG(t *testing.T) { - code, out, errb := runOut("--seed", "1", "--repeat", "5", "--data-path", svSE, "person") + code, out, errb := runOut("{/person}", "--seed", "1", "--repeat", "5", "--data-path", svSE) if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -211,30 +218,102 @@ func TestRunRepeatAdvancesRNG(t *testing.T) { } func TestRunMisuse(t *testing.T) { - for _, args := range [][]string{{}, {"--data-path", svSE}, {"-d", svSE, "person", "word"}, {"-d", svSE, "--list", "person"}, {"--no-shipped-data", "sv_SE.person"}} { - code, out, errb := runOut(args...) + for _, c := range []struct { + stdin string + args []string + }{ + {"", nil}, + {"", []string{"--data-path", svSE}}, + {"\n", []string{"--data-path", svSE}}, + {"{/person}", []string{"-d", svSE, "word"}}, + {"", []string{"-d", svSE, "--list", "person"}}, + {"{/sv_SE.person}", []string{"--no-shipped-data"}}, + } { + code, out, errb := runOut(c.stdin, c.args...) if code != 2 { - t.Errorf("run(%v) = %d, want 2", args, code) + t.Errorf("run(%q, %v) = %d, want 2", c.stdin, c.args, code) } if !strings.Contains(errb, "try 'fejkdata --help'") { - t.Errorf("run(%v) stderr = %q, want a pointer to --help", args, errb) + t.Errorf("run(%q, %v) stderr = %q, want a pointer to --help", c.stdin, c.args, errb) } if out != "" { - t.Errorf("run(%v) stdout = %q, want nothing", args, out) + t.Errorf("run(%q, %v) stdout = %q, want nothing", c.stdin, c.args, out) } } - // A template a shell split names both quotings, since which one cures it turns - // on whether the argument carries a double-quoted JSON. - _, _, errb := runOut("{date(1990-01-01,2010-12-31,", "January", "2,", "2006)}") - for _, want := range []string{`wrap the whole argument in "…"`, `or in '…'`} { - if !strings.Contains(errb, want) { - t.Errorf("a split template stderr = %q, want it to name %s", errb, want) +} + +// TestRunTakesNoArgument pins the way from the old spelling, a template or a path as +// an argument, to the one that runs: the template on stdin. +func TestRunTakesNoArgument(t *testing.T) { + for _, args := range [][]string{{"sv_SE.person"}, {"{/sv_SE.person}"}, {"name:", "{/sv_SE.person.last}"}} { + code, out, errb := runOut("", args...) + if code != 2 || out != "" || !strings.Contains(errb, "stdin") || !strings.Contains(errb, "| fejkdata") { + t.Errorf("run(%v) = %d, %q, %q; want misuse showing a template piped to fejkdata", args, code, out, errb) + } + } +} + +func TestRunTerminalStdinIsMisuse(t *testing.T) { + code, out, errb := runTerminal("--seed", "1") + if code != 2 || out != "" || !strings.Contains(errb, "| fejkdata") { + t.Errorf("run with nothing piped = %d, %q, %q; want misuse showing a template piped to fejkdata", code, out, errb) + } + for _, args := range [][]string{{"--list"}, {"--help"}, {"--version"}} { + if code, out, errb := runTerminal(args...); code != 0 || out == "" { + t.Errorf("run(%v) with nothing piped = %d, %q, %q; want it to run, reading no stdin", args, code, out, errb) + } + } +} + +func TestRunDropsOneTrailingNewline(t *testing.T) { + for stdin, want := range map[string]string{ + "hihi": "hihi\n", + "hihi\n": "hihi\n", + "hihi\n\n": "hihi\n\n", + "a\nb\n": "a\nb\n", + " hihi \n": " hihi \n", + "hihi\r\n": "hihi\r\n", + "{{x}}\n": "{x}\n", + "x: {/sv_SE.person.last}\n\n": "", + } { + code, out, errb := runOut(stdin, "--seed", "1") + if want == "" { + if code != 0 || !strings.HasPrefix(out, "x: ") || !strings.HasSuffix(out, "\n\n") || strings.Count(out, "\n") != 2 { + t.Errorf("run(%q) = %d, %q, %q; want the template's own newline, then the line's", stdin, code, out, errb) + } + continue + } + if code != 0 || out != want { + t.Errorf("run(%q) = %d, %q, %q; want %q", stdin, code, out, errb, want) + } + } +} + +func TestRunTextPrintsAsWritten(t *testing.T) { + for _, stdin := range []string{"sv_SE.person", "42", "null", "true", "[Skåne län]", "x[1]y", `say "hi"`} { + code, out, errb := runOut(stdin) + if code != 0 || out != stdin+"\n" { + t.Errorf("run(%q) = %d, %q, %q; want the text as written", stdin, code, out, errb) + } + } +} + +func TestRunTrimsWhitespaceAroundJSON(t *testing.T) { + for _, stdin := range []string{ + "{\"format\":\"{x}\",\"x\":[\"a\",\"b\"]}\n", + " {\"format\":\"{x}\",\"x\":[\"a\",\"b\"]}\n\n", + "\t[\"a\",\"b\"]\r\n", + " \"a\" \n", + } { + code, out, errb := runOut(stdin, "--seed", "1") + if code != 0 || (out != "a\n" && out != "b\n") { + t.Errorf("run(%q) = %d, %q, %q; want a or b", stdin, code, out, errb) } } } func TestRunMultipleDataPaths(t *testing.T) { - code, out, errb := runOut("-d", enUS, "--data-path", svSE, "person") + code, out, errb := runOut("{/person}", "-d", enUS, "--data-path", svSE) if code != 0 { t.Fatalf("run(multi-dir) = %d, stderr=%q", code, errb) } @@ -244,7 +323,7 @@ func TestRunMultipleDataPaths(t *testing.T) { } func TestRunUnknownCategoryFails(t *testing.T) { - code, _, errb := runOut("--data-path", svSE, "nope") + code, _, errb := runOut("{/nope}", "--data-path", svSE) if code != 1 { t.Fatalf("run = %d, want 1", code) } @@ -254,7 +333,7 @@ func TestRunUnknownCategoryFails(t *testing.T) { } func TestRunList(t *testing.T) { - code, out, errb := runOut("--data-path", svSE, "--list") + code, out, errb := runOut("", "--data-path", svSE, "--list") if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -267,7 +346,7 @@ func TestRunList(t *testing.T) { func TestRunHelpOnStdout(t *testing.T) { for _, h := range []string{"-h", "--help"} { - code, out, errb := runOut(h) + code, out, errb := runOut("", h) if code != 0 { t.Errorf("%s = %d, want 0", h, code) } @@ -278,7 +357,7 @@ func TestRunHelpOnStdout(t *testing.T) { } func TestRunVersion(t *testing.T) { - code, out, errb := runOut("--version") + code, out, errb := runOut("", "--version") if code != 0 { t.Fatalf("--version = %d, stderr=%q", code, errb) } @@ -289,7 +368,7 @@ func TestRunVersion(t *testing.T) { } func TestRunMissingDirFails(t *testing.T) { - code, _, errb := runOut("--data-path", "../../data/nope", "person") + code, _, errb := runOut("{/person}", "--data-path", "../../data/nope") if code != 1 { t.Fatalf("run = %d, want 1", code) } @@ -299,34 +378,20 @@ func TestRunMissingDirFails(t *testing.T) { } func TestRunShippedDataByDefault(t *testing.T) { - code, out, errb := runOut("--seed", "1", "sv_SE.person") + code, out, errb := runOut("{/sv_SE.person}", "--seed", "1") if code != 0 || strings.TrimSpace(out) == "" { t.Fatalf("run = %d, out=%q, stderr=%q", code, out, errb) } - code, list, _ := runOut("--list") + code, list, _ := runOut("", "--list") if code != 0 || !strings.Contains(list, "en_US.person\n") || !strings.Contains(list, "misc.uuid\n") { t.Errorf("--list without --data-path = %d, %q", code, list) } - code, _, errb = runOut("person") + code, _, errb = runOut("{/person}") if code != 1 || !strings.Contains(errb, "person") { t.Errorf("a category outside the shipped tree: code %d, stderr %q", code, errb) } } -func TestClassify(t *testing.T) { - for arg, want := range map[string]argKind{"sv_SE.person": argPath, "name: {x}": argTemplate} { - if got, err := classify(arg); err != nil || got != want { - t.Errorf("classify(%q) = %v, %v; want %v", arg, got, err, want) - } - } - if _, err := classify("[abc]"); err == nil || !strings.Contains(err.Error(), `"["`) || !strings.Contains(err.Error(), "JSON") { - t.Errorf("classify([abc]) = %v; want it rejected naming the leading bracket", err) - } - if got, err := classify("geo.SE.municipality[St. Louis].name"); err != nil || got != argPath { - t.Errorf("classify(a selecting path) = %v, %v; want a path", got, err) - } -} - func TestRunSelectsATableRow(t *testing.T) { dir := t.TempDir() for name, content := range map[string]string{ @@ -337,95 +402,92 @@ func TestRunSelectsATableRow(t *testing.T) { t.Fatal(err) } } - if code, out, errb := runOut("--no-shipped-data", "-d", dir, "region[12]"); code != 0 || out != "Skåne län\n" { - t.Fatalf("region[12] = %d, %q, stderr=%q", code, out, errb) + if code, out, errb := runOut("{/region[12]}", "--no-shipped-data", "-d", dir); code != 0 || out != "Skåne län\n" { + t.Fatalf("{/region[12]} = %d, %q, stderr=%q", code, out, errb) } - if code, out, _ := runOut("--no-shipped-data", "-d", dir, "region[Skåne län].code"); code != 0 || out != "12\n" { - t.Fatalf("region[Skåne län].code = %d, %q", code, out) + if code, out, _ := runOut("{/region[Skåne län].code}", "--no-shipped-data", "-d", dir); code != 0 || out != "12\n" { + t.Fatalf("{/region[Skåne län].code} = %d, %q", code, out) } - if code, out, _ := runOut("--no-shipped-data", "-d", dir, "--format", "sql", "region[12]"); code != 0 || out != `INSERT INTO "region" ("code", "name") VALUES ('12', 'Skåne län');`+"\n" { - t.Fatalf("--format sql region[12] = %d, %q, want the table named without its selector", code, out) + if code, out, _ := runOut("{/region[12]}", "--no-shipped-data", "-d", dir, "--format", "sql"); code != 0 || out != `INSERT INTO "region" ("code", "name") VALUES ('12', 'Skåne län');`+"\n" { + t.Fatalf("--format sql {/region[12]} = %d, %q, want the table named without its selector", code, out) } - if code, _, errb := runOut("--no-shipped-data", "-d", dir, "region[99]"); code != 1 || !strings.Contains(errb, `"99"`) { - t.Fatalf("region[99] = %d, stderr=%q, want a runtime error naming the row", code, errb) + if code, _, errb := runOut("{/region[99]}", "--no-shipped-data", "-d", dir); code != 1 || !strings.Contains(errb, `"99"`) { + t.Fatalf("{/region[99]} = %d, stderr=%q, want a runtime error naming the row", code, errb) } - if code, _, errb := runOut("--no-shipped-data", "-d", dir, "[Skåne län]"); code != 2 || !strings.Contains(errb, "JSON") { - t.Fatalf("[Skåne län] = %d, stderr=%q, want misuse: a leading bracket that is no JSON names nothing", code, errb) - } - if code, out, _ := runOut("--seed", "1", "--format", "csv", "misc.territory[SE]"); code != 0 || !strings.HasPrefix(out, "alpha2,") || !strings.Contains(out, "\nSE,SWE,") { - t.Fatalf("--format csv misc.territory[SE] = %d, %q", code, out) + if code, out, _ := runOut("{/misc.territory[SE]}", "--seed", "1", "--format", "csv"); code != 0 || !strings.HasPrefix(out, "alpha2,") || !strings.Contains(out, "\nSE,SWE,") { + t.Fatalf("--format csv {/misc.territory[SE]} = %d, %q", code, out) } } func TestUsageReferencesResolve(t *testing.T) { for _, token := range regexp.MustCompile(`\{/[^}]+\}`).FindAllString(usage, -1) { - path := token[2 : len(token)-1] - code, out, errb := runOut("--seed", "1", path) + code, out, errb := runOut(token, "--seed", "1") if code != 0 || strings.TrimSpace(out) == "" { - t.Errorf("usage advertises %s: run %s = %d, %q, stderr %q", token, path, code, out, errb) + t.Errorf("usage advertises %s: run = %d, %q, stderr %q", token, code, out, errb) } } } -func TestRunShapeMisuseBeforeLoad(t *testing.T) { - code, _, errb := runOut("--no-shipped-data", "[abc]") - if code != 2 || !strings.Contains(errb, "[abc]") || strings.Contains(errb, "--data-path") { - t.Fatalf("shape misuse with no data = %d, %q; want the shape error before any load", code, errb) +func TestRunArgumentMisuseBeforeLoad(t *testing.T) { + code, _, errb := runOut("", "--no-shipped-data", "x") + if code != 2 || !strings.Contains(errb, "stdin") || strings.Contains(errb, "--data-path") { + t.Fatalf("an argument with no data = %d, %q; want the argument refused before any load", code, errb) } } func TestRunInlineTemplate(t *testing.T) { - code, out, errb := runOut("--seed", "1", "name: {/sv_SE.person.last}") + code, out, errb := runOut("name: {/sv_SE.person.last}", "--seed", "1") if code != 0 || !strings.HasPrefix(out, "name: ") || strings.Contains(out, "{") { t.Fatalf("inline format string = %d, %q, stderr %q", code, out, errb) } - code, out, errb = runOut("--seed", "1", `{"format":"name: {x}","x":["bosse","lina"]}`) + code, out, errb = runOut(`{"format":"name: {x}","x":["bosse","lina"]}`, "--seed", "1") if code != 0 || (out != "name: bosse\n" && out != "name: lina\n") { t.Fatalf("inline JSON template = %d, %q, want one name, stderr %q", code, out, errb) } - code, out, errb = runOut("--seed", "1", `"name: {/sv_SE.person.last}"`) + code, out, errb = runOut(`"name: {/sv_SE.person.last}"`, "--seed", "1") if code != 0 || !strings.HasPrefix(out, "name: ") || strings.Contains(out, "{") { t.Fatalf("inline JSON string = %d, %q, stderr %q", code, out, errb) } - code, out, errb = runOut("--seed", "1", "-n", "2", `{digits(1)}`) + code, out, errb = runOut(`{digits(1)}`, "--seed", "1", "-n", "2") if code != 0 || len(strings.Split(strings.TrimRight(out, "\n"), "\n")) != 2 { t.Fatalf("inline template with --repeat = %d, %q, stderr %q", code, out, errb) } + code, out, errb = runOut("{/sv_SE.person} hihi", "--seed", "1") + if code != 0 || !strings.HasSuffix(out, " hihi\n") || strings.Contains(out, "{") { + t.Fatalf("a reference and text = %d, %q, stderr %q", code, out, errb) + } } func TestRunTemplateMisuse(t *testing.T) { - for arg, want := range map[string]string{ + for stdin, want := range map[string]string{ "{bad": "unterminated", - "[red,green]": `starts with "["`, `{"format":"x"}`: "is a string", "name: {/no.such.path}": "no entry", - "{/sv_SE.person}": "write sv_SE.person", - "x[1]y": `"]"`, - ` ["a","b"] `: "may not be padded", + "a } b": "}}", } { - code, out, errb := runOut("--seed", "1", arg) + code, out, errb := runOut(stdin, "--seed", "1") if code != 2 || out != "" || !strings.Contains(errb, "try 'fejkdata --help'") || !strings.Contains(errb, want) { - t.Errorf("run(%q) = %d, %q, %q; want misuse naming %q and --help", arg, code, out, errb, want) + t.Errorf("run(%q) = %d, %q, %q; want misuse naming %q and --help", stdin, code, out, errb, want) } if strings.Contains(errb, "fejkdata: fejkdata:") { - t.Errorf("run(%q) doubled the program prefix: %q", arg, errb) + t.Errorf("run(%q) doubled the program prefix: %q", stdin, errb) } } } func TestRunNoShippedData(t *testing.T) { - code, list, errb := runOut("--no-shipped-data", "-d", misc, "--list") + code, list, errb := runOut("", "--no-shipped-data", "-d", misc, "--list") if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } if strings.Contains(list, "sv_SE") || !strings.Contains(list, "uuid\n") { t.Errorf("--no-shipped-data --list = %q, want only the given dir", list) } - code, out, _ := runOut("--no-shipped-data", "-d", misc, "-s", "3", "uuid") + code, out, _ := runOut("{/uuid}", "--no-shipped-data", "-d", misc, "-s", "3") if code != 0 || strings.TrimSpace(out) == "" { t.Errorf("run = %d, out=%q", code, out) } - code, _, errb = runOut("--no-shipped-data", "sv_SE.person") + code, _, errb = runOut("{/sv_SE.person}", "--no-shipped-data") if code != 2 || !strings.Contains(errb, "--no-shipped-data needs at least one --data-path") { t.Errorf("--no-shipped-data alone = %d, %q, want misuse naming --data-path", code, errb) } @@ -433,11 +495,11 @@ func TestRunNoShippedData(t *testing.T) { func TestRunRepeatIsBounded(t *testing.T) { for _, args := range [][]string{ - {"--repeat", "9223372036854775807", "sv_SE.word"}, - {"--repeat", "1048577", "sv_SE.word"}, - {"-n", "99999999999", "sv_SE.word"}, + {"--repeat", "9223372036854775807"}, + {"--repeat", "1048577"}, + {"-n", "99999999999"}, } { - code, out, errb := runOut(args...) + code, out, errb := runOut("{/sv_SE.word}", args...) if code != 2 || out != "" || !strings.Contains(errb, "1..1048576") { t.Errorf("run(%v) = %d, %q, %q, want misuse naming the range", args, code, out, errb) } @@ -446,7 +508,7 @@ func TestRunRepeatIsBounded(t *testing.T) { if err := os.WriteFile(filepath.Join(dir, "x.json"), []byte(`"x"`), 0o644); err != nil { t.Fatal(err) } - code, out, errb := runOut("--no-shipped-data", "-d", dir, "--repeat", "1048576", "--separator", "", "x") + code, out, errb := runOut("{/x}", "--no-shipped-data", "-d", dir, "--repeat", "1048576", "--separator", "") if code != 0 || len(out) != 1048576+1 { t.Errorf("repeat at the cap = %d, %d bytes, stderr %q; want every render streamed", code, len(out), errb) } @@ -454,7 +516,7 @@ func TestRunRepeatIsBounded(t *testing.T) { func TestRunListTakesNoRepeatOrSeparator(t *testing.T) { for _, args := range [][]string{{"--list", "-n", "3"}, {"--list", "--separator", ","}} { - code, _, errb := runOut(args...) + code, _, errb := runOut("", args...) if code != 2 || !strings.Contains(errb, "--list takes no") { t.Errorf("run(%v) = %d, %q, want misuse", args, code, errb) } @@ -462,14 +524,14 @@ func TestRunListTakesNoRepeatOrSeparator(t *testing.T) { } func TestRunUnknownFlagIsNamedByRune(t *testing.T) { - code, _, errb := runOut("-ä", "sv_SE.word") + code, _, errb := runOut("{/sv_SE.word}", "-ä") if code != 2 || !strings.Contains(errb, "unknown flag -ä") { t.Errorf("run(-ä) = %d, %q, want the flag named whole", code, errb) } } func TestRunEmptyDataPathIsNamed(t *testing.T) { - code, _, errb := runOut("--data-path=", "sv_SE.word") + code, _, errb := runOut("{/sv_SE.word}", "--data-path=") if code != 1 || !strings.Contains(errb, "empty") { t.Errorf("run(--data-path=) = %d, %q, want the empty path named", code, errb) } @@ -486,7 +548,7 @@ func recordDir(t *testing.T) string { } func TestRunRecordJSON(t *testing.T) { - code, out, errb := runOut("--seed", "1", "--format", "json", "--repeat", "2", "--data-path", recordDir(t), "users") + code, out, errb := runOut("{/users}", "--seed", "1", "--format", "json", "--repeat", "2", "--data-path", recordDir(t)) if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -500,7 +562,7 @@ func TestRunRecordJSON(t *testing.T) { } func TestRunRecordNDJSON(t *testing.T) { - code, out, errb := runOut("--seed", "1", "--format", "ndjson", "--repeat", "2", "--data-path", recordDir(t), "users") + code, out, errb := runOut("{/users}", "--seed", "1", "--format", "ndjson", "--repeat", "2", "--data-path", recordDir(t)) if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -517,7 +579,7 @@ func TestRunRecordNDJSON(t *testing.T) { } func TestRunRecordCSVRoundTrips(t *testing.T) { - code, out, errb := runOut("--seed", "1", "--format", "csv", "--repeat", "3", "--data-path", recordDir(t), "users") + code, out, errb := runOut("{/users}\n", "--seed", "1", "--format", "csv", "--repeat", "3", "--data-path", recordDir(t)) if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -538,7 +600,7 @@ func TestRunRecordCSVRoundTrips(t *testing.T) { } func TestRunRecordSQL(t *testing.T) { - code, out, errb := runOut("--seed", "1", "--format", "sql", "--repeat", "2", "--table", "people", "--data-path", recordDir(t), "users") + code, out, errb := runOut("{/users}", "--seed", "1", "--format", "sql", "--repeat", "2", "--table", "people", "--data-path", recordDir(t)) if code != 0 { t.Fatalf("run = %d, stderr=%q", code, errb) } @@ -549,14 +611,14 @@ func TestRunRecordSQL(t *testing.T) { } func TestRunRecordDefaultTable(t *testing.T) { - code, out, errb := runOut("--seed", "1", "--format", "sql", "--data-path", recordDir(t), "users") + code, out, errb := runOut("{/users}", "--seed", "1", "--format", "sql", "--data-path", recordDir(t)) if code != 0 || !strings.HasPrefix(out, `INSERT INTO "users" (`) { - t.Fatalf("default table = %d, %q, want the path's last segment; stderr %q", code, out, errb) + t.Fatalf("default table = %d, %q, want the reference's last segment; stderr %q", code, out, errb) } } func TestRunRecordInlineTemplate(t *testing.T) { - code, out, errb := runOut("--seed", "1", "--format", "json", `{"format":"{x}","x":["a","b"]}`) + code, out, errb := runOut(`{"format":"{x}","x":["a","b"]}`, "--seed", "1", "--format", "json") if code != 0 { t.Fatalf("inline record = %d, stderr=%q", code, errb) } @@ -571,13 +633,13 @@ func TestRunRecordMisuse(t *testing.T) { args []string want string }{ - {[]string{"--format", "yaml", "users"}, "--format takes csv, json, ndjson, sql or text"}, - {[]string{"--format", "json", "--separator", ",", "users"}, "--separator joins text values"}, - {[]string{"--table", "t", "users"}, "--table names the INSERT target"}, - {[]string{"--format", "json", "--table", "t", "users"}, "--table names the INSERT target"}, - {[]string{"--format", "sql", "--table", "", "users"}, "--table names the INSERT target"}, + {[]string{"--format", "yaml"}, "--format takes csv, json, ndjson, sql or text"}, + {[]string{"--format", "json", "--separator", ","}, "--separator joins text values"}, + {[]string{"--table", "t"}, "--table names the INSERT target"}, + {[]string{"--format", "json", "--table", "t"}, "--table names the INSERT target"}, + {[]string{"--format", "sql", "--table", ""}, "--table names the INSERT target"}, } { - code, out, errb := runOut(c.args...) + code, out, errb := runOut("{/users}", c.args...) if code != 2 || out != "" || !strings.Contains(errb, c.want) { t.Errorf("run(%v) = %d, %q, %q; want misuse naming %q", c.args, code, out, errb, c.want) } @@ -589,7 +651,7 @@ func TestRunRecordOnABareValueIsRuntimeError(t *testing.T) { if err := os.WriteFile(filepath.Join(dir, "word.json"), []byte(`["a","b"]`), 0o644); err != nil { t.Fatal(err) } - code, _, errb := runOut("--format", "json", "--data-path", dir, "word") + code, _, errb := runOut("{/word}", "--format", "json", "--data-path", dir) if code != 1 { t.Fatalf("record on a choice = %d, want exit 1 (runtime error), stderr %q", code, errb) } @@ -601,22 +663,22 @@ func TestRunRecordRejectsATopLevelRepeat(t *testing.T) { if err := os.WriteFile(filepath.Join(dir, "rep.json"), []byte(content), 0o644); err != nil { t.Fatal(err) } - code, _, errb := runOut("--format", "json", "--data-path", dir, "rep") + code, _, errb := runOut("{/rep}", "--format", "json", "--data-path", dir) if code != 1 || !strings.Contains(errb, "carries repeat 3") { t.Errorf("record on a repeating path = %d, %q; want exit 1 naming the repeat", code, errb) } - code, _, errb = runOut("--format", "json", content) + code, _, errb = runOut(content, "--format", "json") if code != 2 || !strings.Contains(errb, "carries repeat 3") { t.Errorf("record on a repeating inline template = %d, %q; want exit 2 naming the repeat", code, errb) } - if code, out, _ := runOut("--seed", "1", "--data-path", dir, "rep"); code != 0 || strings.TrimSpace(out) != "x-|x-|x-" { + if code, out, _ := runOut("{/rep}", "--seed", "1", "--data-path", dir); code != 0 || strings.TrimSpace(out) != "x-|x-|x-" { t.Errorf("text view = %d, %q; want the repeat still composed", code, out) } } func TestRunNumbersFollowTheShell(t *testing.T) { - _, want, _ := runOut("--seed", "7", "--repeat", "3", "sv_SE.word") - code, got, errb := runOut("--seed", "007", "--repeat", "+3", "sv_SE.word") + _, want, _ := runOut("{/sv_SE.word}", "--seed", "7", "--repeat", "3") + code, got, errb := runOut("{/sv_SE.word}", "--seed", "007", "--repeat", "+3") if code != 0 || got != want { t.Errorf("run(--seed 007 --repeat +3) = %d, %q, stderr %q; want the same as --seed 7 --repeat 3 %q", code, got, errb, want) } @@ -624,11 +686,11 @@ func TestRunNumbersFollowTheShell(t *testing.T) { // TestRunRecordOfAFieldlessPath pins the way out of asking a category of one value // for columns: the record to write, and the quoting a shell needs to take it. The -// argument is well-formed and only the data cannot serve it, so the code is 1. +// template is well-formed and only the data cannot serve it, so the code is 1. func TestRunRecordOfAFieldlessPath(t *testing.T) { - code, out, errb := runOut("--format", "csv", "sv_SE.personnummer") + code, out, errb := runOut("{/sv_SE.personnummer}", "--format", "csv") if code != 1 { - t.Fatalf("run(--format csv sv_SE.personnummer) = %d, want 1", code) + t.Fatalf("run(--format csv) of {/sv_SE.personnummer} = %d, want 1", code) } for _, want := range []string{`{"format":"","personnummer":"{/sv_SE.personnummer}"}`, "single quotes"} { if !strings.Contains(errb, want) {