1package main_test23import (4 "go/parser"5 "go/token"6 "os"7 "path/filepath"8 "runtime"9 "slices"10 "strings"11 "testing"12)1314func TestPuzzlePackageStaysPure(t *testing.T) {15 assertPackageDoesNotImport(t, "puzzle", []string{16 "charm.land/bubbletea",17 "charm.land/bubbles",18 "charm.land/lipgloss",19 "github.com/FelineStateMachine/puzzletea/theme",20 "github.com/FelineStateMachine/puzzletea/pdfexport",21 })22}2324func TestStatsPackageAvoidsRenderingImports(t *testing.T) {25 assertPackageDoesNotImport(t, "stats", []string{26 "charm.land/bubbletea",27 "charm.land/bubbles",28 "charm.land/lipgloss",29 "github.com/FelineStateMachine/puzzletea/theme",30 "github.com/FelineStateMachine/puzzletea/ui",31 })32}3334func TestStorePackageDoesNotImportSchedulePackages(t *testing.T) {35 assertPackageDoesNotImport(t, "store", []string{36 "github.com/FelineStateMachine/puzzletea/daily",37 "github.com/FelineStateMachine/puzzletea/weekly",38 })39}4041func TestGamePackageDoesNotImportPDFExport(t *testing.T) {42 assertPackageDoesNotImport(t, "game", []string{43 "github.com/FelineStateMachine/puzzletea/pdfexport",44 })45}4647func TestCatalogPackageDoesNotImportConcreteGames(t *testing.T) {48 assertPackageDoesNotImport(t, "catalog", concreteGameImportPaths(t))49}5051func TestBuiltinPrintDoesNotImportConcreteGames(t *testing.T) {52 assertPackageDoesNotImport(t, "builtinprint", concreteGameImportPaths(t))53}5455func TestSessionPackageDoesNotUseNameDerivedRunMetadata(t *testing.T) {56 assertFilesDoNotContain(t, "session", []string{57 "RunKindForName(",58 "RunDateForName(",59 "SeedTextForName(",60 "WeeklyIdentityForName(",61 })62}6364func TestStoreCreateGameDoesNotUseNameDerivedRunMetadata(t *testing.T) {65 assertFileDoesNotContain(t, filepath.Join("store", "store.go"), []string{66 "RunKindForName(",67 "RunDateForName(",68 "SeedTextForName(",69 "WeeklyIdentityForName(",70 })71}7273func assertPackageDoesNotImport(t *testing.T, dir string, forbidden []string) {74 t.Helper()7576 files, err := filepath.Glob(filepath.Join(dir, "*.go"))77 if err != nil {78 t.Fatalf("glob %s: %v", dir, err)79 }8081 fset := token.NewFileSet()82 for _, path := range files {83 if strings.HasSuffix(path, "_test.go") {84 continue85 }86 file, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)87 if err != nil {88 t.Fatalf("parse %s: %v", path, err)89 }90 for _, imp := range file.Imports {91 value := strings.Trim(imp.Path.Value, `"`)92 for _, prefix := range forbidden {93 if strings.HasPrefix(value, prefix) {94 t.Fatalf("%s imports forbidden package %q", path, value)95 }96 }97 }98 }99}100101func concreteGameImportPaths(t testing.TB) []string {102 t.Helper()103104 pattern := filepath.Join(repoRoot(t), "*", "gamemode.go")105 matches, err := filepath.Glob(pattern)106 if err != nil {107 t.Fatalf("glob concrete game packages: %v", err)108 }109110 importPaths := make([]string, 0, len(matches))111 for _, match := range matches {112 dir := filepath.Base(filepath.Dir(match))113 importPaths = append(importPaths, "github.com/FelineStateMachine/puzzletea/"+dir)114 }115 slices.Sort(importPaths)116 return importPaths117}118119func repoRoot(t testing.TB) string {120 t.Helper()121122 _, path, _, ok := runtime.Caller(0)123 if !ok {124 t.Fatal("runtime.Caller failed")125 }126 return filepath.Clean(filepath.Dir(path))127}128129func assertFileDoesNotContain(t *testing.T, path string, forbidden []string) {130 t.Helper()131132 data, err := os.ReadFile(path)133 if err != nil {134 t.Fatalf("read %s: %v", path, err)135 }136 content := string(data)137 for _, pattern := range forbidden {138 if strings.Contains(content, pattern) {139 t.Fatalf("%s contains forbidden pattern %q", path, pattern)140 }141 }142}143144func assertFilesDoNotContain(t *testing.T, dir string, forbidden []string) {145 t.Helper()146147 files, err := filepath.Glob(filepath.Join(dir, "*.go"))148 if err != nil {149 t.Fatalf("glob %s: %v", dir, err)150 }151152 for _, path := range files {153 if strings.HasSuffix(path, "_test.go") {154 continue155 }156157 data, err := os.ReadFile(path)158 if err != nil {159 t.Fatalf("read %s: %v", path, err)160 }161 content := string(data)162 for _, pattern := range forbidden {163 if strings.Contains(content, pattern) {164 t.Fatalf("%s contains forbidden pattern %q", path, pattern)165 }166 }167 }168}