Files
sandwich-hime/sando/api_contract_test.go
T

107 lines
2.8 KiB
Go

// SPDX-License-Identifier: Apache-2.0
package sando
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"sort"
"strings"
"testing"
)
func TestV1PublicAPIContract(t *testing.T) {
t.Parallel()
entries, err := os.ReadDir(".")
if err != nil {
t.Fatal(err)
}
fileSet := token.NewFileSet()
var files []*ast.File
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
continue
}
file, err := parser.ParseFile(fileSet, entry.Name(), nil, 0)
if err != nil {
t.Fatalf("parse %s: %v", entry.Name(), err)
}
files = append(files, file)
}
configuration := types.Config{Importer: importer.Default()}
checked, err := configuration.Check("gamertan.com/sandwich-hime/sando", fileSet, files, nil)
if err != nil {
t.Fatal(err)
}
qualifier := func(pkg *types.Package) string {
if pkg == nil || pkg.Path() == checked.Path() {
return ""
}
return pkg.Name()
}
var actual []string
for _, name := range checked.Scope().Names() {
if !token.IsExported(name) {
continue
}
object := checked.Scope().Lookup(name)
switch object := object.(type) {
case *types.Const:
actual = append(actual, fmt.Sprintf("const %s = %s", object.Name(), object.Val().ExactString()))
case *types.TypeName:
named, ok := object.Type().(*types.Named)
if !ok {
actual = append(actual, types.ObjectString(object, qualifier))
break
}
if structure, ok := named.Underlying().(*types.Struct); ok {
var fields []string
for index := 0; index < structure.NumFields(); index++ {
field := structure.Field(index)
if field.Exported() {
fields = append(fields, field.Name()+" "+types.TypeString(field.Type(), qualifier))
}
}
if len(fields) == 0 {
actual = append(actual, "type "+object.Name()+" struct{ /* opaque */ }")
} else {
actual = append(actual, "type "+object.Name()+" struct{"+strings.Join(fields, "; ")+"}")
}
} else {
actual = append(actual, types.ObjectString(object, qualifier))
}
default:
actual = append(actual, types.ObjectString(object, qualifier))
}
typeName, ok := object.(*types.TypeName)
if !ok {
continue
}
named, ok := typeName.Type().(*types.Named)
if !ok {
continue
}
for index := 0; index < named.NumMethods(); index++ {
method := named.Method(index)
if method.Exported() {
actual = append(actual, types.ObjectString(method, qualifier))
}
}
}
sort.Strings(actual)
got := strings.Join(actual, "\n") + "\n"
want, err := os.ReadFile("testdata/public-api-v1.txt")
if err != nil {
t.Fatal(err)
}
want = []byte(strings.TrimPrefix(string(want), "# SPDX-License-Identifier: Apache-2.0\n\n"))
if string(want) != got {
t.Fatalf("v1 public API drifted\n--- committed contract ---\n%s--- observed API ---\n%s", want, got)
}
}