codegen: make test fixture factory function intelligent about what fields and values it assigns

This commit is contained in:
2026-02-14 18:33:41 -08:00
parent b33127db6c
commit ae036d15f2

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"strings"
"github.com/jinzhu/inflection" "github.com/jinzhu/inflection"
@@ -11,6 +12,80 @@ import (
"git.offline-twitter.com/offline-labs/gas-stack/pkg/textutils" "git.offline-twitter.com/offline-labs/gas-stack/pkg/textutils"
) )
// SampleValue returns a deterministic value of the appropriate type for the given column.
func SampleValue(c pkgschema.Column, offset int) ast.Expr {
switch c.Type {
case "integer", "int":
if strings.HasPrefix(c.Name, "is_") || strings.HasPrefix(c.Name, "has_") {
// Boolean case
if offset%2 == 0 {
return ast.NewIdent("false")
}
return ast.NewIdent("true")
} else if strings.HasSuffix(c.Name, "_at") {
// Timestamp case
return &ast.CallExpr{
Fun: ast.NewIdent("TimestampFromUnix"),
Args: []ast.Expr{
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", 10000+offset)},
},
}
} else {
// Regular integer case
return &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", 10+offset)}
}
case "text":
if offset == 0 {
return &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", "asdf")}
}
return &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", fmt.Sprintf("asdf%d", offset))}
case "real":
return &ast.BasicLit{Kind: token.FLOAT, Value: fmt.Sprintf("%.2f", 1.23+float64(offset))}
case "blob":
return &ast.CompositeLit{
Type: &ast.ArrayType{
Elt: ast.NewIdent("byte"),
},
Elts: func() []ast.Expr {
ret := []ast.Expr{
&ast.BasicLit{Kind: token.INT, Value: "72"}, // 'H'
&ast.BasicLit{Kind: token.INT, Value: "105"}, // 'i'
&ast.BasicLit{Kind: token.INT, Value: "33"}, // '!'
}
for range offset {
ret = append(ret,
&ast.BasicLit{Kind: token.INT, Value: "33"}, // '!'
)
}
return ret
}(),
}
default:
panic("Unrecognized sqlite column type: " + c.Type)
}
}
// UpdateTestFields returns the columns for which the test generator should assign its own
// sample values, in the MakeXyz() factory and in the create/update test: the same columns
// that GenerateSaveItemFunc's "update" branch writes to, minus foreign keys (which can't be
// given plausible values here) and the auto-managed "created_at"/"updated_at" columns.
func UpdateTestFields(tbl pkgschema.Table) (ret []pkgschema.Column) {
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
for _, c := range tbl.Columns {
if c.Name == "rowid" || c.IsPrimaryKey || c.IsForeignKey {
continue
}
if c.Name == "created_at" && hasCreatedAt {
continue
}
if c.Name == "updated_at" && hasUpdatedAt {
continue
}
ret = append(ret, c)
}
return
}
// GenerateModelTestAST produces an AST for a starter test file for a given model. // GenerateModelTestAST produces an AST for a starter test file for a given model.
func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodName string) *ast.File { func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodName string) *ast.File {
packageName := "db" packageName := "db"
@@ -18,6 +93,9 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
makeHelperName := ast.NewIdent("Make" + tbl.GoTypeName) makeHelperName := ast.NewIdent("Make" + tbl.GoTypeName)
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
updateTestFields := UpdateTestFields(tbl)
// func MakeItem() Item { return Item{} } // func MakeItem() Item { return Item{} }
makeItemFunc := &ast.FuncDecl{ makeItemFunc := &ast.FuncDecl{
Name: makeHelperName, Name: makeHelperName,
@@ -35,24 +113,15 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
Results: []ast.Expr{ Results: []ast.Expr{
&ast.CompositeLit{ &ast.CompositeLit{
Type: ast.NewIdent(tbl.GoTypeName), Type: ast.NewIdent(tbl.GoTypeName),
Elts: []ast.Expr{ Elts: func() (ret []ast.Expr) {
&ast.KeyValueExpr{ for _, c := range updateTestFields {
Key: ast.NewIdent("Data"), ret = append(ret, &ast.KeyValueExpr{
Value: &ast.CompositeLit{ Key: ast.NewIdent(c.GoFieldName()),
Type: &ast.ArrayType{ Value: SampleValue(c, 0),
Elt: ast.NewIdent("byte"), })
}, }
Elts: []ast.Expr{}, return
}, }(),
},
&ast.KeyValueExpr{
Key: ast.NewIdent("Description"),
Value: &ast.BasicLit{
Kind: token.STRING,
Value: `""`,
},
},
},
}, },
}, },
}, },
@@ -62,13 +131,8 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
testObj := ast.NewIdent(textutils.CamelToPascal(tbl.GoTypeName)) testObj := ast.NewIdent(textutils.CamelToPascal(tbl.GoTypeName))
testObj2 := ast.NewIdent(textutils.CamelToPascal(tbl.GoTypeName) + "2") testObj2 := ast.NewIdent(textutils.CamelToPascal(tbl.GoTypeName) + "2")
fieldName := ast.NewIdent("Description") // TODO
description1 := "an item"
description2 := "a big item"
testDB := ast.NewIdent("TestDB") testDB := ast.NewIdent("TestDB")
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
makeDeepEqual := func(obj1 *ast.Ident, obj2 *ast.Ident) *ast.IfStmt { makeDeepEqual := func(obj1 *ast.Ident, obj2 *ast.Ident) *ast.IfStmt {
return &ast.IfStmt{ return &ast.IfStmt{
Init: &ast.AssignStmt{ Init: &ast.AssignStmt{
@@ -236,23 +300,22 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
Tok: token.DEFINE, Tok: token.DEFINE,
Rhs: []ast.Expr{&ast.CallExpr{Fun: makeHelperName, Args: nil}}, Rhs: []ast.Expr{&ast.CallExpr{Fun: makeHelperName, Args: nil}},
}, },
// item.Description = "an item" // item.Description = <sample value, offset 1>
&ast.AssignStmt{ // ...one assignment per updatable field
}
for _, c := range updateTestFields {
stmts = append(stmts, &ast.AssignStmt{
Lhs: []ast.Expr{ Lhs: []ast.Expr{
&ast.SelectorExpr{ &ast.SelectorExpr{
X: testObj, X: testObj,
Sel: ast.NewIdent("Description"), Sel: ast.NewIdent(c.GoFieldName()),
}, },
}, },
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{ Rhs: []ast.Expr{SampleValue(c, 1)},
&ast.BasicLit{ })
Kind: token.STRING, }
Value: fmt.Sprintf("%q", description1), stmts = append(stmts,
},
},
},
// TestDB.SaveItem(&item), possibly with error check // TestDB.SaveItem(&item), possibly with error check
&ast.ExprStmt{X: func() *ast.CallExpr { &ast.ExprStmt{X: func() *ast.CallExpr {
mainExpr := &ast.CallExpr{ mainExpr := &ast.CallExpr{
@@ -274,7 +337,7 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
Fun: &ast.SelectorExpr{X: ast.NewIdent("require"), Sel: ast.NewIdent("NotZero")}, Fun: &ast.SelectorExpr{X: ast.NewIdent("require"), Sel: ast.NewIdent("NotZero")},
Args: []ast.Expr{ast.NewIdent("t"), &ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}}, Args: []ast.Expr{ast.NewIdent("t"), &ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}},
}}, }},
} )
// After create: assert timestamps are set // After create: assert timestamps are set
if hasCreatedAt { if hasCreatedAt {
@@ -305,14 +368,19 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
stmts = append(stmts, stmts = append(stmts,
BlankLine(), BlankLine(),
Comment("Update"), Comment("Update"),
)
// item.Description = "a big item" // item.Description = <sample value, offset 2>
&ast.AssignStmt{ // ...one assignment per updatable field
Lhs: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: fieldName}}, for _, c := range updateTestFields {
stmts = append(stmts, &ast.AssignStmt{
Lhs: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent(c.GoFieldName())}},
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", description2)}}, Rhs: []ast.Expr{SampleValue(c, 2)},
}, })
}
stmts = append(stmts,
// TestDB.SaveItem(&item) // TestDB.SaveItem(&item)
&ast.ExprStmt{X: &ast.CallExpr{ &ast.ExprStmt{X: &ast.CallExpr{
Fun: &ast.SelectorExpr{X: testDB, Sel: ast.NewIdent("Save" + tbl.GoTypeName)}, Fun: &ast.SelectorExpr{X: testDB, Sel: ast.NewIdent("Save" + tbl.GoTypeName)},