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

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
11 changed files with 165 additions and 58 deletions
Showing only changes of commit 29a76d5d9a - Show all commits
+42 -5
View File
@@ -12,11 +12,48 @@ import (
"testing"
)
const (
svSE = "../../data/sv_SE/sv_SE"
enUS = "../../data/en_US/en_US"
misc = "../../data/misc/misc"
)
// svSE, enUS and misc are copies of a shipped module's files without its manifest, so a test
// layers its categories unprefixed with -d.
var svSE, enUS, misc string
func TestMain(m *testing.M) {
tmp, err := os.MkdirTemp("", "fejkdata-cli-test")
if err != nil {
panic(err)
}
svSE, enUS, misc = bareCopy(tmp, "svse"), bareCopy(tmp, "enus"), bareCopy(tmp, "misc")
code := m.Run()
os.RemoveAll(tmp)
os.Exit(code)
}
// bareCopy copies the category and rows files of the shipped module in data/<module> into a
// folder under tmp, and returns that folder.
func bareCopy(tmp, module string) string {
src, dst := filepath.Join("..", "..", "data", module), filepath.Join(tmp, module)
if err := os.Mkdir(dst, 0o755); err != nil {
panic(err)
}
for _, pattern := range []string{"*.json", "*.tsv"} {
files, err := filepath.Glob(filepath.Join(src, pattern))
if err != nil {
panic(err)
}
for _, f := range files {
if strings.HasPrefix(filepath.Base(f), ".") {
continue
}
b, err := os.ReadFile(f)
if err == nil {
err = os.WriteFile(filepath.Join(dst, filepath.Base(f)), b, 0o644)
}
if err != nil {
panic(err)
}
}
}
return dst
}
func runOut(stdin string, args ...string) (int, string, string) {
var out, errb bytes.Buffer
@@ -1,18 +1,18 @@
package SE_test
package enus_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/geo/SE"
"github.com/larvit/fejkdata/data/enus"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(SE.FS, ".fejkdata.json"); err != nil {
if _, err := fs.Stat(enus.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(SE.FS))
f, err := fejkdata.New(fejkdata.WithDataFS(enus.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
@@ -1,18 +1,18 @@
package en_US_test
package geose_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/en_US"
"github.com/larvit/fejkdata/data/geose"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(en_US.FS, ".fejkdata.json"); err != nil {
if _, err := fs.Stat(geose.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(en_US.FS))
f, err := fejkdata.New(fejkdata.WithDataFS(geose.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
@@ -1,18 +1,18 @@
package sv_SE_test
package geous_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/sv_SE"
"github.com/larvit/fejkdata/data/geous"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(sv_SE.FS, ".fejkdata.json"); err != nil {
if _, err := fs.Stat(geous.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(sv_SE.FS))
f, err := fejkdata.New(fejkdata.WithDataFS(geous.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
@@ -1,18 +1,18 @@
package US_test
package svse_test
import (
"io/fs"
"testing"
"github.com/larvit/fejkdata"
"github.com/larvit/fejkdata/data/geo/US"
"github.com/larvit/fejkdata/data/svse"
)
func TestFSLoadsByItsManifest(t *testing.T) {
if _, err := fs.Stat(US.FS, ".fejkdata.json"); err != nil {
if _, err := fs.Stat(svse.FS, ".fejkdata.json"); err != nil {
t.Fatalf("FS holds no manifest: %v", err)
}
f, err := fejkdata.New(fejkdata.WithDataFS(US.FS))
f, err := fejkdata.New(fejkdata.WithDataFS(svse.FS))
if err != nil {
t.Fatalf("New(WithDataFS(FS)) = %v", err)
}
+4 -4
View File
@@ -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/misc", WithSeed(1))
f := newBareModule(t, "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/misc", WithSeed(1))
f := newBareModule(t, "misc", WithSeed(1))
re := map[string]*regexp.Regexp{
"currency": regexp.MustCompile(`^[A-Z]{3}$`),
"currency.name": regexp.MustCompile(`\p{L}`),
@@ -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 + "/geo/" + country + "/" + table + ".tsv")
b, err := os.ReadFile("data/geo" + strings.ToLower(country) + "/" + table + ".tsv")
if err != nil {
t.Fatal(err)
}
@@ -468,7 +468,7 @@ func TestShippedTableCellsAreTrimmed(t *testing.T) {
}
// TestShippedNamespacedTree loads every shipped module and reaches each locale through its
// folder segment: data/sv_SE/sv_SE/person.json -> sv_SE.person.
// namespace: data/svse/person.json, mounted under sv_SE, is sv_SE.person.
func TestShippedNamespacedTree(t *testing.T) {
f := shipped(t, WithSeed(1))
for _, path := range []string{"sv_SE.person", "en_US.person", "sv_SE.address.locality"} {
+13
View File
@@ -3,6 +3,8 @@ package fejkdata
import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
"testing"
"testing/fstest"
@@ -35,6 +37,17 @@ func newGeneratorN(t *testing.T, dirs []string, opts ...Option) *Generator {
return f
}
// newBareModule loads the shipped module in data/<module> without its manifest, so its
// categories sit at the root, failing on error.
func newBareModule(t *testing.T, module string, opts ...Option) *Generator {
t.Helper()
f, err := New(append([]Option{WithDataFS(withoutManifest{os.DirFS(filepath.Join("data", module))})}, opts...)...)
if err != nil {
t.Fatalf("New(%s without its manifest): %v", module, err)
}
return f
}
// fake generates a value, failing the test on error.
func fake(t *testing.T, f *Generator, path string) string {
t.Helper()
+10 -10
View File
@@ -31,12 +31,12 @@ func TestPackageImports(t *testing.T) {
allowed := map[string][]string{
".": {"internal/builtinfunc", "internal/datafiles", "internal/datatype", "internal/drawstate", "internal/grammar", "internal/invariant", "internal/jsonvalue", "internal/proven", "internal/rows"},
"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": {"data/enus", "data/geose", "data/geous", "data/misc", "data/svse"},
"data/enus": nil,
"data/geose": nil,
"data/geous": nil,
"data/misc": nil,
"data/sv_SE": nil,
"data/svse": nil,
"internal/builtinfunc": {"internal/datatype", "internal/drawstate", "internal/grammar", "internal/invariant", "internal/proven"},
"internal/datafiles": {"internal/grammar", "internal/jsonvalue"},
"internal/datatype": nil,
@@ -48,13 +48,13 @@ func TestPackageImports(t *testing.T) {
"internal/rows": {"internal/drawstate", "internal/grammar", "internal/invariant"},
}
testsAlso := map[string][]string{
".": {"data", "data/en_US", "data/geo/SE", "data/geo/US", "data/misc", "data/sv_SE"},
".": {"data", "data/enus", "data/geose", "data/geous", "data/misc", "data/svse"},
"cmd/fejkdata": {"internal/grammar"},
"data/en_US": {"."},
"data/geo/SE": {"."},
"data/geo/US": {"."},
"data/enus": {"."},
"data/geose": {"."},
"data/geous": {"."},
"data/misc": {"."},
"data/sv_SE": {"."},
"data/svse": {"."},
}
seen := map[string]bool{}
eachPackage(t, func(dir string, pkg *build.Package) {
+1 -1
View File
@@ -206,7 +206,7 @@ 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", and no loaded module names a module it reads by default`,
`fejkdata: b: reference {/nope}: no entry "nope", and no loaded manifest names a module to add`,
},
}
for _, c := range cases {
+77 -20
View File
@@ -18,11 +18,11 @@ import (
"testing/fstest"
"github.com/larvit/fejkdata/data"
"github.com/larvit/fejkdata/data/en_US"
"github.com/larvit/fejkdata/data/geo/SE"
"github.com/larvit/fejkdata/data/geo/US"
"github.com/larvit/fejkdata/data/enus"
"github.com/larvit/fejkdata/data/geose"
"github.com/larvit/fejkdata/data/geous"
"github.com/larvit/fejkdata/data/misc"
"github.com/larvit/fejkdata/data/sv_SE"
"github.com/larvit/fejkdata/data/svse"
"github.com/larvit/fejkdata/internal/datafiles"
)
@@ -36,24 +36,42 @@ 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.
// shippedModule is a module data.Modules lists, its folder under data/, and the namespace its
// manifest mounts it under.
type shippedModule struct {
fsys fs.FS
dir string
fsys fs.FS
dir string
namespace 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, "/", ".")+".")
// holds reports whether the category at path is the module's.
func (m shippedModule) holds(path string) bool { return strings.HasPrefix(path, m.namespace+".") }
// whole is the module's files with a manifest naming only its namespace, so it loads whole in New.
func (m shippedModule) whole(t testing.TB) fs.FS {
t.Helper()
out := fstest.MapFS{datafiles.ManifestFile: {Data: []byte(`{"namespace": "` + m.namespace + `"}`)}}
err := fs.WalkDir(m.fsys, ".", func(p string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || p == datafiles.ManifestFile {
return err
}
b, err := fs.ReadFile(m.fsys, p)
out[p] = &fstest.MapFile{Data: b}
return err
})
if err != nil {
t.Fatal(err)
}
return out
}
// shippedModules is every module data.Modules lists, each beside its folder under data/.
func shippedModules(t testing.TB) []shippedModule {
t.Helper()
out := []shippedModule{{en_US.FS, "en_US"}, {SE.FS, "geo/SE"}, {US.FS, "geo/US"}, {misc.FS, "misc"}, {sv_SE.FS, "sv_SE"}}
out := []shippedModule{{enus.FS, "enus", "en_US"}, {geose.FS, "geose", "geo.SE"}, {geous.FS, "geous", "geo.US"}, {misc.FS, "misc", "misc"}, {svse.FS, "svse", "sv_SE"}}
var listed []fs.FS
for _, m := range out {
listed = append(listed, m.fsys)
@@ -70,7 +88,7 @@ func newShippedWhole(t testing.TB, opts ...Option) *Generator {
t.Helper()
var modules []fs.FS
for _, m := range shippedModules(t) {
modules = append(modules, withoutManifest{m.fsys})
modules = append(modules, m.whole(t))
}
f, err := New(append([]Option{WithDataFS(modules...)}, opts...)...)
if err != nil {
@@ -141,7 +159,8 @@ func shippedManifests(whole *Generator, modules []shippedModule) ([][]byte, erro
out := make([][]byte, len(modules))
for i := range modules {
var b bytes.Buffer
b.WriteString("{\n\t\"index\": {" + strings.Join(index[i], ",") + "\n\t}")
namespace, _ := json.Marshal(modules[i].namespace)
b.WriteString("{\n\t\"index\": {" + strings.Join(index[i], ",") + "\n\t},\n\t\"namespace\": " + string(namespace))
if reads := readsOf(i, direct, modules); len(reads) > 0 {
line, _ := json.Marshal(reads)
b.WriteString(",\n\t\"reads\": " + string(line))
@@ -211,11 +230,11 @@ func TestEachShippedModuleRendersBesideTheModulesItReads(t *testing.T) {
}
func TestAReadNoModuleProvidesNamesTheReadersModules(t *testing.T) {
f, err := New(WithDataFS(sv_SE.FS))
f, err := New(WithDataFS(svse.FS))
if err != nil {
t.Fatalf("New(WithDataFS(sv_SE.FS)) = %v", err)
t.Fatalf("New(WithDataFS(svse.FS)) = %v", err)
}
if _, err := f.Fake("sv_SE.address"); err == nil || !strings.Contains(err.Error(), "the loaded modules read by default github.com/larvit/fejkdata/data/geo/SE") {
if _, err := f.Fake("sv_SE.address"); err == nil || !strings.Contains(err.Error(), "is a module missing? The loaded modules read github.com/larvit/fejkdata/data/geose") {
t.Errorf(`Fake("sv_SE.address") = %v, want the module it reads named`, err)
}
for name, opt := range map[string]Option{
@@ -226,7 +245,7 @@ func TestAReadNoModuleProvidesNamesTheReadersModules(t *testing.T) {
if err == nil {
_, err = f.Fake("x")
}
if err == nil || !strings.Contains(err.Error(), "no loaded module names a module it reads by default") {
if err == nil || !strings.Contains(err.Error(), "no loaded manifest names a module to add") {
t.Errorf("%s: x = %v, want it said that no loaded module names one", name, err)
}
}
@@ -237,7 +256,7 @@ func TestADataPathManifestNamesWhatItReads(t *testing.T) {
".fejkdata.json": `{"reads": ["example.com/places"]}`,
"x.json": `"{/place}"`,
})
if _, err := New(WithDataPath(dir)); err == nil || !strings.Contains(err.Error(), "the loaded modules read by default example.com/places") {
if _, err := New(WithDataPath(dir)); err == nil || !strings.Contains(err.Error(), "is a module missing? The loaded modules read example.com/places") {
t.Errorf("New = %v, want the module x reads named", err)
}
}
@@ -250,8 +269,43 @@ func TestAnIndexedModuleNamesWhatItReads(t *testing.T) {
if err != nil {
t.Fatalf("New = %v", err)
}
if _, err := f.Fake("x"); err == nil || !strings.Contains(err.Error(), "the loaded modules read by default example.com/places") {
t.Errorf(`Fake("x") = %v, want the module x reads named`, err)
const note = "is a module missing? The loaded modules read example.com/places"
for name, reach := range map[string]func() error{
"Fake": func() error { _, err := f.Fake("x"); return err },
"FakeTemplate": func() error { _, err := f.FakeTemplate("{/x}"); return err },
"FakeStruct": func() error {
return f.FakeStruct(&struct {
A string `fake:"/x"`
}{})
},
} {
if err := reach(); err == nil || strings.Count(err.Error(), note) != 1 {
t.Errorf("%s reaching x = %v, want the module x reads named once", name, err)
}
}
if _, err := f.FakeTemplate("{/nope}"); err == nil || strings.Contains(err.Error(), "module") {
t.Errorf("FakeTemplate({/nope}) = %v, want no module named: no load failed", err)
}
}
func TestANamespaceMountsAModuleUnderIt(t *testing.T) {
for name, opt := range map[string]Option{
"a data path": WithDataPath(writeFiles(t, map[string]string{
".fejkdata.json": `{"namespace": "my.data"}`,
"word.json": `"hej"`,
})),
"an indexed source": WithDataFS(fstest.MapFS{
".fejkdata.json": {Data: []byte(`{"index": {"my.data.word": {"paths": [""]}}, "namespace": "my.data"}`)},
"word.json": {Data: []byte(`"hej"`)},
}),
} {
f, err := New(opt)
if err != nil {
t.Fatalf("New(%s) = %v", name, err)
}
if got := fake(t, f, "my.data.word"); got != "hej" {
t.Errorf("%s: my.data.word = %q, want hej", name, got)
}
}
}
@@ -427,6 +481,9 @@ func TestABrokenManifestFailsNewNamingEveryMistake(t *testing.T) {
`{"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"},
`{"reads": ["a", "a"]}`: {`reads item 2 repeats "a"`},
`{"namespace": 5}`: {"namespace must be a string, not a number"},
`{"namespace": "a..b"}`: {`namespace "a..b"`},
`{"index": {"b.x": {"paths": [""]}}, "namespace": "a"}`: {`index entry "b.x" lies outside the namespace "a"`},
} {
_, err := New(WithDataFS(fstest.MapFS{".fejkdata.json": {Data: []byte(manifest)}}))
for _, w := range want {
+2 -2
View File
@@ -778,7 +778,7 @@ func TestTableOverridesByLayering(t *testing.T) {
}
func TestShippedTables(t *testing.T) {
f := newGenerator(t, "data/misc/misc", WithSeed(1))
f := newBareModule(t, "misc", WithSeed(1))
for path, want := range map[string]string{
"territory[SE]": "Sweden",
"territory[Sweden].alpha3": "SWE",
@@ -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", "misc", file))
data, err := os.ReadFile(filepath.Join("data", "misc", file))
if err != nil {
t.Fatal(err)
}