Pin the shipped shape: every path, each category's format, each column's datatype and nullability

This commit is contained in:
2026-09-16 19:23:27 +02:00
parent b45a1edae0
commit f9181a08b5
2 changed files with 209 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
package fejkdata
import (
"os"
"strconv"
"strings"
"testing"
)
const shapePin = "testdata/shipped_shape.txt"
// TestShippedShapeIsPinned pins what a version promises about the shipped data (see
// the README's Versioning): every path, each category's format, and each record
// column's datatype and nullability. REPIN=1 rewrites the pin.
func TestShippedShapeIsPinned(t *testing.T) {
f, err := New(WithSeed(1))
if err != nil {
t.Fatal(err)
}
got := shippedShape(f)
if os.Getenv("REPIN") == "1" {
if err := os.WriteFile(shapePin, []byte(got), 0o644); err != nil {
t.Fatal(err)
}
return
}
want, err := os.ReadFile(shapePin)
if err != nil {
t.Fatal(err)
}
if got != string(want) {
t.Fatalf("the shipped shape differs from %s: a removed, renamed or retyped line is a breaking change; add the CHANGELOG.md entry, then repin with REPIN=1", shapePin)
}
}
// shippedShape lists every path Fake accepts, one per line. A category-level template
// carries its format, and each of its columns its datatype and whether it may be null.
func shippedShape(f *Generator) string {
facts := map[string]string{}
var walk func(prefix string, n node)
walk = func(prefix string, n node) {
switch n := n.(type) {
case *folder:
for _, name := range sortedNames(n.children) {
walk(join(prefix, name), n.children[name])
}
case *template:
facts[prefix] = "\tformat " + strconv.Quote(n.format)
if _, columns, err := recordOf(n); err == nil {
for _, c := range columns {
fact := "\t" + c.DataType.String()
if _, nullable := columnItems(n.fields[c.Name]); nullable {
fact += " null"
}
facts[join(prefix, c.Name)] = fact
}
}
}
}
for _, name := range sortedNames(f.categories) {
walk(name, f.categories[name])
}
var b strings.Builder
for _, p := range f.List() {
b.WriteString(p + facts[p] + "\n")
}
return b.String()
}
+141
View File
@@ -0,0 +1,141 @@
en_US.address format "{street-number} {street}\n{locality}, {region} {postal-code}"
en_US.address.locality string
en_US.address.postal-code string
en_US.address.region string
en_US.address.street string
en_US.address.street-number string
en_US.address.street.name
en_US.address.street.suffix
en_US.color
en_US.company format "{base} {suffix}"
en_US.company.base string
en_US.company.suffix string
en_US.date format "{month}/{day}/{year}"
en_US.date.day string
en_US.date.month string
en_US.date.year string
en_US.email format "{local}@{domain}"
en_US.email.domain string
en_US.email.local string
en_US.email.local.n
en_US.ip
en_US.person format "{prefix}{femalefirst|malefirst} {last}"
en_US.person.femalefirst string
en_US.person.last string
en_US.person.malefirst string
en_US.person.prefix string
en_US.phone
en_US.phone.area
en_US.phone.exch
en_US.phone.line
en_US.price format "${amt}.{cents}"
en_US.price.amt string
en_US.price.cents string
en_US.sentence
en_US.sentence.adj
en_US.sentence.noun
en_US.sentence.prep
en_US.sentence.verb
en_US.ssn format "{int(100,999)}-{digits(2)}-{digits(4)}"
en_US.time format "{hour}:{minute} {ampm}"
en_US.time.ampm string
en_US.time.hour string
en_US.time.minute string
en_US.time.minute.t
en_US.url format "https://{host}{path}"
en_US.url.host string
en_US.url.path string
en_US.username
en_US.username.n
en_US.version format "{pre}{n}.{n}.{n}{suffix}"
en_US.version.n string
en_US.version.pre string
en_US.version.suffix string
en_US.word
misc.car
misc.car.maker
misc.car.model
misc.coordinate format "{lat}, {lon}"
misc.coordinate.lat string
misc.coordinate.lon string
misc.country
misc.country.alpha2
misc.country.alpha3
misc.country.name
misc.creditcard
misc.creditcard.d
misc.currency
misc.currency.code
misc.currency.name
misc.currency.symbol
misc.emoji
misc.httpstatus
misc.httpstatus.code
misc.httpstatus.reason
misc.language
misc.language.code
misc.language.name
misc.mac format "{hex(2)}:{hex(2)}:{hex(2)}:{hex(2)}:{hex(2)}:{hex(2)}"
misc.mimetype
misc.mimetype.ext
misc.mimetype.type
misc.objectid format "{hex(24)}"
misc.timezone
misc.useragent
misc.uuid format "{hex(8)}-{hex(4)}-4{hex(3)}-{variant}{hex(3)}-{hex(12)}"
misc.uuid.variant string
sv_SE.address format "{street} {street-number}\n{postal-code} {locality}"
sv_SE.address.locality string
sv_SE.address.postal-code string
sv_SE.address.street string
sv_SE.address.street-number string
sv_SE.color
sv_SE.company format "{base} {suffix}"
sv_SE.company.base string
sv_SE.company.suffix string
sv_SE.date format "{year}-{month}-{day}"
sv_SE.date.day string
sv_SE.date.month string
sv_SE.date.year string
sv_SE.email format "{local}@{domain}"
sv_SE.email.domain string
sv_SE.email.local string
sv_SE.email.local.n
sv_SE.ip
sv_SE.person format "{prefix}{femalefirst|malefirst} {last}"
sv_SE.person.femalefirst string
sv_SE.person.last string
sv_SE.person.malefirst string
sv_SE.person.prefix string
sv_SE.phone
sv_SE.phone.a
sv_SE.phone.b
sv_SE.phone.c
sv_SE.phone.prefix
sv_SE.price format "{amt}{ore} kr"
sv_SE.price.amt string
sv_SE.price.ore string
sv_SE.sentence
sv_SE.sentence.adj
sv_SE.sentence.noun
sv_SE.sentence.prep
sv_SE.sentence.verb
sv_SE.ssn format "{digits(2)}{mmdd}-{digits(3)}{luhn()}"
sv_SE.ssn.mmdd string
sv_SE.ssn.mmdd.d
sv_SE.ssn.mmdd.m
sv_SE.time format "{hour}:{minute}{sec}"
sv_SE.time.hour string
sv_SE.time.minute string
sv_SE.time.minute.t
sv_SE.time.sec string
sv_SE.url format "https://{host}{path}"
sv_SE.url.host string
sv_SE.url.path string
sv_SE.username
sv_SE.username.n
sv_SE.version format "{pre}{n}.{n}.{n}{suffix}"
sv_SE.version.n string
sv_SE.version.pre string
sv_SE.version.suffix string
sv_SE.word