-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: generate simple Create test cases
- Loading branch information
Showing
15 changed files
with
795 additions
and
1,670 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package plugin | ||
|
||
import ( | ||
"github.com/stoewer/go-strcase" | ||
"go.einride.tech/aip/reflect/aipreflect" | ||
"google.golang.org/protobuf/compiler/protogen" | ||
"google.golang.org/protobuf/reflect/protoreflect" | ||
) | ||
|
||
func hasParent(resource *aipreflect.ResourceDescriptor) bool { | ||
if len(resource.Names) == 0 { | ||
return false | ||
} | ||
return len(resource.Names[0].Ancestors) > 0 | ||
} | ||
|
||
func findMethod(service *protogen.Service, methodName protoreflect.Name) (*protogen.Method, bool) { | ||
for _, method := range service.Methods { | ||
if method.Desc.Name() == methodName { | ||
return method, true | ||
} | ||
} | ||
return nil, false | ||
} | ||
|
||
func hasUserSettableID(resource *aipreflect.ResourceDescriptor, method protoreflect.MethodDescriptor) bool { | ||
idField := strcase.LowerCamelCase(resource.Singular.UpperCamelCase()) + "_id" | ||
return hasField(method.Input(), protoreflect.Name(idField)) | ||
} | ||
|
||
func hasField(message protoreflect.MessageDescriptor, field protoreflect.Name) bool { | ||
f := message.Fields().ByName(field) | ||
return f != nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package plugin | ||
|
||
import ( | ||
"go.einride.tech/aip/reflect/aipreflect" | ||
"google.golang.org/protobuf/compiler/protogen" | ||
) | ||
|
||
type methodCreate struct { | ||
resource *aipreflect.ResourceDescriptor | ||
method *protogen.Method | ||
|
||
parent string | ||
message string | ||
userSettableID string | ||
} | ||
|
||
func (m *methodCreate) Generate(f *protogen.GeneratedFile, response string, err string, assign string) { | ||
f.P(response, ", ", err, " ", assign, " fx.service.", m.method.GoName, "(fx.ctx, &", m.method.Input.GoIdent, "{") | ||
if hasParent(m.resource) { | ||
f.P("Parent: ", m.parent, ",") | ||
} | ||
|
||
switch { | ||
case m.message != "": | ||
f.P(m.resource.Singular.UpperCamelCase(), ": ", m.message, ",") | ||
case !hasParent(m.resource): | ||
f.P(m.resource.Singular.UpperCamelCase(), ": fx.Create(),") | ||
default: | ||
f.P(m.resource.Singular.UpperCamelCase(), ": fx.Create(", m.parent, "),") | ||
} | ||
|
||
if hasUserSettableID(m.resource, m.method.Desc) && m.userSettableID != "" { | ||
f.P(m.resource.Singular.UpperCamelCase(), "Id: ", m.userSettableID, ",") | ||
} | ||
f.P("})") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,153 @@ | ||
package plugin | ||
|
||
import ( | ||
"strconv" | ||
|
||
"go.einride.tech/aip/reflect/aipreflect" | ||
"google.golang.org/protobuf/compiler/protogen" | ||
"google.golang.org/protobuf/reflect/protoreflect" | ||
) | ||
|
||
type resourceGenerator struct { | ||
service *protogen.Service | ||
resource *aipreflect.ResourceDescriptor | ||
message protoreflect.MessageDescriptor | ||
message *protogen.Message | ||
} | ||
|
||
func (r *resourceGenerator) Generate(f *protogen.GeneratedFile) error { | ||
r.generateFixture(f) | ||
r.generateTestMethod(f) | ||
testCases := r.collectTestCases() | ||
r.generateTestMethod(f, testCases) | ||
r.generateTestCases(f, testCases) | ||
r.generateParentMethods(f) | ||
r.generateSkip(f) | ||
return nil | ||
} | ||
|
||
func (r *resourceGenerator) generateFixture(f *protogen.GeneratedFile) { | ||
context := f.QualifiedGoIdent(protogen.GoIdent{ | ||
GoName: "Context", | ||
GoImportPath: "context", | ||
}) | ||
service := f.QualifiedGoIdent(protogen.GoIdent{ | ||
GoName: r.service.GoName + "Server", | ||
GoImportPath: r.service.Methods[0].Input.GoIdent.GoImportPath, | ||
}) | ||
|
||
f.P("type ", r.resource.Type.Type(), " struct {") | ||
f.P("ctx ", context) | ||
f.P("service ", service) | ||
f.P("currParent int") | ||
f.P() | ||
|
||
if hasParent(r.resource) { | ||
f.P("// The parents to use when creating resources.") | ||
f.P("// At least one parent needs to be set. Depending on methods available on the resource,") | ||
f.P("// more may be required. If insufficient number of parents are") | ||
f.P("// provided the test will fail.") | ||
f.P("Parents []string") | ||
} | ||
_, hasCreate := r.resource.Methods[aipreflect.MethodTypeCreate] | ||
if hasCreate { | ||
f.P("// Create should return a resource which is valid to create, ie.") | ||
f.P("// all required fields set.") | ||
if hasParent(r.resource) { | ||
f.P("Create func(parent string) *", r.message.GoIdent) | ||
} else { | ||
f.P("Create func() *", r.message.GoIdent) | ||
} | ||
} | ||
|
||
f.P("// Patterns of tests to skip.") | ||
f.P("// For example if a service has a Get method:") | ||
f.P("// Skip: [\"Get\"] will skip all tests for Get.") | ||
f.P("// Skip: [\"Get/persisted\"] will only skip the subtest called \"persisted\" of Get.") | ||
f.P("Skip []string") | ||
f.P("}") | ||
f.P() | ||
} | ||
|
||
func (r *resourceGenerator) generateTestMethod(f *protogen.GeneratedFile) { | ||
testing := f.QualifiedGoIdent(protogen.GoIdent{ | ||
func (r *resourceGenerator) generateTestMethod(f *protogen.GeneratedFile, testCases []testCase) { | ||
testingT := f.QualifiedGoIdent(protogen.GoIdent{ | ||
GoName: "T", | ||
GoImportPath: "testing", | ||
}) | ||
|
||
f.P("func (fx *", r.resource.Type.Type(), ") test(t *", testing, ") {") | ||
f.P("func (fx *", r.resource.Type.Type(), ") test(t *", testingT, ") {") | ||
for _, tc := range testCases { | ||
if !tc.enabled { | ||
continue | ||
} | ||
f.P("t.Run(", strconv.Quote(tc.Name()), ", fx.", tc.FuncName(), ")") | ||
} | ||
f.P("}") | ||
f.P() | ||
} | ||
|
||
func (r *resourceGenerator) generateTestCases(f *protogen.GeneratedFile, testCases []testCase) { | ||
testingT := f.QualifiedGoIdent(protogen.GoIdent{ | ||
GoName: "T", | ||
GoImportPath: "testing", | ||
}) | ||
for _, tc := range testCases { | ||
if !tc.enabled { | ||
continue | ||
} | ||
f.P("func (fx *", r.resource.Type.Type(), ")", tc.FuncName(), "(t *", testingT, ") {") | ||
tc.fn(f) | ||
f.P("}") | ||
f.P() | ||
} | ||
} | ||
|
||
func (r *resourceGenerator) generateSkip(f *protogen.GeneratedFile) { | ||
testingT := f.QualifiedGoIdent(protogen.GoIdent{ | ||
GoName: "T", | ||
GoImportPath: "testing", | ||
}) | ||
stringsContains := f.QualifiedGoIdent(protogen.GoIdent{ | ||
GoName: "Contains", | ||
GoImportPath: "strings", | ||
}) | ||
f.P("func (fx *", r.resource.Type.Type(), ") maybeSkip(t *", testingT, ") {") | ||
f.P("for _, skip := range fx.Skip {") | ||
f.P("if ", stringsContains, "(t.Name(), skip) {") | ||
f.P("t.Skip(\"skipped because of .Skip\")") | ||
f.P("}") | ||
f.P("}") | ||
f.P("}") | ||
f.P() | ||
} | ||
|
||
func (r *resourceGenerator) generateParentMethods(f *protogen.GeneratedFile) { | ||
if !hasParent(r.resource) { | ||
return | ||
} | ||
testingT := f.QualifiedGoIdent(protogen.GoIdent{ | ||
GoName: "T", | ||
GoImportPath: "testing", | ||
}) | ||
f.P("func (fx *", r.resource.Type.Type(), ") nextParent(t *", testingT, ", pristine bool) string {") | ||
f.P("if pristine {") | ||
f.P("fx.currParent++") | ||
f.P("}") | ||
f.P("if fx.currParent >= len(fx.Parents) {") | ||
f.P("t.Fatal(\"need at least\", fx.currParent + 1, \"parents\")") | ||
f.P("}") | ||
f.P("return fx.Parents[fx.currParent]") | ||
f.P("}") | ||
f.P() | ||
f.P("func (fx *", r.resource.Type.Type(), ") peekNextParent(t *", testingT, ") string {") | ||
f.P("next := fx.currParent + 1") | ||
f.P("if next >= len(fx.Parents) {") | ||
f.P("t.Fatal(\"need at least\", next +1, \"parents\")") | ||
f.P("}") | ||
f.P("return fx.Parents[next]") | ||
f.P("}") | ||
f.P() | ||
} | ||
|
||
func (r *resourceGenerator) collectTestCases() []testCase { | ||
return []testCase{ | ||
r.createTestCase(), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package plugin | ||
|
||
import "google.golang.org/protobuf/compiler/protogen" | ||
|
||
type testCase struct { | ||
enabled bool | ||
name string | ||
fn func(file *protogen.GeneratedFile) | ||
} | ||
|
||
func disabledTestCase() testCase { | ||
return testCase{} | ||
} | ||
|
||
func newTestCase(name string, fn func(f *protogen.GeneratedFile)) testCase { | ||
return testCase{ | ||
enabled: true, | ||
name: name, | ||
fn: fn, | ||
} | ||
} | ||
|
||
func (t testCase) Name() string { | ||
return t.name | ||
} | ||
|
||
func (t testCase) FuncName() string { | ||
return "test" + t.name | ||
} |
Oops, something went wrong.