clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

chore/app-model-decomp
puzzletea / architecture_test.go
architecture_test.go 4.4K
1package main_test23import (4 "go/parser"5 "go/token"6 "os"7 "path/filepath"8 "slices"9 "strings"10 "testing"11)1213func TestPuzzlePackageStaysPure(t *testing.T) {14 assertPackageDoesNotImport(t, "puzzle", []string{15 "charm.land/bubbletea",16 "charm.land/bubbles",17 "charm.land/lipgloss",18 "github.com/FelineStateMachine/puzzletea/theme",19 "github.com/FelineStateMachine/puzzletea/export/pdf",20 })21}2223func TestStatsPackageAvoidsRenderingImports(t *testing.T) {24 assertPackageDoesNotImport(t, "stats", []string{25 "charm.land/bubbletea",26 "charm.land/bubbles",27 "charm.land/lipgloss",28 "github.com/FelineStateMachine/puzzletea/theme",29 "github.com/FelineStateMachine/puzzletea/ui",30 })31}3233func TestStorePackageDoesNotImportSchedulePackages(t *testing.T) {34 assertPackageDoesNotImport(t, "store", []string{35 "github.com/FelineStateMachine/puzzletea/daily",36 "github.com/FelineStateMachine/puzzletea/weekly",37 })38}3940func TestGamePackageDoesNotImportPDFExport(t *testing.T) {41 assertPackageDoesNotImport(t, "game", []string{42 "github.com/FelineStateMachine/puzzletea/export/pdf",43 })44}4546func TestCatalogPackageDoesNotImportConcreteGames(t *testing.T) {47 assertPackageDoesNotImport(t, "catalog", concreteGameImportPaths(t))48}4950func TestBuiltinPrintDoesNotImportConcreteGames(t *testing.T) {51 assertPackageDoesNotImport(t, "export/builtinprint", concreteGameImportPaths(t))52}5354func TestSessionPackageDoesNotUseNameDerivedRunMetadata(t *testing.T) {55 assertFilesDoNotContain(t, "session", []string{56 "RunKindForName(",57 "RunDateForName(",58 "SeedTextForName(",59 "WeeklyIdentityForName(",60 })61}6263func TestStoreCreateGameDoesNotUseNameDerivedRunMetadata(t *testing.T) {64 assertFileDoesNotContain(t, filepath.Join("store", "store.go"), []string{65 "RunKindForName(",66 "RunDateForName(",67 "SeedTextForName(",68 "WeeklyIdentityForName(",69 })70}7172func assertPackageDoesNotImport(t *testing.T, dir string, forbidden []string) {73 t.Helper()7475 files, err := filepath.Glob(filepath.Join(dir, "*.go"))76 if err != nil {77 t.Fatalf("glob %s: %v", dir, err)78 }7980 fset := token.NewFileSet()81 for _, path := range files {82 if strings.HasSuffix(path, "_test.go") {83 continue84 }85 file, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)86 if err != nil {87 t.Fatalf("parse %s: %v", path, err)88 }89 for _, imp := range file.Imports {90 value := strings.Trim(imp.Path.Value, `"`)91 for _, prefix := range forbidden {92 if strings.HasPrefix(value, prefix) {93 t.Fatalf("%s imports forbidden package %q", path, value)94 }95 }96 }97 }98}99100func concreteGameImportPaths(t testing.TB) []string {101 t.Helper()102103 pattern := filepath.Join(repoRoot(t), "games", "*", "gamemode.go")104 matches, err := filepath.Glob(pattern)105 if err != nil {106 t.Fatalf("glob concrete game packages: %v", err)107 }108109 importPaths := make([]string, 0, len(matches))110 for _, match := range matches {111 dir := filepath.Base(filepath.Dir(match))112 importPaths = append(importPaths, "github.com/FelineStateMachine/puzzletea/games/"+dir)113 }114 slices.Sort(importPaths)115 return importPaths116}117118func repoRoot(t testing.TB) string {119 t.Helper()120121 wd, err := os.Getwd()122 if err != nil {123 t.Fatalf("get working directory: %v", err)124 }125126 for dir := filepath.Clean(wd); ; dir = filepath.Dir(dir) {127 path := filepath.Join(dir, "go.mod")128 if _, err := os.Stat(path); err == nil {129 return dir130 } else if !os.IsNotExist(err) {131 t.Fatalf("stat %s: %v", path, err)132 }133134 parent := filepath.Dir(dir)135 if parent == dir {136 t.Fatalf("find repo root from %s: go.mod not found", wd)137 }138 }139}140141func assertFileDoesNotContain(t *testing.T, path string, forbidden []string) {142 t.Helper()143144 data, err := os.ReadFile(path)145 if err != nil {146 t.Fatalf("read %s: %v", path, err)147 }148 content := string(data)149 for _, pattern := range forbidden {150 if strings.Contains(content, pattern) {151 t.Fatalf("%s contains forbidden pattern %q", path, pattern)152 }153 }154}155156func assertFilesDoNotContain(t *testing.T, dir string, forbidden []string) {157 t.Helper()158159 files, err := filepath.Glob(filepath.Join(dir, "*.go"))160 if err != nil {161 t.Fatalf("glob %s: %v", dir, err)162 }163164 for _, path := range files {165 if strings.HasSuffix(path, "_test.go") {166 continue167 }168169 data, err := os.ReadFile(path)170 if err != nil {171 t.Fatalf("read %s: %v", path, err)172 }173 content := string(data)174 for _, pattern := range forbidden {175 if strings.Contains(content, pattern) {176 t.Fatalf("%s contains forbidden pattern %q", path, pattern)177 }178 }179 }180}