Files
mesh-controller/internal/inventory/chain_source_test.go
T

152 lines
5.8 KiB
Go

package inventory
import (
"go/ast"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
)
// The tables of the chain, and the column, that the controller's store no longer holds (novox/hq ADR 0408 §8, issue 1096).
var chainTables = []string{"build", "build_request", "release_plan", "batched_merge", "assignment", "pending_assignment"}
// sqlStatement is what makes a literal a statement of the store: it opens with a verb of SQL.
var sqlStatement = regexp.MustCompile(`(?is)^\s*(select|insert|update|delete|alter|drop|create|with|truncate|from|join|where|and|set|values)\b|\b(select\s.+\sfrom|insert\s+into|delete\s+from)\b`)
// **No code names a table of the chain** (acceptance 12, issue 1096), found by reading the sources: a string literal of any
// non-test file of internal/ or cmd/ that names one after from, into, update, join, delete from, references or table —
// build_gate and port_assignment are other words —. This is the static half of the proof; the
// integration test reads the catalog.
func TestNoCodeNamesATableOfTheChain(t *testing.T) {
names := regexp.MustCompile(`(?i)\b(from|into|update|join|delete\s+from|references|table)\s+(` + strings.Join(chainTables, "|") + `)\b`)
var found []string
for _, root := range []string{"../../internal", "../../cmd"} {
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() && d.Name() == "vendor" {
return filepath.SkipDir
}
if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
if err != nil {
return err
}
ast.Inspect(file, func(n ast.Node) bool {
lit, ok := n.(*ast.BasicLit)
if !ok || lit.Kind != token.STRING {
return true
}
text, err := strconv.Unquote(lit.Value)
if err != nil {
return true
}
if !sqlStatement.MatchString(text) {
return true // prose that happens to say "from assignment" is no statement
}
if m := names.FindString(text); m != "" {
found = append(found, filepath.ToSlash(path)+": "+strings.Join(strings.Fields(m), " "))
}
return true
})
return nil
})
if err != nil {
t.Fatal(err)
}
}
if len(found) > 0 {
t.Fatalf("code still names a table of the chain, which the store no longer holds:\n %s", strings.Join(found, "\n "))
}
}
// The scan sees what it says it sees: planted, a string naming a table is found, and a comment or a word beside is not.
func TestTheSourceScanFindsATablePlanted(t *testing.T) {
names := regexp.MustCompile(`(?i)\b(from|into|update|join|delete\s+from|references|table)\s+(` + strings.Join(chainTables, "|") + `)\b`)
for text, want := range map[string]bool{
"select id from build where id = $1": true,
"insert into assignment (node, module)": true,
"select 1 from build_gate": false,
"delete from port_assignment": false,
"select a from build_request a join build b": true,
"an assignment from build to build": true,
"the assignment generation": false,
"select count(*) from pending_assignment": true,
} {
if got := names.MatchString(text); got != want {
t.Errorf("%q: %v, want %v", text, got, want)
}
}
}
// **Migration 105 drops the tables of the chain, behind its guard** (issue 1096; it was the last until issue 1145 added 106): it names each table, the column and
// the function, drops no build_gate, and raises before it drops anything.
func TestMigration105DropsTheTablesOfTheChain(t *testing.T) {
migrations, err := Migrations()
if err != nil {
t.Fatal(err)
}
var last struct{ Number int }
for _, m := range migrations {
if m.Number == 105 {
last.Number = m.Number
}
}
if last.Number != 105 {
t.Fatal("there is no migration 105")
}
raw, err := os.ReadFile(filepath.Join("migrations", migrationFile(t, last.Number)))
if err != nil {
t.Fatal(err)
}
var code []string
for _, line := range strings.Split(string(raw), "\n") {
if !strings.HasPrefix(strings.TrimSpace(line), "--") {
code = append(code, line)
}
}
sql := strings.ToLower(strings.Join(code, "\n"))
for _, table := range append(append([]string{}, chainTables...), "chain_moved") {
if !strings.Contains(sql, "drop table "+table+";") {
t.Errorf("migration %d does not drop %s", last.Number, table)
}
}
for _, want := range []string{"drop function raise_assignment_generation()"} {
if !strings.Contains(sql, want) {
t.Errorf("migration %d lacks %q", last.Number, want)
}
}
if strings.Contains(sql, "sent_builds") {
t.Errorf("migration %d touches node.sent_builds, which stays as the source of the copy (issue 1112 drops it)", last.Number)
}
if strings.Contains(sql, "build_gate") || strings.Contains(sql, "assignment_generation;") {
t.Errorf("migration %d touches a table that stays", last.Number)
}
if raise, drop := strings.Index(sql, "raise exception"), strings.Index(sql, "drop table"); raise < 0 || drop < 0 || raise > drop {
t.Errorf("migration %d does not raise before it drops (%d, %d)", last.Number, raise, drop)
}
for _, kind := range []string{"'builds'", "'rollouts'", "'assignments'", "pending_assignment", "chain_moved"} {
if !strings.Contains(sql[:strings.Index(sql, "drop table")], kind) {
t.Errorf("the guard of migration %d does not look at %s", last.Number, kind)
}
}
}
func migrationFile(t *testing.T, number int) string {
t.Helper()
files, err := filepath.Glob(filepath.Join("migrations", strconv.Itoa(number/1000%10)+strconv.Itoa(number/100%10)+strconv.Itoa(number/10%10)+strconv.Itoa(number%10)+"-*.sql"))
if err != nil || len(files) != 1 {
t.Fatalf("the file of migration %d: %v %v", number, files, err)
}
return filepath.Base(files[0])
}