Files
ReleaseKit/internal/apicheck/main_test.go

238 lines
7.9 KiB
Go

package main
import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"strings"
"testing"
)
func TestExportedContractBoundaries(t *testing.T) {
tests := []struct {
name string
source string
wantError string
}{
{
name: "leaked implementation type",
source: `package facade
import "example.com/implementation"
func New() *implementation.State { return nil }
`,
wantError: "exposes named type example.com/implementation.State",
},
{
name: "alias to implementation type",
source: `package facade
import "example.com/implementation"
type Alias = implementation.State
`,
wantError: "exposes named type example.com/implementation.State",
},
{
name: "callback leaks implementation type",
source: `package facade
import "example.com/implementation"
type Callback func() *implementation.State
`,
wantError: "exposes named type example.com/implementation.State",
},
{
name: "generic constraint leaks implementation type",
source: `package facade
import "example.com/implementation"
type Box[T interface{ Value() *implementation.State }] struct { value T }
`,
wantError: "exposes named type example.com/implementation.State",
},
{
name: "generic instantiation leaks implementation type",
source: `package facade
import "example.com/implementation"
type Box[T any] struct { value T }
func New() Box[implementation.State] { return Box[implementation.State]{} }
`,
wantError: "exposes named type example.com/implementation.State",
},
{
name: "promoted implementation method",
source: `package facade
import "example.com/implementation"
type state = implementation.State
type Handle struct { state }
`,
wantError: "promotes method Promoted from example.com/implementation",
},
{
name: "promoted pointer implementation method",
source: `package facade
import "example.com/implementation"
type state = implementation.State
type Handle struct { state }
`,
wantError: "promotes method PointerPromoted from example.com/implementation",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
violations := checkFixture(t, test.source)
if !strings.Contains(strings.Join(violations, "\n"), test.wantError) {
t.Fatalf("checkExports() = %v, want an error containing %q", violations, test.wantError)
}
})
}
}
func TestPrivateImplementationFieldAndSelectedStandardTypes(t *testing.T) {
source := `package facade
import (
"context"
"example.com/implementation"
)
type Handle struct { state *implementation.State }
func Open(context.Context) *Handle { return nil }
`
if violations := checkFixture(t, source); len(violations) != 0 {
t.Fatalf("private implementation field and selected standard type were rejected: %v", violations)
}
}
func TestValidationDiagnosticTypesBelongToFacade(t *testing.T) {
valid := `package facade
type ValidationCheck string
type ValidationFinding struct{}
type FileSizeMeasurement struct{}
type ValidationError struct {
Check ValidationCheck
Findings []ValidationFinding
Measurement *FileSizeMeasurement
Cause error
}
`
pkg := typecheck(t, token.NewFileSet(), modulePath, valid)
if violations := checkValidationTypes(pkg); len(violations) != 0 {
t.Fatalf("checkValidationTypes() = %v, want facade-owned diagnostics", violations)
}
implementation := typecheck(t, token.NewFileSet(), "example.com/implementation", `package implementation
type State string
`)
fset := token.NewFileSet()
fixtureImporter := singlePackageImporter{path: "example.com/implementation", pkg: implementation}
invalid := strings.Replace(valid, "type ValidationCheck string", "import \"example.com/implementation\"\ntype ValidationCheck = implementation.State", 1)
pkg = typecheckWithImporter(t, fset, modulePath, invalid, fixtureImporter)
if violations := checkValidationTypes(pkg); !strings.Contains(strings.Join(violations, "\n"), "ValidationCheck is not a facade-owned named type") {
t.Fatalf("checkValidationTypes() = %v, want ownership violation", violations)
}
}
func TestActualRepositoryBoundaries(t *testing.T) {
if err := checkRepository(t.Context(), "../.."); err != nil {
t.Fatal(err)
}
}
func TestInternalPackagesCannotImportPublicFacades(t *testing.T) {
packages := []listedPackage{
{ImportPath: modulePath + "/internal/operations", Imports: []string{modulePath}},
{ImportPath: modulePath + "/cmd/releasekit/internal/cli", Imports: []string{modulePath + "/buildinfo"}},
{ImportPath: modulePath},
{ImportPath: modulePath + "/buildinfo"},
}
err := checkImportDirections(packages)
if err == nil ||
!strings.Contains(err.Error(), "internal package "+modulePath+"/internal/operations imports public or command package "+modulePath) ||
!strings.Contains(err.Error(), "internal package "+modulePath+"/cmd/releasekit/internal/cli imports public or command package "+modulePath+"/buildinfo") {
t.Fatalf("checkImportDirections() error = %v, want internal-to-facade violation", err)
}
}
func TestRuntimeFacadeCannotDependOnExternalPackages(t *testing.T) {
packages := []listedPackage{
{ImportPath: modulePath + "/buildinfo", Imports: []string{"example.com/runtime"}},
{ImportPath: "example.com/runtime"},
}
err := checkImportDirections(packages)
if err == nil || !strings.Contains(err.Error(), "buildinfo depends on non-runtime package example.com/runtime") {
t.Fatalf("checkImportDirections() error = %v, want runtime dependency violation", err)
}
}
func checkFixture(t *testing.T, facadeSource string) []string {
t.Helper()
fset := token.NewFileSet()
implementation := typecheck(t, fset, "example.com/implementation", `package implementation
type State struct{}
func (State) Promoted() {}
func (*State) PointerPromoted() {}
`)
fixtureImporter := singlePackageImporter{path: "example.com/implementation", pkg: implementation}
facade := typecheckWithImporter(t, fset, "example.com/facade", facadeSource, fixtureImporter)
return checkExports(facade)
}
type singlePackageImporter struct {
path string
pkg *types.Package
}
func (i singlePackageImporter) Import(path string) (*types.Package, error) {
if path == i.path {
return i.pkg, nil
}
return importer.Default().Import(path)
}
func typecheck(t *testing.T, fset *token.FileSet, path, source string) *types.Package {
t.Helper()
return typecheckWithImporter(t, fset, path, source, importer.Default())
}
func typecheckWithImporter(t *testing.T, fset *token.FileSet, path, source string, imports types.Importer) *types.Package {
t.Helper()
file, err := parser.ParseFile(fset, path+".go", source, 0)
if err != nil {
t.Fatal(err)
}
var errors []error
config := types.Config{
Importer: imports,
Error: func(err error) {
errors = append(errors, err)
},
}
pkg, err := config.Check(path, fset, []*ast.File{file}, nil)
if err != nil {
t.Fatalf("typecheck %s: %v (%v)", path, err, errors)
}
return pkg
}
func TestCommandInternalCompositionPreservesLibraryBoundaries(t *testing.T) {
for _, test := range []struct {
name, from, to string
reject bool
}{
{"command helpers", "/cmd/releasekit/internal/cli/parser", "/cmd/releasekit/internal/cli/settings", false},
{"other executable helpers", "/cmd/future/internal/parser", "/cmd/future/internal/settings", false},
{"helper root", "/cmd/releasekit/internal", "/cmd/releasekit/internal/cli", false},
{"controller uses facade", "/cmd/releasekit", "", false},
{"library imports controller", "/internal/build", "/cmd/releasekit", true},
{"library imports helper", "/internal/build", "/cmd/releasekit/internal/cli", true},
{"helper imports facade", "/cmd/releasekit/internal/cli", "", true},
{"helper imports runtime facade", "/cmd/releasekit/internal/cli", "/buildinfo", true},
{"helper imports controller", "/cmd/releasekit/internal/cli", "/cmd/releasekit", true},
} {
t.Run(test.name, func(t *testing.T) {
err := checkImportDirections([]listedPackage{{ImportPath: modulePath + test.from, Imports: []string{modulePath + test.to}}})
if (err != nil) != test.reject {
t.Fatalf("dependency %s -> %s: %v", test.from, test.to, err)
}
})
}
}