Test that the shipped data loads from a Go package per module, that a bare New fails naming WithDataFS, and that a read nothing provides names the modules its reader's manifest reads

This commit is contained in:
2026-10-10 13:08:01 +02:00
committed by Lillem4n
parent bf1becbb59
commit 3368f085fd
26 changed files with 471 additions and 188 deletions
+19 -19
View File
@@ -6,17 +6,17 @@ import (
"testing"
)
func benchGenerator(b *testing.B, dir string) *Generator {
func benchGenerator(b *testing.B, data Option) *Generator {
b.Helper()
f, err := New(WithoutShippedData(), WithDataPath(dir), WithSeed(1))
f, err := New(data, WithSeed(1))
if err != nil {
b.Fatal(err)
}
return f
}
func benchPath(b *testing.B, dir, path string) {
f := benchGenerator(b, dir)
func benchPath(b *testing.B, data Option, path string) {
f := benchGenerator(b, data)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -26,12 +26,12 @@ func benchPath(b *testing.B, dir, path string) {
}
}
func BenchmarkPerson(b *testing.B) { benchPath(b, "data", "sv_SE.person") }
func BenchmarkAddress(b *testing.B) { benchPath(b, "data", "sv_SE.address") }
func BenchmarkWord(b *testing.B) { benchPath(b, "data", "sv_SE.word") }
func BenchmarkCreditcard(b *testing.B) { benchPath(b, "data", "misc.creditcard") }
func BenchmarkPersonnummer(b *testing.B) { benchPath(b, "data", "sv_SE.personnummer") }
func BenchmarkUUIDv7(b *testing.B) { benchPath(b, "data", "misc.uuid") }
func BenchmarkPerson(b *testing.B) { benchPath(b, withShipped(), "sv_SE.person") }
func BenchmarkAddress(b *testing.B) { benchPath(b, withShipped(), "sv_SE.address") }
func BenchmarkWord(b *testing.B) { benchPath(b, withShipped(), "sv_SE.word") }
func BenchmarkCreditcard(b *testing.B) { benchPath(b, withShipped(), "misc.creditcard") }
func BenchmarkPersonnummer(b *testing.B) { benchPath(b, withShipped(), "sv_SE.personnummer") }
func BenchmarkUUIDv7(b *testing.B) { benchPath(b, withShipped(), "misc.uuid") }
func tmpData(b *testing.B, name, body string) string {
b.Helper()
@@ -44,17 +44,17 @@ func tmpData(b *testing.B, name, body string) string {
func BenchmarkCalc(b *testing.B) {
dir := tmpData(b, "inv", `{"format":"{net} x {qty} = {calc(net * qty, 2)}","net":"19.99","qty":"3"}`)
benchPath(b, dir, "inv")
benchPath(b, WithDataPath(dir), "inv")
}
func BenchmarkLongLiteral(b *testing.B) {
dir := tmpData(b, "sql", `{"format":"INSERT INTO customers (id, name, city) VALUES (123, '{word}', '{word}');","word":["alpha","beta","gamma","delta"]}`)
benchPath(b, dir, "sql")
benchPath(b, WithDataPath(dir), "sql")
}
func BenchmarkRepeat(b *testing.B) {
dir := tmpData(b, "many", `{"format":"{word}","repeat":20,"separator":", ","word":["alpha","beta","gamma","delta"]}`)
benchPath(b, dir, "many")
benchPath(b, WithDataPath(dir), "many")
}
// BenchmarkNamed measures a format that binds a field to a name and reads two paths through
@@ -62,18 +62,18 @@ func BenchmarkRepeat(b *testing.B) {
// independent fields.
func BenchmarkNamed(b *testing.B) {
dir := tmpData(b, "addr", `{"format":"{place as p}{p.postal-code} {p.locality}","place":[{"format":"{locality}","locality":"Stockholm","postal-code":"1{digits(2)} {digits(2)}"},{"format":"{locality}","locality":"Tranås","postal-code":"573 {digits(2)}"}]}`)
benchPath(b, dir, "addr")
benchPath(b, WithDataPath(dir), "addr")
}
func BenchmarkUnnamed(b *testing.B) {
dir := tmpData(b, "addr", `{"format":"{postal-code} {locality}","postal-code":["1{digits(2)} {digits(2)}","573 {digits(2)}"],"locality":["Stockholm","Tranås"]}`)
benchPath(b, dir, "addr")
benchPath(b, WithDataPath(dir), "addr")
}
// BenchmarkNamedDeep reads two paths through a name and an intermediate level.
func BenchmarkNamedDeep(b *testing.B) {
dir := tmpData(b, "addr", `{"format":"{p as q}{q.addr.city} {q.addr.zip}","p":{"format":"{addr}","addr":{"format":"{city}","city":["Stockholm","Tranås"],"zip":"1{digits(2)} {digits(2)}"}}}`)
benchPath(b, dir, "addr")
benchPath(b, WithDataPath(dir), "addr")
}
// BenchmarkNamedWide reads ten paths through one name: the point where the maps a pick keeps
@@ -82,13 +82,13 @@ func BenchmarkNamedWide(b *testing.B) {
dir := tmpData(b, "row", `{"format":"{r as s}{s.a}{s.b}{s.c}{s.d}{s.e}{s.f}{s.g}{s.h}{s.i}{s.j}","r":[
{"format":"x","a":"1","b":"2","c":"3","d":"4","e":"5","f":"6","g":"7","h":"8","i":"9","j":"0"},
{"format":"y","a":"A","b":"B","c":"C","d":"D","e":"E","f":"F","g":"G","h":"H","i":"I","j":"J"}]}`)
benchPath(b, dir, "row")
benchPath(b, WithDataPath(dir), "row")
}
func BenchmarkFirstFake(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
f, err := New(WithSeed(1))
f, err := New(withShipped(), WithSeed(1))
if err != nil {
b.Fatal(err)
}
@@ -101,7 +101,7 @@ func BenchmarkFirstFake(b *testing.B) {
func BenchmarkNew(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := New(WithSeed(1)); err != nil {
if _, err := New(withShipped(), WithSeed(1)); err != nil {
b.Fatal(err)
}
}
+3 -3
View File
@@ -303,14 +303,14 @@ func TestCalcRefusesABadOperand(t *testing.T) {
"zero": `"0"`,
"card": card,
})
if _, err := New(WithDataPath(dir), WithSeed(1)); err == nil || !strings.Contains(err.Error(), want) {
if _, err := New(withShipped(), WithDataPath(dir), WithSeed(1)); err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("New with card %s = %v, want an error containing %q", card, err, want)
}
}
}
func TestATypedColumnRefusesACalcOverNoDigits(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
_, err := New(WithDataPath(writeData(t, map[string]string{
"row": `{"format":"","x":{"format":"{calc(d + 1)}","d":"{digits(0)}","datatype":"integer"}}`,
})))
if err == nil || !strings.Contains(err.Error(), "{digits(0)} prints nothing, which is no number") {
@@ -338,7 +338,7 @@ func TestATypedColumnProvesACalcOverAName(t *testing.T) {
"word": `["1","x"]`,
"order": `{"format":"{/word as w}{w}","double":{"format":"{calc(w * 2)}","datatype":"integer"}}`,
})
if _, err := New(WithDataPath(bad), WithSeed(1)); err == nil || !strings.Contains(err.Error(), `operand "w": "x" is not a number`) {
if _, err := New(withShipped(), WithDataPath(bad), WithSeed(1)); err == nil || !strings.Contains(err.Error(), `operand "w": "x" is not a number`) {
t.Fatalf("New = %v, want the typed column refused over an operand that is not always a number", err)
}
}
+3 -3
View File
@@ -13,9 +13,9 @@ import (
)
const (
svSE = "../../data/sv_SE"
enUS = "../../data/en_US"
misc = "../../data/misc"
svSE = "../../data/sv_SE/sv_SE"
enUS = "../../data/en_US/en_US"
misc = "../../data/misc/misc"
)
func runOut(stdin string, args ...string) (int, string, string) {
+22
View File
@@ -0,0 +1,22 @@
package en_US_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/en_US"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(en_US.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(en_US.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
if len(f.List()) == 0 {
t.Error("List() is empty, want the module's categories")
}
}
+22
View File
@@ -0,0 +1,22 @@
package SE_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/geo/SE"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(SE.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(SE.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
if len(f.List()) == 0 {
t.Error("List() is empty, want the module's categories")
}
}
+22
View File
@@ -0,0 +1,22 @@
package US_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/geo/US"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(US.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(US.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
if len(f.List()) == 0 {
t.Error("List() is empty, want the module's categories")
}
}
+22
View File
@@ -0,0 +1,22 @@
package misc_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/misc"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(misc.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(misc.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
if len(f.List()) == 0 {
t.Error("List() is empty, want the module's categories")
}
}
+22
View File
@@ -0,0 +1,22 @@
package sv_SE_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/sv_SE"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(sv_SE.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(sv_SE.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
if len(f.List()) == 0 {
t.Error("List() is empty, want the module's categories")
}
}
+19 -19
View File
@@ -50,7 +50,7 @@ func TestShippedDataCategories(t *testing.T) {
regexp.MustCompile(`^\d{1,3}( \d{3})?(,\d{2})? kr$`)},
}
f := newGenerator(t, "data", WithSeed(1))
f := shipped(t, WithSeed(1))
for _, c := range cases {
for i := 0; i < 200; i++ {
if v := fake(t, f, "en_US."+c.path); !c.en.MatchString(v) {
@@ -67,7 +67,7 @@ func TestShippedDataCategories(t *testing.T) {
// v4 UUID (version/variant nibbles fixed), a MAC address, and credit-card numbers
// whose trailing {luhn()} check passes.
func TestShippedMiscCategories(t *testing.T) {
f := newGenerator(t, "data/misc", WithSeed(1))
f := newGenerator(t, "data/misc/misc", WithSeed(1))
v4 := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`)
mac := regexp.MustCompile(`^([0-9a-f]{2}:){5}[0-9a-f]{2}$`)
objectid := regexp.MustCompile(`^[0-9a-f]{24}$`)
@@ -95,7 +95,7 @@ func TestShippedMiscCategories(t *testing.T) {
// codes follow their standard shape, dotted sub-paths resolve, emoji is a real
// glyph, and a coordinate parses to a point within geographic bounds.
func TestShippedMiscReferenceData(t *testing.T) {
f := newGenerator(t, "data/misc", WithSeed(1))
f := newGenerator(t, "data/misc/misc", WithSeed(1))
re := map[string]*regexp.Regexp{
"currency": regexp.MustCompile(`^[A-Z]{3}$`),
"currency.name": regexp.MustCompile(`\p{L}`),
@@ -158,7 +158,7 @@ func TestShippedMiscReferenceData(t *testing.T) {
// TestSwedishPersonNamesHaveNoTripleLetter pins an orthographic rule the shape
// regexes miss: no generated name repeats a character three times over.
func TestSwedishPersonNamesHaveNoTripleLetter(t *testing.T) {
f := newGenerator(t, "data", WithSeed(11))
f := shipped(t, WithSeed(11))
for _, path := range []string{"sv_SE.person", "sv_SE.person.last"} {
for i := 0; i < 20000; i++ {
name := fake(t, f, path)
@@ -177,7 +177,7 @@ func TestSwedishPersonNamesHaveNoTripleLetter(t *testing.T) {
// male), the trailing digit is a Luhn checksum over the other nine, and a
// samordningsnummer is the same with 60 added to the day.
func TestSwedishPersonnummer(t *testing.T) {
sv := newGenerator(t, "data", WithSeed(1))
sv := shipped(t, WithSeed(1))
re := regexp.MustCompile(`^\d{6}-23[89]\d$`)
sawLongMonthEnd, birth := false, map[string]bool{}
for i := 0; i < 2000; i++ {
@@ -216,7 +216,7 @@ func TestSwedishPersonnummer(t *testing.T) {
// 666, its group 01-99 and its serial 0001-9999; an ITIN is 9XX-GG-XXXX with GG
// in 50-65, 70-88, 90-92 or 94-99.
func TestShippedUSTaxIds(t *testing.T) {
f := newGenerator(t, "data", WithSeed(5))
f := shipped(t, WithSeed(5))
ssn := regexp.MustCompile(`^(\d{3})-(\d{2})-(\d{4})$`)
itin := regexp.MustCompile(`^9\d{2}-(5\d|6[0-5]|7\d|8[0-8]|9[0-24-9])-\d{4}$`)
for i := 0; i < 2000; i++ {
@@ -240,7 +240,7 @@ func TestShippedUSTaxIds(t *testing.T) {
// TestShippedPersonNames pins the name tables: a sex pins its first names, a name
// both sexes carry appears under both, and the record's sex agrees with its name.
func TestShippedPersonNames(t *testing.T) {
f := newGenerator(t, "data", WithSeed(9))
f := shipped(t, WithSeed(9))
for path, want := range map[string]string{
"sv_SE.sex[f]": "kvinna",
"sv_SE.sex[man].code": "m",
@@ -281,7 +281,7 @@ func TestShippedPersonNames(t *testing.T) {
// TestShippedPersonIsOneNamedPick pins that a person's first name, sex and title come
// from one pick, and that two names bound to one person category are two people.
func TestShippedPersonIsOneNamedPick(t *testing.T) {
f := newGenerator(t, "data", WithSeed(9))
f := shipped(t, WithSeed(9))
for _, locale := range []string{"sv_SE", "en_US"} {
apart := false
for i := 0; i < 300; i++ {
@@ -302,7 +302,7 @@ func TestShippedPersonIsOneNamedPick(t *testing.T) {
}
func TestShippedSwedishPhone(t *testing.T) {
f := newGenerator(t, "data", WithSeed(11))
f := shipped(t, WithSeed(11))
re := regexp.MustCompile(`^0\d{1,2}-\d{3} \d{2} \d{2}$`)
for i := 0; i < 50; i++ {
if n := fake(t, f, "sv_SE.phone"); !re.MatchString(n) {
@@ -312,7 +312,7 @@ func TestShippedSwedishPhone(t *testing.T) {
}
func TestShippedSwedishAddress(t *testing.T) {
f := newGenerator(t, "data", WithSeed(3))
f := shipped(t, WithSeed(3))
digit := regexp.MustCompile(`\d`)
for i := 0; i < 30; i++ {
a := fake(t, f, "sv_SE.address")
@@ -333,7 +333,7 @@ func TestShippedSwedishAddress(t *testing.T) {
// locality and region are one place in the geo tables, read through a name in a template or
// across a struct's tags, and that a reference beside the name is another address.
func TestShippedAddressIsOneNamedPick(t *testing.T) {
f := newGenerator(t, "data", WithSeed(3))
f := shipped(t, WithSeed(3))
for _, c := range []struct{ locale, country, template, largest string }{
{"sv_SE", "SE", "{a.street}|{a.postal-code}|{a.locality}|", "Stockholm"},
{"en_US", "US", "{a.street}|{a.postal-code}|{a.locality}|{a.region}", "New York"},
@@ -379,7 +379,7 @@ func TestShippedAddressIsOneNamedPick(t *testing.T) {
// one locality holds, the region empty for SE, whose address carries none.
func geoPlaces(t *testing.T, country string) map[string]bool {
rows := func(table string) [][]string {
b, err := os.ReadFile("data/geo/" + country + "/" + table + ".tsv")
b, err := os.ReadFile("data/geo/" + country + "/geo/" + country + "/" + table + ".tsv")
if err != nil {
t.Fatal(err)
}
@@ -417,7 +417,7 @@ func geoPlaces(t *testing.T, country string) map[string]bool {
}
func TestShippedPersonHasParts(t *testing.T) {
f := newGenerator(t, "data", WithSeed(7))
f := shipped(t, WithSeed(7))
for _, path := range []string{"sv_SE.person", "en_US.person"} {
for i := 0; i < 30; i++ {
if name := fake(t, f, path); len(name) < 3 || !regexp.MustCompile(`\S \S`).MatchString(name) {
@@ -429,7 +429,7 @@ func TestShippedPersonHasParts(t *testing.T) {
// NANPA's central office code records mark N11, 555, 950, 958, 959 and 976 unassignable.
func TestShippedUSPhone(t *testing.T) {
f := newGenerator(t, "data", WithSeed(11))
f := shipped(t, WithSeed(11))
re := regexp.MustCompile(`^(?:\(\d{3}\) |\d{3}-)([2-9]\d{2})-\d{4}$`)
unassignable := map[string]bool{"555": true, "950": true, "958": true, "959": true, "976": true}
for i := 0; i < 2000; i++ {
@@ -470,7 +470,7 @@ func TestShippedTableCellsAreTrimmed(t *testing.T) {
// TestShippedNamespacedTree loads the whole data/ tree (not a single locale) and
// reaches each locale through its folder segment: data/sv_SE/person -> sv_SE.person.
func TestShippedNamespacedTree(t *testing.T) {
f := newGenerator(t, "data", WithSeed(1))
f := shipped(t, WithSeed(1))
for _, path := range []string{"sv_SE.person", "en_US.person", "sv_SE.address.locality"} {
if got := fake(t, f, path); got == "" {
t.Fatalf("Fake(%q) returned empty", path)
@@ -514,7 +514,7 @@ func luhnValid(s string) bool {
var swedishName = regexp.MustCompile(`^\p{L}+([ -]\p{L}+)*$`)
func TestShippedStreetIsARegisteredName(t *testing.T) {
f := newGenerator(t, "data", WithSeed(5))
f := shipped(t, WithSeed(5))
street := regexp.MustCompile(`^\p{L}[\p{L}\d:.-]*([ -][\p{L}\d:.-]+)*$`)
for i := 0; i < 300; i++ {
if s := fake(t, f, "sv_SE.address.street"); !street.MatchString(s) {
@@ -526,7 +526,7 @@ func TestShippedStreetIsARegisteredName(t *testing.T) {
func TestShippedLastNameComposition(t *testing.T) {
// last is a choice of patronymic {first}sson templates, compound
// {first}{last} templates and literal surnames.
f := newGenerator(t, "data", WithSeed(6))
f := shipped(t, WithSeed(6))
for i := 0; i < 300; i++ {
if s := fake(t, f, "sv_SE.person.last"); !swedishName.MatchString(s) {
t.Fatalf("last name %q is not a Swedish surname", s)
@@ -536,7 +536,7 @@ func TestShippedLastNameComposition(t *testing.T) {
func TestShippedStreetNumberFormats(t *testing.T) {
// Reachable via a hyphenated path; covers all five weighted number variants.
f := newGenerator(t, "data", WithSeed(8))
f := shipped(t, WithSeed(8))
re := regexp.MustCompile(`^[1-9]\d{0,2}[A-Z]?$`)
for i := 0; i < 300; i++ {
if n := fake(t, f, "sv_SE.address.street-number"); !re.MatchString(n) {
@@ -548,7 +548,7 @@ func TestShippedStreetNumberFormats(t *testing.T) {
// TestShippedUSTitleAgreesWithSex pins that one person's title and sex agree, and
// that the everyday titles are reachable.
func TestShippedUSTitleAgreesWithSex(t *testing.T) {
f := newGenerator(t, "data", WithSeed(4))
f := shipped(t, WithSeed(4))
female := map[string]bool{"Miss": true, "Mrs": true, "Ms": true}
seen := map[string]bool{}
for i := 0; i < 3000; i++ {
+4 -4
View File
@@ -88,7 +88,7 @@ func TestDatatypeRejectsAValueItsTypeRejects(t *testing.T) {
for name, body := range tree {
files[name] = body
}
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, files)))
_, err := New(WithDataPath(writeData(t, files)))
if err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%s: New = %v, want an error containing %q", c.name, err, c.want)
}
@@ -102,14 +102,14 @@ func TestDatatypeRejectsAValueItsTypeRejects(t *testing.T) {
`[{"format":"1","datatype":"integer"},{"format":"1.5","datatype":"number"}]`: "its items hold integer and number; a column holds one datatype",
} {
row := `{"format":"","col":` + column + `}`
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"row": row}))); err == nil || !strings.HasSuffix(err.Error(), want) {
if _, err := New(WithDataPath(writeData(t, map[string]string{"row": row}))); err == nil || !strings.HasSuffix(err.Error(), want) {
t.Errorf("items of two declared datatypes %s: New = %v, want an error ending %q", column, err, want)
}
if _, err := newGenerator(t, writeData(t, tree)).NewTemplate(row); err == nil || !strings.HasSuffix(err.Error(), want) {
t.Errorf("items of two declared datatypes %s: NewTemplate = %v, want an error ending %q", column, err, want)
}
}
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
_, err := New(WithDataPath(writeData(t, map[string]string{
"a": `{"format":"","c":["{/b.x}",{"format":"1","datatype":"integer"}]}`,
"b": `{"format":"","x":["2",{"format":"1","datatype":"integer"}]}`,
})))
@@ -148,7 +148,7 @@ func TestDatatypeAcceptsAColumnThatAlwaysParses(t *testing.T) {
} {
row := `{"format":"","col":` + column + `}`
src := `{"format":"","code":["200","404"],"n":{"format":"{int(1,9)}","datatype":"integer"}}`
f, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"cat": cat, "row": row, "src": src})), WithSeed(1))
f, err := New(WithDataPath(writeData(t, map[string]string{"cat": cat, "row": row, "src": src})), WithSeed(1))
if err != nil {
t.Errorf("%s: New = %v, want it loaded", column, err)
continue
+1 -1
View File
@@ -265,7 +265,7 @@ func TestGoExamplesPrintWhatTheyRender(t *testing.T) {
if lines == nil {
t.Errorf("%s prints no seeded Fake, so nothing holds its example to a render", name)
}
f, err := New(WithSeed(42))
f, err := New(withShipped(), WithSeed(42))
if err != nil {
t.Fatal(err)
}
+9 -6
View File
@@ -7,10 +7,13 @@ import (
"testing"
"testing/fstest"
"github.com/larvit/fejkdata/data"
"github.com/larvit/fejkdata/internal/drawstate"
"github.com/larvit/fejkdata/internal/jsonvalue"
)
func withShipped() Option { return WithDataFS(data.Modules()...) }
// newGenerator creates a generator over a single data directory, failing on
// error. Most tests load one dir; newGeneratorN loads several (last-loaded wins
// on conflicts).
@@ -21,7 +24,7 @@ func newGenerator(t *testing.T, dir string, opts ...Option) *Generator {
func newGeneratorN(t *testing.T, dirs []string, opts ...Option) *Generator {
t.Helper()
all := []Option{WithoutShippedData()}
var all []Option
for _, dir := range dirs {
all = append(all, WithDataPath(dir))
}
@@ -43,14 +46,14 @@ func fake(t *testing.T, f *Generator, path string) string {
}
func TestNewMissingDirectory(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath("data/de_DE"))
_, err := New(WithDataPath("data/de_DE"))
if err == nil || !strings.Contains(err.Error(), "de_DE") || !errors.Is(err, fs.ErrNotExist) {
t.Fatalf("New(missing) error = %v, want the real error, naming the path", err)
}
}
func TestNewRejectsAnEmptyDataPath(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath(""))
_, err := New(WithDataPath(""))
if err == nil || !strings.Contains(err.Error(), "empty") {
t.Fatalf("New(WithDataPath(\"\")) = %v, want the empty path named", err)
}
@@ -60,14 +63,14 @@ func TestNewReportsAnEntropyFailure(t *testing.T) {
saved := randomBytes
randomBytes = func([]byte) (int, error) { return 0, errors.New("no entropy") }
defer func() { randomBytes = saved }()
_, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{"w.json": {Data: []byte(`"x"`)}}))
_, err := New(WithDataFS(fstest.MapFS{"w.json": {Data: []byte(`"x"`)}}))
if err == nil || !strings.Contains(err.Error(), "no entropy") {
t.Fatalf("New() without entropy = %v, want the failure reported", err)
}
}
func TestWithSeedIsDeterministic(t *testing.T) {
a, b := newGenerator(t, "data", WithSeed(42)), newGenerator(t, "data", WithSeed(42))
a, b := shipped(t, WithSeed(42)), shipped(t, WithSeed(42))
for i := 0; i < 50; i++ {
if x, y := fake(t, a, "sv_SE.person"), fake(t, b, "sv_SE.person"); x != y {
t.Fatalf("same seed diverged at %d: %q != %q", i, x, y)
@@ -76,7 +79,7 @@ func TestWithSeedIsDeterministic(t *testing.T) {
}
func TestDifferentSeedsDiffer(t *testing.T) {
a, b := newGenerator(t, "data", WithSeed(1)), newGenerator(t, "data", WithSeed(2))
a, b := shipped(t, WithSeed(1)), shipped(t, WithSeed(2))
for i := 0; i < 50; i++ {
if fake(t, a, "en_US.person") != fake(t, b, "en_US.person") {
return
+13 -1
View File
@@ -30,7 +30,13 @@ func TestPackageImports(t *testing.T) {
checked := 0
allowed := map[string][]string{
".": {"internal/builtinfunc", "internal/datafiles", "internal/datatype", "internal/drawstate", "internal/grammar", "internal/invariant", "internal/jsonvalue", "internal/proven", "internal/rows"},
"cmd/fejkdata": {"."},
"cmd/fejkdata": {".", "data"},
"data": {"data/en_US", "data/geo/SE", "data/geo/US", "data/misc", "data/sv_SE"},
"data/en_US": nil,
"data/geo/SE": nil,
"data/geo/US": nil,
"data/misc": nil,
"data/sv_SE": nil,
"internal/builtinfunc": {"internal/datatype", "internal/drawstate", "internal/grammar", "internal/invariant", "internal/proven"},
"internal/datafiles": {"internal/grammar", "internal/jsonvalue"},
"internal/datatype": nil,
@@ -42,7 +48,13 @@ func TestPackageImports(t *testing.T) {
"internal/rows": {"internal/drawstate", "internal/grammar", "internal/invariant"},
}
testsAlso := map[string][]string{
".": {"data", "data/sv_SE"},
"cmd/fejkdata": {"internal/grammar"},
"data/en_US": {"."},
"data/geo/SE": {"."},
"data/geo/US": {"."},
"data/misc": {"."},
"data/sv_SE": {"."},
}
seen := map[string]bool{}
eachPackage(t, func(dir string, pkg *build.Package) {
+1 -1
View File
@@ -16,7 +16,7 @@ func tmpl(t *testing.T, f *Generator, input string) string {
func shipped(t *testing.T, opts ...Option) *Generator {
t.Helper()
f, err := New(append([]Option{WithSeed(1)}, opts...)...)
f, err := New(append([]Option{withShipped(), WithSeed(1)}, opts...)...)
if err != nil {
t.Fatal(err)
}
+7 -7
View File
@@ -139,7 +139,7 @@ func TestMultiPathMergesFolders(t *testing.T) {
// tree: everything List advertises renders, every time, and the sub-fields the
// README advertises are discoverable.
func TestListedPathsAllRender(t *testing.T) {
f := newGenerator(t, "data", WithSeed(4))
f := shipped(t, WithSeed(4))
paths := f.List()
for _, p := range paths {
for i := 0; i < 20; i++ {
@@ -187,12 +187,12 @@ func TestRepeatProductAlongAPathIsCapped(t *testing.T) {
"b": `{"format":"x","repeat":2048}`,
},
} {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, files)))
_, err := New(WithDataPath(writeData(t, files)))
if err == nil || !strings.Contains(err.Error(), "repeat") || !strings.Contains(err.Error(), "1048576") {
t.Errorf("%s: New = %v, want the repeat product rejected naming the maximum", name, err)
}
}
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
if _, err := New(WithDataPath(writeData(t, map[string]string{
"cat": `{"format":"{a}{c}","repeat":1024,"a":{"format":"{b}","repeat":1024,"b":"x"},"c":{"format":"y","repeat":1024}}`,
}))); err != nil {
t.Errorf("New = %v, want 1024 x 1024 along one path accepted", err)
@@ -205,11 +205,11 @@ func TestNewErrors(t *testing.T) {
if err := os.WriteFile(file, []byte("{}"), 0o644); err != nil {
t.Fatal(err)
}
if _, err := New(WithoutShippedData(), WithDataPath(file)); err == nil || !strings.Contains(err.Error(), "xx_XX is not a directory") {
if _, err := New(WithDataPath(file)); err == nil || !strings.Contains(err.Error(), "xx_XX is not a directory") {
t.Errorf("New(file) = %v, want not-a-directory error", err)
}
// Invalid JSON in a category file fails.
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"broken": `{ not json`}))); err == nil {
if _, err := New(WithDataPath(writeData(t, map[string]string{"broken": `{ not json`}))); err == nil {
t.Error("New(invalid JSON) = nil error")
}
// An option that cannot take effect, and a category or folder no dot path can
@@ -300,7 +300,7 @@ func TestNewErrors(t *testing.T) {
},
}
for name, c := range rejected {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, c.files)))
_, err := New(WithDataPath(writeData(t, c.files)))
if err == nil {
t.Errorf("%s: New = nil error, want it rejected at load", name)
continue
@@ -328,7 +328,7 @@ func TestNewErrors(t *testing.T) {
"repeated reference arm": {map[string]string{"a": `"x"`, "b": `"{/a|/a}"`}, "b", "x"},
}
for name, c := range accepted {
f, err := New(WithoutShippedData(), WithDataPath(writeData(t, c.files)))
f, err := New(WithDataPath(writeData(t, c.files)))
if err != nil {
t.Errorf("%s: New = %v, want it accepted", name, err)
continue
+2 -2
View File
@@ -253,7 +253,7 @@ func TestNameErrors(t *testing.T) {
}
files[name] = body
}
_, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, files)))
_, err := New(WithDataPath(writeFiles(t, files)))
if c.want == "" {
if err != nil {
t.Fatalf("New() = %v, want it to load", err)
@@ -463,7 +463,7 @@ func TestARowThroughANameHintsAPathThatLoads(t *testing.T) {
files := nameTables()
files["card.json"] = `{"format":"{x as r}{r[01]}","x":"{/region}"}`
want := `a path through name "r" may not select a row; bind the row to a name of its own`
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, files))); err == nil || !strings.Contains(err.Error(), want) {
if _, err := New(WithDataPath(writeFiles(t, files))); err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("New(a field-bound name selecting a row) = %v, want %s", err, want)
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ func TestAnOptionOfTheWrongKindIsNamedByItsJSONKind(t *testing.T) {
t.Errorf("compile(%s) = %v, want %q", src, err, want)
}
}
_, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":5}`, "t.tsv": "a\nx\n"})))
_, err := New(WithDataPath(writeFiles(t, map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":5}`, "t.tsv": "a\nx\n"})))
if err == nil || !strings.Contains(err.Error(), "key must be a string, not a number") {
t.Errorf("key 5 = %v, want the kind named", err)
}
+2 -2
View File
@@ -130,7 +130,7 @@ func TestPathKeyIsUnambiguous(t *testing.T) {
dir := writeData(t, map[string]string{
"cat": `[{"format":"{a.b}","a.b":"1"},{"format":"{a}","a":{"format":"{b}","b":"2"}}]`,
})
_, err := New(WithoutShippedData(), WithDataPath(dir))
_, err := New(WithDataPath(dir))
if err == nil || !strings.Contains(err.Error(), `field "a.b" contains "."`) {
t.Fatalf("New = %v, want the dotted field name rejected", err)
}
@@ -150,7 +150,7 @@ func TestPathKeyIsUnambiguous(t *testing.T) {
// single-variant choice always draws the same item, so it needs no every-variant
// guard and the error can name the field that is missing.
func TestMissingFieldNamesItself(t *testing.T) {
f := newGenerator(t, "data", WithSeed(1))
f := shipped(t, WithSeed(1))
_, err := f.Fake("sv_SE.person.typo")
if err == nil || !strings.Contains(err.Error(), `no field "typo"`) {
t.Errorf("Fake(person.typo) = %v, want it to name the missing field", err)
+17 -13
View File
@@ -44,7 +44,7 @@ func TestNoRenderAllocRegression(t *testing.T) {
{"format":"y","a":"A","b":"B","c":"C","d":"D","e":"E","f":"F","g":"G","h":"H","i":"I","j":"J"}]}`, 3},
}
for _, s := range shapes {
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{"x.json": {Data: []byte(s.json)}}))
f, err := New(WithDataFS(fstest.MapFS{"x.json": {Data: []byte(s.json)}}))
if err != nil {
t.Fatalf("New(%s): %v", s.name, err)
}
@@ -66,7 +66,7 @@ func TestNoReferenceAllocRegression(t *testing.T) {
{"a repeat of a name", `{"format":"{r}","r":{"format":"{/word as n}{n.w}{n.w}","repeat":20,"separator":", "}}`, 162, 1.10},
{"a read two levels under a pick", `{"format":"{place.geo as g}{g} {g.town.zip}","place":{"format":"{geo}","geo":{"format":"{town.zip}","town":{"format":"{zip}","zip":"1"}}}}`, 12, 1},
} {
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{
f, err := New(WithDataFS(fstest.MapFS{
"word.json": {Data: []byte(word)},
"x.json": {Data: []byte(s.json)},
}))
@@ -81,7 +81,7 @@ func TestNoReferenceAllocRegression(t *testing.T) {
// A table read pins a row inline and reads its cells in place.
func TestNoTableAllocRegression(t *testing.T) {
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{
f, err := New(WithDataFS(fstest.MapFS{
"region.json": {Data: []byte(`{"format":"{name}","rows":"region.tsv","key":"code","weight":"population"}`)},
"region.tsv": {Data: []byte("code\tname\tpopulation\n01\tStockholms län\t2400000\n12\tSkåne län\t1400000\n14\tVästra Götalands län\t1750000\n")},
"x.json": {Data: []byte(`"{/region.name}, {/region.code}"`)},
@@ -113,7 +113,7 @@ func TestNoTableAllocRegression(t *testing.T) {
}
deep[name+".json"], deep[name+".tsv"] = &fstest.MapFile{Data: []byte(category)}, &fstest.MapFile{Data: []byte(rows)}
}
f, err = New(WithoutShippedData(), WithDataFS(deep))
f, err = New(WithDataFS(deep))
if err != nil {
t.Fatal(err)
}
@@ -129,7 +129,7 @@ func TestNoRecordAllocRegression(t *testing.T) {
{"record 3 columns", `{"format":"","a":"x","b":"y","c":"z"}`},
{"record 50 columns", wideTokenJSON(50)},
} {
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{"x.json": {Data: []byte(s.json)}}))
f, err := New(WithDataFS(fstest.MapFS{"x.json": {Data: []byte(s.json)}}))
if err != nil {
t.Fatalf("New(%s): %v", s.name, err)
}
@@ -148,7 +148,7 @@ func TestNoRecordAllocRegression(t *testing.T) {
}
func TestNoStructAllocRegression(t *testing.T) {
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{"x.json": {Data: []byte(`{"format":"","a":"x","b":"y","c":"z"}`)}}))
f, err := New(WithDataFS(fstest.MapFS{"x.json": {Data: []byte(`{"format":"","a":"x","b":"y","c":"z"}`)}}))
if err != nil {
t.Fatal(err)
}
@@ -166,20 +166,24 @@ func TestNoStructAllocRegression(t *testing.T) {
}
}
func BenchmarkNestedDepth25(b *testing.B) { benchPath(b, tmpData(b, "deep", nestedJSON(25)), "deep") }
func BenchmarkNestedDepth100(b *testing.B) { benchPath(b, tmpData(b, "deep", nestedJSON(100)), "deep") }
func BenchmarkNestedDepth25(b *testing.B) {
benchPath(b, WithDataPath(tmpData(b, "deep", nestedJSON(25))), "deep")
}
func BenchmarkNestedDepth100(b *testing.B) {
benchPath(b, WithDataPath(tmpData(b, "deep", nestedJSON(100))), "deep")
}
func BenchmarkWideTokens100(b *testing.B) {
benchPath(b, tmpData(b, "wide", wideTokenJSON(100)), "wide")
benchPath(b, WithDataPath(tmpData(b, "wide", wideTokenJSON(100))), "wide")
}
func BenchmarkWideTokens500(b *testing.B) {
benchPath(b, tmpData(b, "wide", wideTokenJSON(500)), "wide")
benchPath(b, WithDataPath(tmpData(b, "wide", wideTokenJSON(500))), "wide")
}
// New decodes the shipped manifest and loads nothing else.
// New decodes the shipped modules' manifests and loads nothing else.
func TestNoNewAllocRegression(t *testing.T) {
const base = 2449.0
const base = 2864.0
allocs := testing.AllocsPerRun(100, func() {
if _, err := New(WithSeed(1)); err != nil {
if _, err := New(withShipped(), WithSeed(1)); err != nil {
t.Fatal(err)
}
})
+10 -10
View File
@@ -152,7 +152,7 @@ func TestANameReadOnceIsWhatItBinds(t *testing.T) {
func TestCycleReachedOnlyByAPathTokenIsRejected(t *testing.T) {
// A path token renders what it lands on, not the level it started from, so the
// cycle walk has to follow it there.
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
_, err := New(WithDataPath(writeData(t, map[string]string{
"a": `{"format":"{p.x}","p":{"format":"static","x":"{/b}"}}`,
"b": `"{/a}"`,
})))
@@ -162,7 +162,7 @@ func TestCycleReachedOnlyByAPathTokenIsRejected(t *testing.T) {
}
func TestACycleInALaterVariantIsRejected(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
_, err := New(WithDataPath(writeData(t, map[string]string{
"cat": `{"format":"{p.x}","p":[{"format":"h","x":"safe"},{"format":"h","x":"{/hop}"}]}`,
"hop": `"{/cat}"`,
})))
@@ -172,7 +172,7 @@ func TestACycleInALaterVariantIsRejected(t *testing.T) {
}
func TestCycleThroughAPathTokenIsRejected(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
_, err := New(WithDataPath(writeData(t, map[string]string{
"a": `{"format":"{p.x}","p":{"format":"{x}","x":"{/b}"}}`,
"b": `"{/a}"`,
})))
@@ -190,7 +190,7 @@ func TestADeepDiamondChainLoads(t *testing.T) {
`{"format":"{a}{b}","a":"{/l%d}","b":"{/l%d}"}`, i-1, i-1)
}
files["thing"] = `{"format":"{p.first} {/l30}","p":{"format":"x","first":["A","B"]}}`
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, files))); err != nil {
if _, err := New(WithDataPath(writeData(t, files))); err != nil {
t.Fatalf("New = %v, want a deep diamond chain to load", err)
}
}
@@ -208,7 +208,7 @@ func TestNestedChoiceDrawsOneVariant(t *testing.T) {
}
func TestRepeatingLevelIsNamedInTheError(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
_, err := New(WithDataPath(writeData(t, map[string]string{
"cat": `{"format":"[{p.a.b}]","p":{"format":"{a}","a":{"format":"{b}","repeat":3,"separator":",","b":"z"}}}`,
})))
if err == nil || !strings.Contains(err.Error(), `"p.a"`) {
@@ -223,7 +223,7 @@ func TestPathIntoARepeatingLevelIsRejected(t *testing.T) {
`{"format":"[{p.a}]","p":{"format":"{a}","repeat":3,"separator":",","a":"z"}}`,
`{"format":"[{p.a}]","p":[{"format":"{a}","repeat":3,"separator":",","a":"x"},{"format":"{a}","a":"y"}]}`,
} {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"cat": cat})))
_, err := New(WithDataPath(writeData(t, map[string]string{"cat": cat})))
if err == nil || !strings.Contains(err.Error(), "repeat") {
t.Errorf("New(%s) = %v, want a path into a repeating level rejected", cat, err)
}
@@ -231,7 +231,7 @@ func TestPathIntoARepeatingLevelIsRejected(t *testing.T) {
}
func TestPathIntoAPlainTemplateNamesTheMissingField(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
_, err := New(WithDataPath(writeData(t, map[string]string{
"cat": `{"format":"{a.nope}","a":{"format":"x","b":"1"}}`,
})))
if err == nil || !strings.Contains(err.Error(), `no field "nope"`) {
@@ -273,7 +273,7 @@ func TestDottedTokenErrors(t *testing.T) {
},
}
for name, c := range rejected {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"cat": c.file})))
_, err := New(WithDataPath(writeData(t, map[string]string{"cat": c.file})))
if err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%s: New = %v, want it to mention %q", name, err, c.want)
}
@@ -285,7 +285,7 @@ func TestEmptyPathSegmentIsRejected(t *testing.T) {
"trailing dot": `{"format":"[{a.}]","a":"x"}`,
"triple dot": `{"format":"[{a...b}]","a":"x"}`,
} {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"cat": file})))
_, err := New(WithDataPath(writeData(t, map[string]string{"cat": file})))
if err == nil || !strings.Contains(err.Error(), "empty segment") {
t.Errorf("%s: New = %v, want it to name the empty segment", name, err)
}
@@ -294,7 +294,7 @@ func TestEmptyPathSegmentIsRejected(t *testing.T) {
func TestASubFieldIsReachableByFake(t *testing.T) {
dir := writeData(t, map[string]string{"address": places("{place as p}{p.postal-code} {p.locality}")})
f, err := New(WithoutShippedData(), WithDataPath(dir), WithSeed(9))
f, err := New(WithDataPath(dir), WithSeed(9))
if err != nil {
t.Fatalf("New = %v", err)
}
+5 -5
View File
@@ -65,7 +65,7 @@ func TestReadmeExamplesLoadAndRender(t *testing.T) {
if strings.Contains(body, "…") {
continue
}
f, err := New(WithDataPath(writeFiles(t, exampleFiles(t, src, at[0], body))), WithSeed(1))
f, err := New(withShipped(), WithDataPath(writeFiles(t, exampleFiles(t, src, at[0], body))), WithSeed(1))
if err != nil {
t.Errorf("README example does not load: %v\n%s", err, body)
continue
@@ -91,7 +91,7 @@ func TestReadmeRecordExample(t *testing.T) {
if block == nil {
t.Fatal("README lost the Records example")
}
f, err := New(WithDataPath(writeData(t, map[string]string{"users": block[1]})), WithSeed(1))
f, err := New(withShipped(), WithDataPath(writeData(t, map[string]string{"users": block[1]})), WithSeed(1))
if err != nil {
t.Fatal(err)
}
@@ -119,7 +119,7 @@ func TestReadmeSQLExampleOutput(t *testing.T) {
if block == nil || want == nil {
t.Fatal("README lost the SQL example or its printed output")
}
f, err := New(WithDataPath(writeData(t, map[string]string{"sql": block[1]})), WithSeed(1))
f, err := New(withShipped(), WithDataPath(writeData(t, map[string]string{"sql": block[1]})), WithSeed(1))
if err != nil {
t.Fatal(err)
}
@@ -135,7 +135,7 @@ func TestReadmeDatatypeExample(t *testing.T) {
t.Fatal("README lost the Datatype section")
}
block := jsonBlock.FindStringSubmatch(src[i:])
f, err := New(WithDataPath(writeData(t, map[string]string{"order": block[1]})), WithSeed(1))
f, err := New(withShipped(), WithDataPath(writeData(t, map[string]string{"order": block[1]})), WithSeed(1))
if err != nil {
t.Fatal(err)
}
@@ -179,7 +179,7 @@ func TestReadmeTableExample(t *testing.T) {
t.Fatal("README Table section has no json block")
}
body := src[i:][at[2]:at[3]]
f, err := New(WithDataPath(writeFiles(t, exampleFiles(t, src, i+at[0], body))), WithSeed(1))
f, err := New(withShipped(), WithDataPath(writeFiles(t, exampleFiles(t, src, i+at[0], body))), WithSeed(1))
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -530,7 +530,7 @@ func TestRecordTemplateRejectsATopLevelRepeat(t *testing.T) {
// command a fixture author types: a category of one value has no columns, and the
// error names the record that gives it one.
func TestRecordOfAFieldlessCategoryNamesTheWrapper(t *testing.T) {
f := newGenerator(t, "data", WithSeed(1))
f := shipped(t, WithSeed(1))
want := `{"format":"","personnummer":"{/sv_SE.personnummer}"}`
_, err := f.FakeRecord("sv_SE.personnummer")
if err == nil || !strings.Contains(err.Error(), want) {
+11 -11
View File
@@ -107,7 +107,7 @@ func TestReferenceErrors(t *testing.T) {
"field key using the reference prefix": {"cat": `{"format":"hi","..x":"{/nope}"}`},
}
for name, files := range cases {
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, files))); err == nil {
if _, err := New(WithDataPath(writeData(t, files))); err == nil {
t.Errorf("%s: New = nil error, want a reference error", name)
}
}
@@ -142,13 +142,13 @@ func TestAReferenceIntoItsOwnCategoryIsAFreshDraw(t *testing.T) {
"the category in its format": `{"format":"{/cat}"}`,
"a field reading itself": `{"format":"{x}","x":"{/cat.x}"}`,
} {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{"cat": file})))
_, err := New(WithDataPath(writeData(t, map[string]string{"cat": file})))
if err == nil || !strings.Contains(err.Error(), "cycle") {
t.Errorf("%s: New = %v, want the cycle refused", name, err)
}
}
// Reading a sibling as a path is the spelling that stays.
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
if _, err := New(WithDataPath(writeData(t, map[string]string{
"cat": `{"format":"{y.v}","y":{"format":"{v}","v":["1","2"]}}`,
}))); err != nil {
t.Errorf("New = %v, want the sibling path accepted", err)
@@ -173,7 +173,7 @@ func TestNewErrorIsDeterministic(t *testing.T) {
dir := writeData(t, files)
var first string
for i := 0; i < 50; i++ {
_, err := New(WithoutShippedData(), WithDataPath(dir))
_, err := New(WithDataPath(dir))
if err == nil {
t.Fatalf("%s: New = nil error, want a load error", name)
}
@@ -206,11 +206,11 @@ func TestNewErrorPathIsCanonical(t *testing.T) {
{
"bad reference reached through another reference",
map[string]string{"a": `"{/b}"`, "b": `"{/nope}"`},
`fejkdata: b: reference {/nope}: no entry "nope"`,
`fejkdata: b: reference {/nope}: no entry "nope", and the module holding b names no module it reads by default`,
},
}
for _, c := range cases {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, c.files)))
_, err := New(WithDataPath(writeData(t, c.files)))
if err == nil {
t.Errorf("%s: New = nil error", c.name)
continue
@@ -222,7 +222,7 @@ func TestNewErrorPathIsCanonical(t *testing.T) {
}
func TestReferenceThroughChoiceNeedsEveryVariant(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{
_, err := New(WithDataPath(writeData(t, map[string]string{
"who": `[{"format":"{f}{h}","f":"1","h":"x"},{"format":"{g}{h}","g":"2","h":"y"}]`,
"card": `"{/who.f}"`,
})))
@@ -255,7 +255,7 @@ func TestReferencesIntoOneLevelLoad(t *testing.T) {
{"row": `{"format":"{p.first} {/hop}","p":[{"format":"{first}","first":"A","last":"1"},{"format":"{first}","first":"B","last":"2"}]}`, "hop": `"{/row.p.last}"`},
{"sv_SE/person": cat, "sv_SE/mail": `"{.person} <{/sv_SE.person.p.first}>"`},
} {
f, err := New(WithoutShippedData(), WithDataPath(writeData(t, files)), WithSeed(1))
f, err := New(WithDataPath(writeData(t, files)), WithSeed(1))
if err != nil {
t.Errorf("New(%v) = %v, want each read a draw of its own", files, err)
continue
@@ -325,7 +325,7 @@ func TestReferenceSigilErrors(t *testing.T) {
"missing sibling": {"sv_SE/a": `"{.nope}"`},
"slash in a field name": {"a": `{"format":"{x}","x":"1","a/b":"2"}`},
} {
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, files))); err == nil {
if _, err := New(WithDataPath(writeData(t, files))); err == nil {
t.Errorf("%s: New = nil error, want a reference error", name)
}
}
@@ -336,7 +336,7 @@ func TestSlashAfterAReferenceSigilIsRejected(t *testing.T) {
"after ..": {"sv_SE/person": `"Ada"`, "sv_SE/deep/a": `"{../person}"`},
"after .": {"sv_SE/person": `"Ada"`, "sv_SE/a": `"{./person}"`},
} {
_, err := New(WithoutShippedData(), WithDataPath(writeData(t, files)))
_, err := New(WithDataPath(writeData(t, files)))
if err == nil || !strings.Contains(err.Error(), "person}") || !strings.Contains(err.Error(), "write {") {
t.Errorf("%s: New = %v, want the slash rejected naming the spelling", name, err)
}
@@ -344,7 +344,7 @@ func TestSlashAfterAReferenceSigilIsRejected(t *testing.T) {
}
func TestACycleThroughASelectedRowIsRefused(t *testing.T) {
_, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, map[string]string{
_, err := New(WithDataPath(writeFiles(t, map[string]string{
"x.json": `{"format":"{v} {/y}","rows":"x.tsv","key":"code"}`,
"x.tsv": "code\tv\n1\ta\n2\tb\n",
"y.json": `"{/x[1]}"`,
+30 -10
View File
@@ -9,8 +9,16 @@ import (
"testing/fstest"
)
func TestNewDefaultsToShippedData(t *testing.T) {
f, err := New(WithSeed(1))
func TestABareNewFailsNamingWithDataFS(t *testing.T) {
for name, opts := range map[string][]Option{"no option": nil, "only a seed": {WithSeed(1)}} {
if _, err := New(opts...); err == nil || !strings.Contains(err.Error(), "WithDataFS(data.Modules()...)") {
t.Errorf("New(%s) = %v, want it refused, naming WithDataFS(data.Modules()...)", name, err)
}
}
}
func TestEveryShippedModuleLoads(t *testing.T) {
f, err := New(withShipped(), WithSeed(1))
if err != nil {
t.Fatalf("New() = %v", err)
}
@@ -27,7 +35,7 @@ func TestNewDefaultsToShippedData(t *testing.T) {
func TestWithDataPathLayersOverShipped(t *testing.T) {
dir := writeData(t, map[string]string{"sv_SE/word": `"only-mine"`, "greeting": `"hej"`})
f, err := New(WithDataPath(dir), WithSeed(1))
f, err := New(withShipped(), WithDataPath(dir), WithSeed(1))
if err != nil {
t.Fatalf("New = %v", err)
}
@@ -44,7 +52,7 @@ func TestWithDataPathLayersOverShipped(t *testing.T) {
func TestUserDataMayReferenceShipped(t *testing.T) {
dir := writeData(t, map[string]string{"greeting": `"Hej {/sv_SE.person}!"`})
f, err := New(WithDataPath(dir), WithSeed(1))
f, err := New(withShipped(), WithDataPath(dir), WithSeed(1))
if err != nil {
t.Fatalf("New = %v", err)
}
@@ -61,8 +69,8 @@ func TestAGeneratorWithDataSaysNothingOfNoData(t *testing.T) {
func TestAGeneratorWithNoDataRendersWhatReadsNone(t *testing.T) {
for name, opts := range map[string][]Option{
"no source": {WithoutShippedData()},
"empty folder": {WithoutShippedData(), WithDataPath(t.TempDir())},
"no module": {WithDataFS()},
"empty folder": {WithDataPath(t.TempDir())},
} {
f, err := New(opts...)
if err != nil {
@@ -83,7 +91,7 @@ func TestAGeneratorWithNoDataRendersWhatReadsNone(t *testing.T) {
}
}
func TestWithoutShippedDataListsOnlyOwn(t *testing.T) {
func TestADataPathAloneListsOnlyItsOwn(t *testing.T) {
dir := writeData(t, map[string]string{"greeting": `"hej"`})
f := newGenerator(t, dir, WithSeed(1))
if got := f.List(); !reflect.DeepEqual(got, []string{"greeting"}) {
@@ -98,7 +106,7 @@ func TestWithDataFS(t *testing.T) {
".hidden.json": {Data: []byte(`"ignored"`)},
"broken/no.json": {Data: []byte(`"x"`)},
}
f, err := New(WithoutShippedData(), WithDataFS(fsys), WithSeed(1))
f, err := New(WithDataFS(fsys), WithSeed(1))
if err != nil {
t.Fatalf("New(WithDataFS) = %v", err)
}
@@ -109,13 +117,25 @@ func TestWithDataFS(t *testing.T) {
t.Errorf("nested.x.y = %q, want z", got)
}
bad := fstest.MapFS{"broken.json": {Data: []byte(`{ not json`)}}
if _, err := New(WithoutShippedData(), WithDataFS(bad)); err == nil || !strings.Contains(err.Error(), "broken.json") {
if _, err := New(WithDataFS(bad)); err == nil || !strings.Contains(err.Error(), "broken.json") {
t.Errorf("New(bad fs) = %v, want an error naming the file", err)
}
}
func TestWithDataFSLayersEachModuleOverTheOneBefore(t *testing.T) {
a := fstest.MapFS{"x.json": {Data: []byte(`"a"`)}, "y.json": {Data: []byte(`"a"`)}}
b := fstest.MapFS{"x.json": {Data: []byte(`"b"`)}}
f, err := New(WithDataFS(a, b))
if err != nil {
t.Fatalf("New(WithDataFS(a, b)) = %v", err)
}
if got := fake(t, f, "x") + fake(t, f, "y"); got != "ba" {
t.Errorf("x and y = %q, want b's x and a's y", got)
}
}
func TestFakeIsSafeForConcurrentUse(t *testing.T) {
f, err := New(WithSeed(1))
f, err := New(withShipped(), WithSeed(1))
if err != nil {
t.Fatal(err)
}
+183 -49
View File
@@ -4,7 +4,9 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/fs"
"maps"
"os"
"path"
"path/filepath"
@@ -16,11 +18,11 @@ import (
"testing"
"testing/fstest"
"github.com/larvit/fejkdata/data"
"github.com/larvit/fejkdata/data/sv_SE"
"github.com/larvit/fejkdata/internal/datafiles"
)
var shippedManifestFile = path.Join("data", datafiles.ManifestFile)
// withoutManifest is fsys with its manifest hidden, so it loads whole in New.
type withoutManifest struct{ fs.FS }
@@ -31,15 +33,52 @@ func (w withoutManifest) Open(name string) (fs.File, error) {
return w.FS.Open(name)
}
// shippedModule is a module data.Modules lists, and its folder under data/, slash-separated.
type shippedModule struct {
fsys fs.FS
dir string
}
// importPath is the Go package the module's FS is exported from.
func (m shippedModule) importPath() string { return "github.com/larvit/fejkdata/data/" + m.dir }
// holds reports whether the category at path is the module's: its tree spells its folder.
func (m shippedModule) holds(path string) bool {
return strings.HasPrefix(path, strings.ReplaceAll(m.dir, "/", ".")+".")
}
// shippedModules is every module data.Modules lists, each folder found by descending its
// tree while a folder holds one folder and nothing else.
func shippedModules(t testing.TB) []shippedModule {
t.Helper()
var out []shippedModule
for _, fsys := range data.Modules() {
dir := "."
for {
entries, err := fs.ReadDir(fsys, dir)
if err != nil {
t.Fatal(err)
}
entries = slices.DeleteFunc(entries, func(e fs.DirEntry) bool { return strings.HasPrefix(e.Name(), ".") })
if len(entries) != 1 || !entries[0].IsDir() {
break
}
dir = path.Join(dir, entries[0].Name())
}
out = append(out, shippedModule{fsys: fsys, dir: dir})
}
return out
}
// newShippedWhole loads every shipped category in New, as a source without an index does, so
// a test reads the whole shipped tree.
func newShippedWhole(t testing.TB, opts ...Option) *Generator {
t.Helper()
data, err := fs.Sub(shippedFS, "data")
if err != nil {
t.Fatal(err)
var modules []fs.FS
for _, m := range shippedModules(t) {
modules = append(modules, withoutManifest{m.fsys})
}
f, err := New(append([]Option{WithoutShippedData(), WithDataFS(withoutManifest{data})}, opts...)...)
f, err := New(append([]Option{WithDataFS(modules...)}, opts...)...)
if err != nil {
t.Fatalf("New() = %v", err)
}
@@ -55,57 +94,150 @@ func loadedCategories(f *Generator) []string {
return out
}
// REGENERATE=1 rewrites the manifest.
func TestShippedManifestIsCurrent(t *testing.T) {
got, err := shippedManifest(newShippedWhole(t))
// REGENERATE=1 rewrites the manifests.
func TestShippedManifestsAreCurrent(t *testing.T) {
modules := shippedModules(t)
manifests, err := shippedManifests(newShippedWhole(t), modules)
if err != nil {
t.Fatal(err)
}
if os.Getenv("REGENERATE") == "1" {
if err := os.WriteFile(shippedManifestFile, got, 0o644); err != nil {
for i, m := range modules {
file := filepath.Join("data", filepath.FromSlash(m.dir), datafiles.ManifestFile)
if os.Getenv("REGENERATE") == "1" {
if err := os.WriteFile(file, manifests[i], 0o644); err != nil {
t.Fatal(err)
}
continue
}
want, err := os.ReadFile(file)
if err != nil {
t.Fatal(err)
}
return
}
want, err := os.ReadFile(shippedManifestFile)
if err != nil {
t.Fatal(err)
}
if string(got) != string(want) {
t.Fatalf("%s is stale; regenerate it: docker compose run --rm --user \"$(id -u):$(id -g)\" generate", shippedManifestFile)
if string(manifests[i]) != string(want) {
t.Errorf("%s is stale; regenerate it: docker compose run --rm --user \"$(id -u):$(id -g)\" generate", file)
}
}
}
// shippedManifest is the manifest of whole's tree, one category a line.
func shippedManifest(whole *Generator) ([]byte, error) {
var b bytes.Buffer
b.WriteString("{\n\t\"index\": {")
for i, s := range categorySites(&whole.root) {
// shippedManifests is each module's manifest, read from whole's tree: its index, one category a
// line, and every other module it reads, directly or through another.
func shippedManifests(whole *Generator, modules []shippedModule) ([][]byte, error) {
owner := func(path string) int {
return slices.IndexFunc(modules, func(m shippedModule) bool { return m.holds(path) })
}
index := make([][]string, len(modules))
direct := make([][]int, len(modules))
for _, s := range categorySites(&whole.root) {
i := owner(s.path)
if i < 0 {
return nil, fmt.Errorf("no shipped module holds %s", s.path)
}
line, err := json.Marshal(indexEntry(s))
if err != nil {
return nil, err
}
if i > 0 {
b.WriteString(",")
}
key, _ := json.Marshal(s.path)
b.WriteString("\n\t\t" + string(key) + ": " + string(line))
index[i] = append(index[i], "\n\t\t"+string(key)+": "+string(line))
for _, c := range needs(&whole.root, s) {
if j := owner(categoryPath(c.dir, c.name)); j != i && !slices.Contains(direct[i], j) {
direct[i] = append(direct[i], j)
}
}
}
b.WriteString("\n\t}\n}\n")
return b.Bytes(), nil
out := make([][]byte, len(modules))
for i := range modules {
var b bytes.Buffer
b.WriteString("{\n\t\"index\": {" + strings.Join(index[i], ",") + "\n\t}")
if reads := readsOf(i, direct, modules); len(reads) > 0 {
line, _ := json.Marshal(reads)
b.WriteString(",\n\t\"reads\": " + string(line))
}
b.WriteString("\n}\n")
out[i] = b.Bytes()
}
return out, nil
}
// readsOf is every module module i reads, directly or through another, by import path.
func readsOf(i int, direct [][]int, modules []shippedModule) []string {
seen := map[int]bool{i: true}
queue := slices.Clone(direct[i])
var out []string
for ; len(queue) > 0; queue = queue[1:] {
j := queue[0]
if seen[j] {
continue
}
seen[j] = true
out = append(out, modules[j].importPath())
queue = append(queue, direct[j]...)
}
sort.Strings(out)
return out
}
func shippedIndex(t *testing.T) map[string]datafiles.IndexEntry {
t.Helper()
m, err := shippedSource.Manifest()
if err != nil {
t.Fatal(err)
index := map[string]datafiles.IndexEntry{}
for _, m := range shippedModules(t) {
manifest, err := datafiles.FS(m.fsys, "").Manifest()
if err != nil {
t.Fatal(err)
}
maps.Copy(index, manifest.Index)
}
return index
}
func TestEachShippedModuleNamesWhatItReads(t *testing.T) {
for _, m := range shippedModules(t) {
manifest, err := datafiles.FS(m.fsys, "").Manifest()
if err != nil {
t.Fatal(err)
}
if m.dir == "sv_SE" && !slices.Contains(manifest.Reads, "github.com/larvit/fejkdata/data/geo/SE") {
t.Errorf("sv_SE reads %v, want geo/SE among them", manifest.Reads)
}
if slices.Contains(manifest.Reads, m.importPath()) {
t.Errorf("%s reads %v, want itself left out", m.dir, manifest.Reads)
}
}
}
func TestAReadNoModuleProvidesNamesTheReadersModules(t *testing.T) {
f, err := New(WithDataFS(sv_SE.FS))
if err != nil {
t.Fatalf("New(WithDataFS(sv_SE.FS)) = %v", err)
}
if _, err := f.Fake("sv_SE.address"); err == nil || !strings.Contains(err.Error(), "the module holding sv_SE.address reads by default github.com/larvit/fejkdata/data/geo/SE") {
t.Errorf(`Fake("sv_SE.address") = %v, want the module it reads named`, err)
}
for name, opt := range map[string]Option{
"no manifest": WithDataPath(writeData(t, map[string]string{"x": `"{/y}"`})),
"no reads in one": WithDataFS(fstest.MapFS{".fejkdata.json": {Data: []byte(`{"index": {"x": {"paths": [""]}}}`)}, "x.json": {Data: []byte(`"{/y}"`)}}),
} {
f, err := New(opt)
if err == nil {
_, err = f.Fake("x")
}
if err == nil || !strings.Contains(err.Error(), "the module holding x names no module it reads by default") {
t.Errorf("%s: x = %v, want it said that x's module names none", name, err)
}
}
}
func TestADataPathManifestNamesWhatItReads(t *testing.T) {
dir := writeFiles(t, map[string]string{
".fejkdata.json": `{"reads": ["example.com/places"]}`,
"x.json": `"{/place}"`,
})
if _, err := New(WithDataPath(dir)); err == nil || !strings.Contains(err.Error(), "the module holding x reads by default example.com/places") {
t.Errorf("New = %v, want the module x reads named", err)
}
return m.Index
}
func TestNewLoadsNoShippedCategory(t *testing.T) {
f, err := New()
f, err := New(withShipped())
if err != nil {
t.Fatalf("New() = %v", err)
}
@@ -149,7 +281,7 @@ func TestReachingACategoryLoadsWhatItReads(t *testing.T) {
absent: []string{"sv_SE.person"},
},
} {
f, err := New()
f, err := New(withShipped())
if err != nil {
t.Fatalf("New() = %v", err)
}
@@ -171,7 +303,7 @@ func TestReachingACategoryLoadsWhatItReads(t *testing.T) {
}
func TestADataPathLoadsWhatItReads(t *testing.T) {
f, err := New(WithDataPath(writeData(t, map[string]string{"greeting": `"Hej {/sv_SE.person}!"`})))
f, err := New(withShipped(), WithDataPath(writeData(t, map[string]string{"greeting": `"Hej {/sv_SE.person}!"`})))
if err != nil {
t.Fatalf("New() = %v", err)
}
@@ -196,7 +328,7 @@ func TestAReplacementBreakingAShippedReaderFailsItsFirstReach(t *testing.T) {
"sv_SE/last-name.json": {Data: []byte(`"x"`)},
}),
} {
f, err := New(opt)
f, err := New(withShipped(), opt)
if err != nil {
t.Fatalf("New(%s) = %v", name, err)
}
@@ -216,7 +348,7 @@ func TestAnIndexedSourceLoadsOnFirstReachLikeTheShippedOne(t *testing.T) {
"b.json": {Data: []byte(`"x"`)},
"sub/c.json": {Data: []byte(`{"format":"{d}","d":"y"}`)},
}
f, err := New(WithoutShippedData(), WithDataFS(fsys), WithSeed(1))
f, err := New(WithDataFS(fsys), WithSeed(1))
if err != nil {
t.Fatalf("New() = %v", err)
}
@@ -235,7 +367,7 @@ func TestAnIndexedSourceLoadsOnFirstReachLikeTheShippedOne(t *testing.T) {
}
func TestAnIndexedCategoryFailsAtFirstReachAndEveryReachAfter(t *testing.T) {
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{
f, err := New(WithDataFS(fstest.MapFS{
".fejkdata.json": {Data: []byte(`{"index": {"bad": {"paths": [""]}, "good": {"paths": [""]}}}`)},
"bad.json": {Data: []byte(`"{/nope}"`)},
"good.json": {Data: []byte(`"fine"`)},
@@ -273,8 +405,10 @@ func TestABrokenManifestFailsNewNamingEveryMistake(t *testing.T) {
`{"index": {"a": {"paths": ["", "x", "x"]}}}`: {`paths item 3 repeats "x"`},
`{"index": {"a": {"paths": [""], "parent": "b"}}}`: {`parent "b" names no entry`},
`{"index": {"a": {"paths": [""], "reads": ["b"]}}}`: {`unknown key "reads"`},
`{"reads": "b"}`: {"reads must be a list, not a string"},
`{"reads": ["", 3]}`: {"reads item 1 is empty", "reads item 2 must be a string, not a number"},
} {
_, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{".fejkdata.json": {Data: []byte(manifest)}}))
_, err := New(WithDataFS(fstest.MapFS{".fejkdata.json": {Data: []byte(manifest)}}))
for _, w := range want {
if err == nil || !strings.Contains(err.Error(), w) {
t.Errorf("New(manifest %s) = %v, want an error naming %s", manifest, err, w)
@@ -292,7 +426,7 @@ func TestNewMatchesErrLoadOnDataThatFailsToLoad(t *testing.T) {
"a broken category": WithDataPath(writeData(t, map[string]string{"x": `{ not json`})),
"a broken manifest": WithDataFS(fstest.MapFS{".fejkdata.json": {Data: []byte(`{`)}}),
} {
if _, err := New(opt); !errors.Is(err, ErrLoad) {
if _, err := New(withShipped(), opt); !errors.Is(err, ErrLoad) {
t.Errorf("New(%s) = %v, want it to match ErrLoad", name, err)
}
}
@@ -301,14 +435,14 @@ func TestNewMatchesErrLoadOnDataThatFailsToLoad(t *testing.T) {
func TestOnDemandRendersAsTheWholeLoad(t *testing.T) {
whole := newShippedWhole(t)
paths := whole.List()
lazy, err := New()
lazy, err := New(withShipped())
if err != nil {
t.Fatalf("New() = %v", err)
}
if got := lazy.List(); !reflect.DeepEqual(got, paths) {
t.Fatalf("List() on demand = %v, want %v", got, paths)
}
a, err := New(WithSeed(7))
a, err := New(withShipped(), WithSeed(7))
if err != nil {
t.Fatal(err)
}
@@ -326,7 +460,7 @@ func TestOnDemandRendersAsTheWholeLoad(t *testing.T) {
func TestEveryShippedCategoryLoadsAlone(t *testing.T) {
for _, p := range sortedNames(shippedIndex(t)) {
f, err := New()
f, err := New(withShipped())
if err != nil {
t.Fatalf("New() = %v", err)
}
@@ -337,7 +471,7 @@ func TestEveryShippedCategoryLoadsAlone(t *testing.T) {
}
func TestListRunsBesideAnOnDemandLoad(t *testing.T) {
f, err := New()
f, err := New(withShipped())
if err != nil {
t.Fatalf("New() = %v", err)
}
@@ -359,7 +493,7 @@ func TestListRunsBesideAnOnDemandLoad(t *testing.T) {
}
func TestATableWhoseEntryOmitsItsParentFailsAtFirstReach(t *testing.T) {
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{
f, err := New(WithDataFS(fstest.MapFS{
".fejkdata.json": {Data: []byte(`{"index": {"city": {"paths": ["", "country", "name"]}, "country": {"paths": ["", "code", "name"]}}}`)},
"country.json": {Data: []byte(`{"format":"{name}","rows":"country.tsv","key":"code"}`)},
"country.tsv": {Data: []byte("code\tname\nSE\tSweden\n")},
@@ -382,7 +516,7 @@ func TestATableWhoseEntryOmitsItsParentFailsAtFirstReach(t *testing.T) {
}
func TestADataPathTableUnderAShippedTableLoads(t *testing.T) {
f, err := New(WithSeed(1), WithDataPath(writeFiles(t, map[string]string{
f, err := New(withShipped(), WithSeed(1), WithDataPath(writeFiles(t, map[string]string{
"sv_SE/nickname.json": `{"format":"{name}","rows":"nickname.tsv","key":"name","parent":"sex"}`,
"sv_SE/nickname.tsv": "name\tsex\nKalle\tm\nLotta\tf\n",
})))
@@ -405,7 +539,7 @@ func indexEntry(s categorySite) datafiles.IndexEntry {
}
func TestAnIndexedCategoryWithNoFileNamesTheManifest(t *testing.T) {
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{
f, err := New(WithDataFS(fstest.MapFS{
".fejkdata.json": {Data: []byte(`{"index": {"gone": {"paths": [""]}}}`)},
}))
if err != nil {
+20 -20
View File
@@ -165,7 +165,7 @@ func TestTableCellsAreStringNodes(t *testing.T) {
"place.json": `{"format":"{name}","rows":"place.tsv"}`,
"place.tsv": "name\tzip\nA\t{name}\nB\t2\n",
})
if _, err := New(WithoutShippedData(), WithDataPath(bad)); err == nil || !strings.Contains(err.Error(), "place.tsv") || !strings.Contains(err.Error(), `no field "name"`) {
if _, err := New(WithDataPath(bad)); err == nil || !strings.Contains(err.Error(), "place.tsv") || !strings.Contains(err.Error(), `no field "name"`) {
t.Fatalf("New = %v, want a cell reading a column refused, naming the file", err)
}
}
@@ -327,7 +327,7 @@ func TestTableStepUpRefusals(t *testing.T) {
if _, err := f.Fake(path); err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("Fake(%q) = %v, want an error mentioning %s", path, err, want)
}
_, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, with(geo(), map[string]string{
_, err := New(WithDataPath(writeFiles(t, with(geo(), map[string]string{
"x.json": `{"format":"{y}","y":"a"}`,
"t.json": strconv.Quote("{/" + path + "}"),
}))))
@@ -335,7 +335,7 @@ func TestTableStepUpRefusals(t *testing.T) {
t.Errorf("{/%s}: New = %v, want an error mentioning %s", path, err, want)
}
}
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, map[string]string{"s.json": `{"format":"{a..b}","a":{"format":"{b}","b":"x"}}`}))); err == nil || !strings.Contains(err.Error(), "only from a table's row") {
if _, err := New(WithDataPath(writeFiles(t, map[string]string{"s.json": `{"format":"{a..b}","a":{"format":"{b}","b":"x"}}`}))); err == nil || !strings.Contains(err.Error(), "only from a table's row") {
t.Errorf("{a..b} on a field: New = %v, want an error mentioning only from a table's row", err)
}
}
@@ -464,7 +464,7 @@ func TestTableSelectorInAReference(t *testing.T) {
"selector on template": {`"{/x[1].a}"`, "not a table"},
"selector on repeating template": {`"{/rep[1]}"`, "not a table"},
} {
_, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, with(files, map[string]string{"bad.json": c.json, "rep.json": `{"format":"x","repeat":2}`}))))
_, err := New(WithDataPath(writeFiles(t, with(files, map[string]string{"bad.json": c.json, "rep.json": `{"format":"x","repeat":2}`}))))
if err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%s: New = %v, want an error mentioning %q", name, err, c.want)
}
@@ -484,7 +484,7 @@ func TestTableReadsBesideEachOtherLoad(t *testing.T) {
`"{/region[12].locality[L4].name} {/region[12].locality[L7].name}"`,
`"{/region[14].name} {/locality[L4].name}"`,
} {
f, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, with(geo(), map[string]string{"x.json": json}))), WithSeed(1))
f, err := New(WithDataPath(writeFiles(t, with(geo(), map[string]string{"x.json": json}))), WithSeed(1))
if err != nil {
t.Errorf("%s: New = %v, want each read a draw of its own", json, err)
continue
@@ -504,7 +504,7 @@ func TestSelectedRowsAgreeAcrossSpellings(t *testing.T) {
"a draw under the selection": `"{/region[12].name}|{/region[12].municipality.locality[L4].name}"`,
}
for name, json := range accepted {
f, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, with(geo(), map[string]string{"x.json": json}))), WithSeed(1))
f, err := New(WithDataPath(writeFiles(t, with(geo(), map[string]string{"x.json": json}))), WithSeed(1))
if err != nil {
t.Errorf("%s: New = %v, want it accepted", name, err)
continue
@@ -595,7 +595,7 @@ func TestTableFences(t *testing.T) {
"a category referencing itself": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\n{/t.a}\ny\n"}, "cycle"},
}
for name, c := range rejected {
_, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, c.files)))
_, err := New(WithDataPath(writeFiles(t, c.files)))
if err == nil {
t.Errorf("%s: New = nil error, want it rejected at load", name)
continue
@@ -616,7 +616,7 @@ func TestTableFences(t *testing.T) {
"a cell repeating a reference": with(base, map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\nx\n{/region} {/region} {uppercase(/region)}\n"}),
}
for name, files := range accepted {
f, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, files)), WithSeed(1))
f, err := New(WithDataPath(writeFiles(t, files)), WithSeed(1))
if err != nil {
t.Errorf("%s: New = %v, want each read a draw of its own", name, err)
continue
@@ -627,7 +627,7 @@ func TestTableFences(t *testing.T) {
}
}
}
if _, err := New(WithoutShippedData(), WithDataFS(os.DirFS(writeFiles(t, base)))); err != nil {
if _, err := New(WithDataFS(os.DirFS(writeFiles(t, base)))); err != nil {
t.Fatalf("New(WithDataFS) = %v, want a table loaded from any fs.FS", err)
}
inline := newGenerator(t, writeFiles(t, base))
@@ -711,7 +711,7 @@ func TestTableRowsAreAlternatives(t *testing.T) {
}
symbols := regexp.MustCompile(`€|kr|\$`)
for name, more := range accepted {
g, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, with(files, more))), WithSeed(1))
g, err := New(WithDataPath(writeFiles(t, with(files, more))), WithSeed(1))
if err != nil {
t.Errorf("%s: New = %v, want it accepted", name, err)
continue
@@ -743,7 +743,7 @@ func TestTableRowsAreAlternatives(t *testing.T) {
"a cell reaching another's cells": {"y.json": `{"format":"{sym}","rows":"y.tsv","key":"k"}`, "y.tsv": "k\tsym\n1\t{/cur[SEK].sym}\n2\t{/cur[USD].sym}\n", "cur.tsv": "code\tsym\nEUR\t€\nSEK\tkr\nUSD\t$\n", "country.tsv": "alpha2\tname\tmoney\nFI\tFinland\t{/y[1].sym} {/cur[EUR].sym}\nSE\tSweden\t{/cur[SEK].sym}\n"},
}
for name, more := range twoRows {
g, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, with(files, more))), WithSeed(1))
g, err := New(WithDataPath(writeFiles(t, with(files, more))), WithSeed(1))
if err != nil {
t.Errorf("%s: New = %v, want each read a draw of its own", name, err)
continue
@@ -765,7 +765,7 @@ func TestTableOverridesByLayering(t *testing.T) {
"misc/language.json": `{"format":"{name}","rows":"language.tsv","key":"code"}`,
"misc/language.tsv": "code\tname\nxx\tNowhere\nyy\tElsewhere\n",
})
f, err := New(WithDataPath(mine), WithSeed(1))
f, err := New(withShipped(), WithDataPath(mine), WithSeed(1))
if err != nil {
t.Fatal(err)
}
@@ -778,7 +778,7 @@ func TestTableOverridesByLayering(t *testing.T) {
}
func TestShippedTables(t *testing.T) {
f := newGenerator(t, "data/misc", WithSeed(1))
f := newGenerator(t, "data/misc/misc", WithSeed(1))
for path, want := range map[string]string{
"territory[SE]": "Sweden",
"territory[Sweden].alpha3": "SWE",
@@ -893,7 +893,7 @@ func TestShippedTables(t *testing.T) {
Method string `fake:"{m.method}"`
Safe bool `fake:"{m.safe}"`
}
if err := newGenerator(t, "data", WithSeed(1)).FakeStruct(&row); err != nil {
if err := shipped(t, WithSeed(1)).FakeStruct(&row); err != nil {
t.Fatalf("FakeStruct into a bool = %v, want the register's yes and no shipped as true and false", err)
}
want, named := map[string][2]bool{
@@ -992,7 +992,7 @@ func TestEveryTldRowSpellsItsKey(t *testing.T) {
// columns asked for and that every row fills them.
func shippedRows(t *testing.T, file string, want ...string) (map[string]int, [][]string) {
t.Helper()
data, err := os.ReadFile(filepath.Join("data", "misc", file))
data, err := os.ReadFile(filepath.Join("data", "misc", "misc", file))
if err != nil {
t.Fatal(err)
}
@@ -1122,7 +1122,7 @@ func TestCellReadsLoadAndRender(t *testing.T) {
"region.tsv": "code\tname\tpopulation\tnote\n01\tStockholms län\t2400000\t{/addr}\n12\tSkåne län\t1400000\t-\n14\tVästra Götalands län\t1750000\t-\n",
})
for _, x := range []string{`"{/region.note} {/municipality.note}"`, `"{/region}|{/municipality}"`} {
f, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, with(cells, map[string]string{"x.json": x}))), WithSeed(1))
f, err := New(WithDataPath(writeFiles(t, with(cells, map[string]string{"x.json": x}))), WithSeed(1))
if err != nil {
t.Fatalf("New(%s) = %v, want the cells accepted", x, err)
}
@@ -1136,7 +1136,7 @@ func TestDatatypeRefusesARowRead(t *testing.T) {
for _, format := range []string{"{/region}", "{/region[01]}"} {
files := with(geo(), map[string]string{"row.json": `{"format":"","col":{"format":"` + format + `","datatype":"integer"}}`})
want := `"{name}" renders a row of region, which is composed text`
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, files))); err == nil || !strings.Contains(err.Error(), want) {
if _, err := New(WithDataPath(writeFiles(t, files))); err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("%s: New = %v, want an error containing %q", format, err, want)
}
}
@@ -1148,12 +1148,12 @@ func TestATableErrorNamesItsPathAndACellItsLine(t *testing.T) {
"g/t.tsv": "a\tb\nx\t-\ny\t{/nope}\n",
}
want := "g.t.b, line 3: reference {/nope}"
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, files))); err == nil || !strings.Contains(err.Error(), want) {
if _, err := New(WithDataPath(writeFiles(t, files))); err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("a cell's bad reference: New = %v, want it to contain %q", err, want)
}
files["g/t.json"], files["g/t.tsv"] = `{"format":"{a} {/nope}","rows":"t.tsv","key":"a"}`, "a\tb\nx\t-\ny\t-\n"
want = "g.t: reference {/nope}"
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, files))); err == nil || !strings.Contains(err.Error(), want) {
if _, err := New(WithDataPath(writeFiles(t, files))); err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("a format's bad reference: New = %v, want it to contain %q", err, want)
}
f := newGenerator(t, writeFiles(t, geo()), WithSeed(1))
@@ -1165,7 +1165,7 @@ func TestATableErrorNamesItsPathAndACellItsLine(t *testing.T) {
t.Errorf("Fake(%s) = %v, want it to contain %q", path, err, want)
}
}
shipped, err := New(WithSeed(1))
shipped, err := New(withShipped(), WithSeed(1))
if err != nil {
t.Fatal(err)
}