GNU-style CLI flags, embedded data, and a literal format grammar #3
+6
-6
@@ -88,18 +88,18 @@ func TestCalcReproducible(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCalcTokenOperandsReadsOnlyACalc pins what the helper answers for a body that
|
// TestTokenOperandsReadsOnlyAnOperandBuiltin pins what the helper answers for a body that
|
||||||
// is not a calc, and for one whose expression does not parse: nothing, either way.
|
// is not a calc, and for one whose expression does not parse: nothing, either way.
|
||||||
// checkCalc is what reports a bad expression, so the callers that run after it
|
// checkCalc is what reports a bad expression, so the callers that run after it
|
||||||
// never meet one — but they must not have to depend on that order to be safe.
|
// never meet one — but they must not have to depend on that order to be safe.
|
||||||
func TestCalcTokenOperandsReadsOnlyACalc(t *testing.T) {
|
func TestTokenOperandsReadsOnlyAnOperandBuiltin(t *testing.T) {
|
||||||
for _, body := range []string{"plain", "luhn()", "calc()", "calc(1 +)", "calc(()"} {
|
for _, body := range []string{"plain", "luhn()", "calc()", "calc(1 +)", "calc(()"} {
|
||||||
if got := calcTokenOperands(body); got != nil {
|
if got := tokenOperands(body); got != nil {
|
||||||
t.Errorf("calcTokenOperands(%q) = %v, want none", body, got)
|
t.Errorf("tokenOperands(%q) = %v, want none", body, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if got := calcTokenOperands("calc(net * qty)"); len(got) != 2 {
|
if got := tokenOperands("calc(net * qty)"); len(got) != 2 {
|
||||||
t.Errorf("calcTokenOperands(calc(net * qty)) = %v, want both operands", got)
|
t.Errorf("tokenOperands(calc(net * qty)) = %v, want both operands", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+62
-5
@@ -1,6 +1,9 @@
|
|||||||
package fejkdata
|
package fejkdata
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
// TestRootReferenceAcrossFolders is the headline case: a category in one folder
|
// TestRootReferenceAcrossFolders is the headline case: a category in one folder
|
||||||
// pulls a value from another via a {..path} reference resolved from the data root.
|
// pulls a value from another via a {..path} reference resolved from the data root.
|
||||||
@@ -15,8 +18,7 @@ func TestRootReferenceAcrossFolders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestReferenceIntoAField reaches a field inside a referenced category, crossing
|
// TestReferenceIntoAField reaches a field inside a referenced category.
|
||||||
// a single-variant choice and then a template field (..who.last).
|
|
||||||
func TestReferenceIntoAField(t *testing.T) {
|
func TestReferenceIntoAField(t *testing.T) {
|
||||||
dir := writeData(t, map[string]string{
|
dir := writeData(t, map[string]string{
|
||||||
"who": `{"format":"{first} {last}","first":"Ada","last":"Byron"}`,
|
"who": `{"format":"{first} {last}","first":"Ada","last":"Byron"}`,
|
||||||
@@ -76,8 +78,8 @@ func TestReferenceErrors(t *testing.T) {
|
|||||||
cases := map[string]map[string]string{
|
cases := map[string]map[string]string{
|
||||||
"missing target": {"card": `"{..nope.gone}"`},
|
"missing target": {"card": `"{..nope.gone}"`},
|
||||||
"folder target": {"en_US/word": `"w"`, "card": `"{..en_US}"`},
|
"folder target": {"en_US/word": `"w"`, "card": `"{..en_US}"`},
|
||||||
"multi-variant on the path": {
|
"a variant on the path lacks the field": {
|
||||||
"who": `[{"format":"{f}","f":"1"},{"format":"{f}","f":"2"}]`,
|
"who": `[{"format":"{f}","f":"1"},{"format":"{g}","g":"2"}]`,
|
||||||
"card": `"{..who.f}"`,
|
"card": `"{..who.f}"`,
|
||||||
},
|
},
|
||||||
"empty reference path": {"card": `"{..}"`},
|
"empty reference path": {"card": `"{..}"`},
|
||||||
@@ -208,3 +210,58 @@ func TestNewErrorPathIsCanonical(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReferencePathIsHeld(t *testing.T) {
|
||||||
|
dir := writeData(t, map[string]string{
|
||||||
|
"person": `[{"format":"{first} {last}","first":"Anna","last":"Andersson"},{"format":"{first} {last}","first":"Bo","last":"Berg"}]`,
|
||||||
|
"card": `"{..person.first} {..person.last}"`,
|
||||||
|
})
|
||||||
|
f := newGenerator(t, dir, WithSeed(3))
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
got := fake(t, f, "card")
|
||||||
|
if got != "Anna Andersson" && got != "Bo Berg" {
|
||||||
|
t.Fatalf("card = %q, want one person's first and last name", got)
|
||||||
|
}
|
||||||
|
seen[got] = true
|
||||||
|
}
|
||||||
|
if len(seen) != 2 {
|
||||||
|
t.Fatalf("card only ever rendered %v", seen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReferenceThroughChoiceNeedsEveryVariant(t *testing.T) {
|
||||||
|
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
|
||||||
|
"who": `[{"format":"{f}{h}","f":"1","h":"x"},{"format":"{g}{h}","g":"2","h":"y"}]`,
|
||||||
|
"card": `"{..who.f}"`,
|
||||||
|
})))
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "not every variant") {
|
||||||
|
t.Fatalf("New = %v, want the missing variant named", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBareReferenceDrawsEachTime(t *testing.T) {
|
||||||
|
dir := writeData(t, map[string]string{
|
||||||
|
"die": `["1","2","3","4","5","6"]`,
|
||||||
|
"roll": `"{..die} {..die}"`,
|
||||||
|
})
|
||||||
|
f := newGenerator(t, dir, WithSeed(1))
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
if got := fake(t, f, "roll"); got[0] != got[2] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal("two bare references always agreed; each should be its own draw")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReferenceOverlapIsRejected(t *testing.T) {
|
||||||
|
for name, file := range map[string]string{
|
||||||
|
"head beside a path": `{"format":"{..cat.p} {..cat.p.first}","p":[{"format":"{first}","first":"A"},{"format":"{first}","first":"B"}]}`,
|
||||||
|
"sibling path beside a reference path": `{"format":"{p.first} {..cat.p.last}","p":[{"format":"{first}","first":"A","last":"1"},{"format":"{first}","first":"B","last":"2"}]}`,
|
||||||
|
} {
|
||||||
|
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"cat": file})))
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "reads a path into") {
|
||||||
|
t.Errorf("%s: New = %v, want the overlap rejected", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package fejkdata
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestTransforms(t *testing.T) {
|
||||||
|
f := engine(1)
|
||||||
|
for src, want := range map[string]string{
|
||||||
|
`{"format":"{uppercase(x)}|{lowercase(x)}|{ascii(x)}","x":"Åsa Ödegård-Nuñez"}`: "ÅSA ÖDEGÅRD-NUÑEZ|åsa ödegård-nuñez|Asa Odegard-Nunez",
|
||||||
|
`{"format":"{lowercase(ascii(x))}","x":"Åsa"}`: "asa",
|
||||||
|
`{"format":"{ascii(x)}","x":"Ærø ß 日本"}`: "AEro ss ",
|
||||||
|
} {
|
||||||
|
if got := mustRender(t, f, src); got != want {
|
||||||
|
t.Errorf("render(%s) = %q, want %q", src, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransformReadsTheHeldDraw(t *testing.T) {
|
||||||
|
f := engine(2)
|
||||||
|
src := `{"format":"{p.first}={lowercase(p.first)}","p":[{"format":"{first}","first":"Anna"},{"format":"{first}","first":"Bo"}]}`
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
if got := mustRender(t, f, src); got != "Anna=anna" && got != "Bo=bo" {
|
||||||
|
t.Fatalf("render = %q, want the transform over the same draw", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransformOverAReference(t *testing.T) {
|
||||||
|
dir := writeData(t, map[string]string{
|
||||||
|
"person": `[{"format":"{first} {last}","first":"Åsa","last":"Öberg"},{"format":"{first} {last}","first":"Bo","last":"Ek"}]`,
|
||||||
|
"email": `"{..person.first} {..person.last} <{lowercase(ascii(..person.first))}.{lowercase(ascii(..person.last))}@example.com>"`,
|
||||||
|
})
|
||||||
|
f := newGenerator(t, dir, WithSeed(5))
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
if got := fake(t, f, "email"); got != "Åsa Öberg <asa.oberg@example.com>" && got != "Bo Ek <bo.ek@example.com>" {
|
||||||
|
t.Fatalf("email = %q, want a record from one draw", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransformArgs(t *testing.T) {
|
||||||
|
for _, bad := range []string{
|
||||||
|
`{"format":"{lowercase()}","x":"v"}`,
|
||||||
|
`{"format":"{lowercase(nope)}","x":"v"}`,
|
||||||
|
`{"format":"{ascii(x,y)}","x":"v","y":"w"}`,
|
||||||
|
`{"format":"{lowercase(hex(2))}","x":"v"}`,
|
||||||
|
`{"format":"{lowercase(x)} {x.a}","x":{"format":"{a}","a":"1"}}`,
|
||||||
|
} {
|
||||||
|
if _, err := compile(parse(t, bad)); err == nil {
|
||||||
|
t.Errorf("compile(%s) = nil error, want it rejected", bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user