Add fakes CLI (cmd/fakes); document it README-first with an SQL template example
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
// Command fakes prints one fake value from a locale directory.
|
||||
//
|
||||
// fakes ./locales/sv_SE person # a full person
|
||||
// fakes ./locales/sv_SE person.last # just the surname (dotted path)
|
||||
// fakes -seed 42 ./locales/sv_SE address
|
||||
//
|
||||
// It is a thin CLI over the fakes library: New(dir) then Fake(path).
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/Timewave-AB/fakes"
|
||||
)
|
||||
|
||||
const usage = `Usage: fakes [-seed N] <locale-dir> <path>
|
||||
|
||||
<locale-dir> a locale directory, e.g. ./locales/sv_SE
|
||||
<path> a category, or a dotted path into one (person, person.last)
|
||||
-seed N seed for reproducible output`
|
||||
|
||||
func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) }
|
||||
|
||||
// run is main's testable core: it returns the process exit code (0 ok, 1
|
||||
// runtime error, 2 misuse) and writes only to the given streams.
|
||||
func run(args []string, stdout, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("fakes", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
fs.Usage = func() { fmt.Fprintln(stderr, usage) }
|
||||
seed := fs.Uint64("seed", 0, "seed for reproducible output")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return 2
|
||||
}
|
||||
if fs.NArg() != 2 {
|
||||
fs.Usage()
|
||||
return 2
|
||||
}
|
||||
|
||||
var opts []fakes.Option
|
||||
fs.Visit(func(fl *flag.Flag) {
|
||||
if fl.Name == "seed" {
|
||||
opts = append(opts, fakes.WithSeed(*seed))
|
||||
}
|
||||
})
|
||||
|
||||
f, err := fakes.New(fs.Arg(0), opts...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
v, err := f.Fake(fs.Arg(1))
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintln(stdout, v)
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const svSE = "../../locales/sv_SE"
|
||||
|
||||
// runOut runs the CLI and returns exit code, stdout, stderr.
|
||||
func runOut(args ...string) (int, string, string) {
|
||||
var out, errb bytes.Buffer
|
||||
code := run(args, &out, &errb)
|
||||
return code, out.String(), errb.String()
|
||||
}
|
||||
|
||||
func TestRunOutputsValue(t *testing.T) {
|
||||
code, out, errb := runOut(svSE, "person")
|
||||
if code != 0 {
|
||||
t.Fatalf("run = %d, stderr=%q", code, errb)
|
||||
}
|
||||
if strings.TrimSpace(out) == "" {
|
||||
t.Fatalf("empty output, stderr=%q", errb)
|
||||
}
|
||||
if !strings.HasSuffix(out, "\n") {
|
||||
t.Errorf("output should end with newline, got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunDotPath(t *testing.T) {
|
||||
// person.last descends to just a surname — never the full "First Last".
|
||||
code, full, _ := runOut("-seed", "7", svSE, "person")
|
||||
if code != 0 {
|
||||
t.Fatalf("person run = %d", code)
|
||||
}
|
||||
code, last, errb := runOut("-seed", "7", svSE, "person.last")
|
||||
if code != 0 {
|
||||
t.Fatalf("person.last run = %d, stderr=%q", code, errb)
|
||||
}
|
||||
if strings.TrimSpace(last) == "" {
|
||||
t.Fatal("empty person.last output")
|
||||
}
|
||||
if last == full {
|
||||
t.Errorf("person.last %q should differ from person %q", last, full)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSeedDeterministic(t *testing.T) {
|
||||
_, a, _ := runOut("-seed", "42", svSE, "address")
|
||||
_, b, _ := runOut("-seed", "42", svSE, "address")
|
||||
if a != b {
|
||||
t.Errorf("same seed diverged: %q != %q", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunUsageOnWrongArgCount(t *testing.T) {
|
||||
for _, args := range [][]string{{}, {svSE}, {svSE, "person", "extra"}} {
|
||||
code, _, errb := runOut(args...)
|
||||
if code != 2 {
|
||||
t.Errorf("run(%v) = %d, want 2", args, code)
|
||||
}
|
||||
if !strings.Contains(errb, "Usage") {
|
||||
t.Errorf("run(%v) stderr = %q, want usage", args, errb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunUnknownCategoryFails(t *testing.T) {
|
||||
code, _, errb := runOut(svSE, "nope")
|
||||
if code != 1 {
|
||||
t.Fatalf("run = %d, want 1", code)
|
||||
}
|
||||
if !strings.Contains(errb, "nope") {
|
||||
t.Errorf("stderr %q should name the unknown category", errb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunBadLocaleFails(t *testing.T) {
|
||||
code, _, errb := runOut("../../locales/sv", "person")
|
||||
if code != 1 {
|
||||
t.Fatalf("run = %d, want 1", code)
|
||||
}
|
||||
if errb == "" {
|
||||
t.Error("want an error message on stderr")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user