Skip to content

Commit

Permalink
feat: new way to bootstrap the tests
Browse files Browse the repository at this point in the history
We have noticed historically, that when people adds new
resources, it's easy to forget to implement the newly
generated AIP tests, either directly or later.

This commit tries to solve that issue by introducing a
main entrypoint to execute the tests, which takes a interface
that the user needs to implement.

When a new resource is added, the interface will be extended with
another method that the user needs to implement, if not, a compilation
error would be raised.

The user can choose to return `nil` to indicate that it can't be
implemented right directly.

All tests will still be executed, but if not implemented it will be
skipped, this to show it's available but not implemented.

This commit is backwards compatible with the current test setup.

For example usage, see `examples/`.
  • Loading branch information
thall committed Oct 9, 2024
1 parent 543dc9e commit 5cd553a
Show file tree
Hide file tree
Showing 29 changed files with 1,561 additions and 1 deletion.
19 changes: 19 additions & 0 deletions example/freight_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,22 @@ func Test_FreightService(t *testing.T) {
},
})
}

func Test_FreightService_AlternativeSetup(t *testing.T) {
// Even though no implementation exists, the tests will pass but be skipped.
examplefreightv1.TestFreightService(t, &aipTests{})
}

type aipTests struct{}

var _ examplefreightv1.FreightServiceTestSuiteConfigProvider = &aipTests{}

func (a aipTests) ShipperTestSuiteConfig(_ *testing.T) *examplefreightv1.FreightServiceShipperTestSuiteConfig {
// Returns nil to indicate that it's not ready to be tested.
return nil
}

func (a aipTests) SiteTestSuiteConfig(_ *testing.T) *examplefreightv1.FreightServiceSiteTestSuiteConfig {
// Returns nil to indicate that it's not ready to be tested.
return nil
}
13 changes: 12 additions & 1 deletion internal/plugin/name.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,20 @@ func serviceTestSuiteName(service protoreflect.ServiceDescriptor) string {
return string(service.Name()) + "TestSuite"
}

func serviceResourceName(
service protoreflect.ServiceDescriptor,
resource *annotations.ResourceDescriptor,
) string {
return string(service.Name()) + resourceType(resource)
}

func resourceTestSuiteConfigName(
service protoreflect.ServiceDescriptor,
resource *annotations.ResourceDescriptor,
) string {
return string(service.Name()) + resourceType(resource) + "TestSuiteConfig"
return serviceResourceName(service, resource) + "TestSuiteConfig"
}

func serviceTestConfigSupplierName(service protoreflect.ServiceDescriptor) string {
return string(service.Name()) + "TestSuiteConfigProvider"
}
71 changes: 71 additions & 0 deletions internal/plugin/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ type serviceGenerator struct {
}

func (s *serviceGenerator) Generate(f *protogen.GeneratedFile) error {
s.generateConfigProvider(f)
s.generateMainTestFunction(f)
s.generateTestFunctions(f)
s.generateFixture(f)
s.generateTestMethods(f)
for i, resource := range s.resources {
Expand All @@ -30,6 +33,74 @@ func (s *serviceGenerator) Generate(f *protogen.GeneratedFile) error {
return nil
}

func (s *serviceGenerator) generateConfigProvider(f *protogen.GeneratedFile) {
t := f.QualifiedGoIdent(protogen.GoIdent{
GoName: "T",
GoImportPath: "testing",
})
name := serviceTestConfigSupplierName(s.service.Desc)
f.P("// ", name, " is the interface to implement to decide which resources")
f.P("// that should be tested and how it's configured.")
f.P("type ", name, " interface {")
for _, resource := range s.resources {
resourceFx := resourceTestSuiteConfigName(s.service.Desc, resource)
f.P("// ", resourceFx, " should return a config, or nil, which means that the tests will be skipped.")
f.P(resourceType(resource), "TestSuiteConfig(t *", t, ") *", resourceFx, "")
}
f.P("}")
f.P()
}

func (s *serviceGenerator) generateMainTestFunction(f *protogen.GeneratedFile) {
t := f.QualifiedGoIdent(protogen.GoIdent{
GoName: "T",
GoImportPath: "testing",
})
funcName := "Test" + string(s.service.Desc.Name())
f.P("// ", funcName, " is the main entrypoint for starting the AIP tests.")
f.P("func ", funcName, "(t *", t, ",s ", serviceTestConfigSupplierName(s.service.Desc), ") {")
for _, resource := range s.resources {
name := resourceTestSuiteConfigName(s.service.Desc, resource)
f.P("test", name, "(t, s)")
}
f.P("}")
f.P()
}

func (s *serviceGenerator) generateTestFunctions(f *protogen.GeneratedFile) {
t := f.QualifiedGoIdent(protogen.GoIdent{
GoName: "T",
GoImportPath: "testing",
})
context := f.QualifiedGoIdent(protogen.GoIdent{
GoName: "Context",
GoImportPath: "context",
})
background := f.QualifiedGoIdent(protogen.GoIdent{
GoName: "Background",
GoImportPath: "context",
})
for _, resource := range s.resources {
name := resourceTestSuiteConfigName(s.service.Desc, resource)
f.P("func test", name, "(t *", t, ",s ", serviceTestConfigSupplierName(s.service.Desc), ") {")
f.P("t.Run(", strconv.Quote(resourceType(resource)), ", func(t *", t, ") {")
f.P("config := s.", resourceType(resource), "TestSuiteConfig(t)")
f.P("if (config == nil) {")
f.P("t.Skip(\"Method ", resourceType(resource), "TestSuiteConfig not implemented\")")
f.P("}")
f.P("if (config.Service == nil) {")
f.P("t.Skip(\"Method ", name, ".Service() not implemented\")")
f.P("}")
f.P("if (config.Context == nil) {")
f.P("config.Context = func() ", context, " { return ", background, "() }")
f.P("}")
f.P("config.test(t)")
f.P("})")
f.P("}")
f.P()
}
}

func (s *serviceGenerator) generateFixture(f *protogen.GeneratedFile) {
testingT := f.QualifiedGoIdent(protogen.GoIdent{
GoName: "T",
Expand Down
47 changes: 47 additions & 0 deletions proto/gen/einride/example/freight/v1/freight_service_aiptest.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5cd553a

Please sign in to comment.