From bc4280561ef74b97a13fbbce7496f39120f41545 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 15 Sep 2026 21:10:36 +0200 Subject: [PATCH] Tests: a struct column need not agree on a datatype, text beside a typed column read names its text spelling, a column read is reported before its reader, and column reads through a dot, a choice and an always-null column --- datatype_test.go | 8 ++++++++ record_test.go | 20 +++++++++++++------- struct_test.go | 30 +++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/datatype_test.go b/datatype_test.go index 8cbf156..5116721 100644 --- a/datatype_test.go +++ b/datatype_test.go @@ -49,6 +49,7 @@ func TestDatatypeRejectsAValueItsTypeRejects(t *testing.T) { {"items of two datatypes", `[{"format":"1","datatype":"integer"},{"format":"true","datatype":"boolean"}]`, "a column holds one datatype"}, {"an item beside a typed column it reads", `["{/src.score}","x"]`, `write it as {"format":"x","datatype":"integer"}`}, {"a typed item beside a string column it reads", `["{/src.code}",{"format":"1","datatype":"integer"}]`, `write it as {"format":"{/src.code}","datatype":"integer"}`}, + {"text beside a typed column it reads", `["{/src.score}","n/a"]`, `to read that column as text, write {"format":"{text}","text":"{/src.score}"}`}, {"a datatype over a typed column", `{"format":"{/src.score}","datatype":"integer"}`, `{/src.score} takes datatype integer from the column it reads; drop "datatype"`}, {"another datatype over a typed column", `{"format":"{/src.score}","datatype":"number"}`, `{/src.score} takes datatype integer from the column it reads; drop "datatype"`}, {"a value of the column it reads", `{"format":"{/src.code}","datatype":"integer"}`, `"2x" is not an integer`}, @@ -98,6 +99,13 @@ func TestDatatypeRejectsAValueItsTypeRejects(t *testing.T) { t.Errorf("%s: NewTemplate = %v, want the inline template refused the same way", c.name, err) } } + _, err := New(WithoutShippedData(), WithDataPath(writeData(t, map[string]string{ + "a": `{"format":"","c":["{/b.x}",{"format":"1","datatype":"integer"}]}`, + "b": `{"format":"","x":["2",{"format":"1","datatype":"integer"}]}`, + }))) + if want := `b: field "x": item "2"`; err == nil || !strings.Contains(err.Error(), want) { + t.Errorf("a column reading one whose items disagree: New = %v, want the column read reported first, containing %q", err, want) + } } var jsonInteger = regexp.MustCompile(`^(0|-?[1-9][0-9]*)$`) diff --git a/record_test.go b/record_test.go index 8d76f14..9094172 100644 --- a/record_test.go +++ b/record_test.go @@ -385,7 +385,7 @@ func TestRecordBareReferenceStaysIndependent(t *testing.T) { func TestRecordColumnOfOneReferenceIsTheColumnItReads(t *testing.T) { dir := writeData(t, map[string]string{ "mid": `{"format":"","score":"{/src.score}"}`, - "row": `{"format":"","chain":"{/mid.score}","code":{"format":"{/src.code}","datatype":"integer"},"label":"n={/src.score}","same":"{/src.score}","score":"{/src.score}","text":"{/src.code}"}`, + "row": `{"format":"","chain":"{/mid.score}","code":{"format":"{/src.code}","datatype":"integer"},"dot":"{.src.score}","label":"n={/src.score}","mixed":[{"format":"{text}","text":"{/src.score}"},"n/a"],"pick":["{/src.score}",{"format":"7","datatype":"integer"}],"same":"{/src.score}","score":"{/src.score}","text":"{/src.code}"}`, "src": `{"format":"","code":[null,"200","404"],"score":[null,{"format":"{int(1,9)}","datatype":"integer"}]}`, }) f := newGenerator(t, dir, WithSeed(1)) @@ -408,22 +408,28 @@ func TestRecordColumnOfOneReferenceIsTheColumnItReads(t *testing.T) { return cols[name].Null == want.Null && cols[name].Value == want.Value } switch { - case score.DataType != DataTypeInteger || cols["chain"].DataType != DataTypeInteger || code.DataType != DataTypeInteger || cols["label"].DataType != DataTypeString || cols["text"].DataType != DataTypeString: - t.Fatalf("%s: want score, chain and code integer columns, label and text string ones", r.JSON()) + case score.DataType != DataTypeInteger || cols["chain"].DataType != DataTypeInteger || code.DataType != DataTypeInteger || cols["pick"].DataType != DataTypeInteger || cols["label"].DataType != DataTypeString || cols["mixed"].DataType != DataTypeString || cols["text"].DataType != DataTypeString: + t.Fatalf("%s: want score, chain, code and pick integer columns, label, mixed and text string ones", r.JSON()) case score.Null == (len(score.Value) == 1 && score.Value >= "1" && score.Value <= "9"): t.Fatalf("score = %+v, want null or a digit from src.score", score) case code.Null == (code.Value == "200" || code.Value == "404"): t.Fatalf("code = %+v, want null or a code from src.code", code) - case !agree("same", score) || !agree("chain", score) || !agree("text", code): + case !agree("same", score) || !agree("chain", score) || !agree("dot", score) || !agree("text", code): t.Fatalf("%s: want every read of one src column one draw, through mid too", r.JSON()) + case cols["pick"].Value != "7" && !agree("pick", score), cols["mixed"].Null || cols["mixed"].Value != "n/a" && cols["mixed"].Value != score.Value: + t.Fatalf("pick = %+v, mixed = %+v beside score %+v: want pick 7 or the score's draw, mixed n/a or the score's text", cols["pick"], cols["mixed"], score) case cols["label"].Null || cols["label"].Value != "n="+score.Value: t.Fatalf("label = %+v beside score %+v, want the text of the read, a null as \"\"", cols["label"], score) case strings.Contains(r.JSON(), `"score":null`) != score.Null: t.Fatalf("JSON() = %s, want a null score written null", r.JSON()) } - ic := inline.Fake().Columns()[0] - if ic.DataType != DataTypeInteger || ic.Null == (len(ic.Value) == 1) { - t.Fatalf("inline score = %+v, want an integer column, null or a digit", ic) + ir := inline.Fake() + ic, sqlValue := ir.Columns()[0], ir.Columns()[0].Value + if ic.Null { + sqlValue = "NULL" + } + if ic.DataType != DataTypeInteger || ic.Null == (len(ic.Value) == 1) || ir.SQLInsert("t") != `INSERT INTO "t" ("score") VALUES (`+sqlValue+`);` || ir.CSVLine() != ic.Value { + t.Fatalf("inline score = %+v written %s and %q, want an integer column, NULL and an empty field or a bare digit", ic, ir.SQLInsert("t"), ir.CSVLine()) } for name, c := range map[string]Column{"code": code, "inline": ic, "score": score} { if c.Null { diff --git a/struct_test.go b/struct_test.go index 673500f..61f2fcc 100644 --- a/struct_test.go +++ b/struct_test.go @@ -54,7 +54,8 @@ func structData(t *testing.T) *Generator { return newGenerator(t, writeData(t, map[string]string{ "person": `[{"format":"{first} {last}","first":"Ada","last":"Lovelace"},{"format":"{first} {last}","first":"Bo","last":"Ek"}]`, "place": `[{"format":"{city}","city":"Stockholm","zip":"111 22"},{"format":"{city}","city":"TranĂ¥s","zip":"573 31"}]`, - "src": `{"format":"","code":[null,"200","404"],"score":[null,{"format":"{int(1,9)}","datatype":"integer"}]}`, + "mid": `{"format":"","score":["{/src.score}",{"format":"5","datatype":"integer"}]}`, + "src": `{"format":"","code":[null,"200","404"],"del":null,"score":[null,{"format":"{int(1,9)}","datatype":"integer"}]}`, "trip": `{"format":"","leg":[{"format":"{to}","to":"Oslo"},{"format":"{to}","to":"Rome"}]}`, }), WithSeed(1)) } @@ -62,15 +63,39 @@ func structData(t *testing.T) *Generator { func TestFakeStructFieldTaggedWithAColumnIsThatColumn(t *testing.T) { f := structData(t) nils := map[string]int{} + show := func(p any) string { + switch p := p.(type) { + case *string: + if p != nil { + return *p + } + case *int64: + if p != nil { + return strconv.FormatInt(*p, 10) + } + } + return "nil" + } for i := 0; i < 200; i++ { var v struct { Code *string `fake:"src.code"` + Codes *string `fake:"[\"{/src.score}\",\"{/src.code}\"]"` + Del *int64 `fake:"src.del"` Label string `fake:"n={/src.score}"` + Mixed *string `fake:"[\"{/src.score}\",\"x\"]"` + Pair *int64 `fake:"[\"{/src.score}\",\"5\"]"` Score *int64 `fake:"src.score"` } if err := f.FakeStruct(&v); err != nil { t.Fatal(err) } + score, code := show(v.Score), show(v.Code) + switch { + case show(v.Del) != "nil": + t.Fatalf("Del = %s, want nil from a column only ever null", show(v.Del)) + case show(v.Mixed) != "x" && show(v.Mixed) != score, show(v.Pair) != "5" && show(v.Pair) != score, show(v.Codes) != score && show(v.Codes) != code: + t.Fatalf("Mixed %s, Pair %s, Codes %s beside score %s and code %s: want each the literal or the draw of the column it reads", show(v.Mixed), show(v.Pair), show(v.Codes), score, code) + } switch { case v.Code == nil: nils["code"]++ @@ -209,6 +234,9 @@ func TestFakeStructErrors(t *testing.T) { {&struct { A *bool `fake:"src.score"` }{}, ".A (*bool): {int(1,9)} prints an integer, not a boolean"}, + {&struct { + A int64 `fake:"mid.score"` + }{}, "can draw null, which int64 cannot hold; make it *int64"}, {&struct { A int `fake:"{digits(3)}"` }{}, ".A (int): {digits(3)} prints text, not an integer"},