Compare commits

..

1 Commits

Author SHA1 Message Date
lilleman 4c8f30406e Tests for a bounded, streamed --repeat, rune-named flags, and --list with inert flags
Tests / vet + fmt + tests (pull_request) Failing after 43s
2026-09-02 19:05:54 +02:00
5 changed files with 31 additions and 44 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ level — folders, then the category (a JSON file), then fields.
| `-d`, `--data-path D` | a directory to layer over the shipped data; repeatable, the last wins a name clash | | `-d`, `--data-path D` | a directory to layer over the shipped data; repeatable, the last wins a name clash |
| `--no-shipped-data` | load only the `--data-path` directories | | `--no-shipped-data` | load only the `--data-path` directories |
| `-s`, `--seed N` | reproducible output | | `-s`, `--seed N` | reproducible output |
| `-n`, `--repeat N` | render the path N times (up to 1048576), each an independent draw, streamed | | `-n`, `--repeat N` | render the path N times, each an independent draw |
| `--separator S` | between repeated values (default a newline) | | `--separator S` | between repeated values (default a newline) |
| `--list` | print every path, then exit | | `--list` | print every path, then exit |
| `--version`, `-h`, `--help` | print, then exit | | `--version`, `-h`, `--help` | print, then exit |
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// fat-fingered or overflowing argument fails at New instead of trying to allocate // fat-fingered or overflowing argument fails at New instead of trying to allocate
// gigabytes — or panicking — at render. // gigabytes — or panicking — at render.
const ( const (
maxLen = MaxRepeat maxLen = 1 << 20 // 1,048,576 chars/bytes
maxDecimals = 1024 maxDecimals = 1024
) )
+28 -38
View File
@@ -8,7 +8,6 @@
package main package main
import ( import (
"bufio"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -28,7 +27,7 @@ const usage = `Usage: fejkdata [flags] <path>
-h, --help print this help, then exit -h, --help print this help, then exit
--list list the paths the data offers, then exit --list list the paths the data offers, then exit
--no-shipped-data load only the --data-path directories --no-shipped-data load only the --data-path directories
-n, --repeat N render the path N times, 1..1048576 (default 1) -n, --repeat N render the path N times (default 1)
-s, --seed N seed for reproducible output -s, --seed N seed for reproducible output
--separator S string between repeated values (default newline) --separator S string between repeated values (default newline)
--version print the version, then exit --version print the version, then exit
@@ -38,18 +37,16 @@ attaches or follows (-n3, -n 3); short flags bundle (-hn 3).
` `
type invocation struct { type invocation struct {
dirs []string dirs []string
help bool help bool
list bool list bool
noShipped bool noShipped bool
paths []string paths []string
repeat int repeat int
repeatSet bool seed uint64
seed uint64 seeded bool
seeded bool separator string
separator string version bool
separatorSet bool
version bool
} }
type flagDef struct { type flagDef struct {
@@ -66,10 +63,10 @@ var flagDefs = []flagDef{
{"no-shipped-data", "", false, func(in *invocation, _ string) error { in.noShipped = true; return nil }}, {"no-shipped-data", "", false, func(in *invocation, _ string) error { in.noShipped = true; return nil }},
{"repeat", "n", true, func(in *invocation, v string) error { {"repeat", "n", true, func(in *invocation, v string) error {
n, err := strconv.Atoi(v) n, err := strconv.Atoi(v)
if err != nil || n < 1 || n > fejkdata.MaxRepeat { if err != nil || n < 1 {
return fmt.Errorf("--repeat needs an integer in 1..%d, got %q", fejkdata.MaxRepeat, v) return fmt.Errorf("--repeat needs a positive integer, got %q", v)
} }
in.repeat, in.repeatSet = n, true in.repeat = n
return nil return nil
}}, }},
{"seed", "s", true, func(in *invocation, v string) error { {"seed", "s", true, func(in *invocation, v string) error {
@@ -80,7 +77,7 @@ var flagDefs = []flagDef{
in.seed, in.seeded = n, true in.seed, in.seeded = n, true
return nil return nil
}}, }},
{"separator", "", true, func(in *invocation, v string) error { in.separator, in.separatorSet = v, true; return nil }}, {"separator", "", true, func(in *invocation, v string) error { in.separator = v; return nil }},
{"version", "", false, func(in *invocation, _ string) error { in.version = true; return nil }}, {"version", "", false, func(in *invocation, _ string) error { in.version = true; return nil }},
} }
@@ -127,8 +124,8 @@ func splitFlags(arg string) ([]flagArg, error) {
} }
var flags []flagArg var flags []flagArg
letters := arg[1:] letters := arg[1:]
for i, r := range letters { for i := 0; i < len(letters); i++ {
letter := string(r) letter := letters[i : i+1]
def := flagByShort(letter) def := flagByShort(letter)
if def == nil { if def == nil {
return nil, fmt.Errorf("unknown flag -%s", letter) return nil, fmt.Errorf("unknown flag -%s", letter)
@@ -137,7 +134,7 @@ func splitFlags(arg string) ([]flagArg, error) {
flags = append(flags, flagArg{def: def}) flags = append(flags, flagArg{def: def})
continue continue
} }
rest := letters[i+len(letter):] rest := letters[i+1:]
if strings.HasPrefix(rest, "=") { if strings.HasPrefix(rest, "=") {
return nil, fmt.Errorf("-%s takes its value attached (-%s%s) or next (-%s %s); = belongs to --%s=%s", return nil, fmt.Errorf("-%s takes its value attached (-%s%s) or next (-%s %s); = belongs to --%s=%s",
letter, letter, rest[1:], letter, rest[1:], def.long, rest[1:]) letter, letter, rest[1:], letter, rest[1:], def.long, rest[1:])
@@ -191,9 +188,6 @@ func (in invocation) check() error {
if in.list && len(in.paths) > 0 { if in.list && len(in.paths) > 0 {
return errors.New("--list takes no path") return errors.New("--list takes no path")
} }
if in.list && (in.repeatSet || in.separatorSet) {
return errors.New("--list takes no --repeat or --separator")
}
if !in.list && len(in.paths) != 1 { if !in.list && len(in.paths) != 1 {
return fmt.Errorf("expected one path, got %d", len(in.paths)) return fmt.Errorf("expected one path, got %d", len(in.paths))
} }
@@ -214,23 +208,17 @@ func (in invocation) options() []fejkdata.Option {
return opts return opts
} }
// write streams the path's renders to w, repeat of them joined by the separator // values renders the path repeat times, joined by the separator.
// and ended by a newline. A path that renders once renders every time, so the only func (in invocation) values(f *fejkdata.Generator) (string, error) {
// failure comes before anything is written. vals := make([]string, in.repeat)
func (in invocation) write(f *fejkdata.Generator, w io.Writer) error { for i := range vals {
out := bufio.NewWriter(w)
for i := 0; i < in.repeat; i++ {
v, err := f.Fake(in.paths[0]) v, err := f.Fake(in.paths[0])
if err != nil { if err != nil {
return err return "", err
} }
if i > 0 { vals[i] = v
out.WriteString(in.separator)
}
out.WriteString(v)
} }
out.WriteString("\n") return strings.Join(vals, in.separator), nil
return out.Flush()
} }
func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) }
@@ -266,10 +254,12 @@ func run(args []string, stdout, stderr io.Writer) int {
} }
return 0 return 0
} }
if err := in.write(f, stdout); err != nil { out, err := in.values(f)
if err != nil {
fmt.Fprintln(stderr, err) fmt.Fprintln(stderr, err)
return 1 return 1
} }
fmt.Fprintln(stdout, out)
return 0 return 0
} }
+1 -1
View File
@@ -92,7 +92,7 @@ func TestRunShortFlagValues(t *testing.T) {
{[]string{"-s=42", "-d", svSE, "address"}, "-s42"}, {[]string{"-s=42", "-d", svSE, "address"}, "-s42"},
{[]string{"-s=42", "-d", svSE, "address"}, "--seed=42"}, {[]string{"-s=42", "-d", svSE, "address"}, "--seed=42"},
{[]string{"-d=" + svSE, "address"}, "--data-path="}, {[]string{"-d=" + svSE, "address"}, "--data-path="},
{[]string{"-nd", "3", "-d", svSE, "word"}, `--repeat needs an integer in 1..1048576, got "d"`}, {[]string{"-nd", "3", "-d", svSE, "word"}, `--repeat needs a positive integer, got "d"`},
{[]string{"--seed=", "-d", svSE, "word"}, `--seed needs an unsigned integer, got ""`}, {[]string{"--seed=", "-d", svSE, "word"}, `--seed needs an unsigned integer, got ""`},
} { } {
code, out, errb := runOut(c.args...) code, out, errb := runOut(c.args...)
-3
View File
@@ -28,9 +28,6 @@ import (
//go:embed data //go:embed data
var shippedFS embed.FS var shippedFS embed.FS
// MaxRepeat caps a repeat, and the renders nested repeats multiply to along any path.
const MaxRepeat = 1 << 20
// ErrNoData is returned by New when no source is loaded at all. // ErrNoData is returned by New when no source is loaded at all.
var ErrNoData = errors.New("no data: WithoutShippedData needs at least one WithDataPath or WithDataFS") var ErrNoData = errors.New("no data: WithoutShippedData needs at least one WithDataPath or WithDataFS")