Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
Showing only changes of commit b68ec83f29 - Show all commits
+204 -142
View File
@@ -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) {