clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat/atlas-next-horizon
atlas / tools / depcheck / rules_test.go
rules_test.go 8.8K
1package main23import (4 "strings"5 "testing"6)78const mod = modulePath + "/"910// The rules are pure functions of two package paths, which is what lets the11// whole import matrix of issue #5 §3.2 be read as a table.12func TestLaneImportEdge(t *testing.T) {13 tests := []struct {14 name string15 from string // module-relative path of the importing package16 imports string // full import path17 want string // substring of the expected message; "" means allowed18 }{19 {"format may use the stdlib", "format/bundle", "archive/zip", ""},20 {"format may use itself", "format/bundle", mod + "format/semconv", ""},21 {"format may not use third parties", "format/bundle", "github.com/fsnotify/fsnotify", "standard library only"},22 {"format may not use a lane", "format/bundle", mod + "internal/generate/doc", "standard library only"},2324 {"generate may use format", "internal/generate/compose", mod + "format/bundle", ""},25 {"generate may use itself", "internal/generate/compose", mod + "internal/generate/doc", ""},26 {"generate may use third parties", "internal/generate/tiles", "golang.org/x/image/draw", ""},27 {"generate may not use enrich", "internal/generate/compose", mod + "internal/enrich/merge", "generate must not import enrich"},28 {"enrich may not use generate", "internal/enrich/merge", mod + "internal/generate/doc", "enrich must not import generate"},29 {"generate may not use app", "internal/generate/doc", mod + "internal/app/session", "generate must not import app"},30 {"enrich may not use the test tree", "internal/enrich/maturity", mod + "tests/corpus", "enrich must not import tests"},3132 {"app may use format", "internal/app/handler", mod + "format/semconv", ""},33 {"app may use itself", "internal/app/handler", mod + "internal/app/hostenv", ""},34 {"app may not use generate", "internal/app/handler", mod + "internal/generate/compose", "app must not import generate"},35 {"app may not use enrich", "internal/app/handler", mod + "internal/enrich/merge", "app must not import enrich"},3637 {"workbench may use enrich/maturity", "internal/workbench", mod + "internal/enrich/maturity", ""},38 {"workbench may not use other enrichers", "internal/workbench", mod + "internal/enrich/merge", "only enrich/maturity"},39 {"workbench may not use generate", "internal/workbench", mod + "internal/generate/tiles", "workbench must not import generate"},4041 {"nothing imports render", "internal/app/handler", mod + "render/scene", "nothing imports render"},42 {"nothing imports render, not even the CLI", "cmd/atlas", mod + "render/scene", "nothing imports render"},43 {"analysis is TypeScript", "internal/app/handler", mod + "analysis/cellsystems", "not importable from Go"},4445 {"every lane may narrate itself", "internal/app/hostenv/oshost", mod + "internal/logging", ""},46 {"including the CLI that sets the stream up", "cmd/atlas", mod + "internal/logging", ""},47 {"format narrates nothing", "format/bundle", mod + "internal/logging", "standard library only"},48 {"logging depends on nothing of ours", "internal/logging", mod + "internal/app/hostenv", "logging must not import app"},49 {"every lane may speak the event stream", "internal/generate/compose", mod + "internal/logging", ""},50 {"logging may use the center", "internal/logging", mod + "format/bundle", ""},51 {"logging may not use a lane", "internal/logging", mod + "internal/generate/doc", "logging must not import generate"},5253 {"the CLI wires every lane", "cmd/atlas", mod + "internal/enrich/merge", ""},54 {"the test tree may read a lane", "tests/island", mod + "internal/generate/doc", ""},55 {"a path the clean room never heard of is not judged", "internal/measure", mod + "internal/bundle", ""},5657 {"the shell mounts the app", "", mod + "internal/app", ""},58 {"and the host it mounts it over", "", mod + "internal/app/hostenv/wailshost", ""},59 {"and narrates itself", "", mod + "internal/logging", ""},60 {"but does not import the seam either", "", mod + "render/scene", "nothing imports render"},61 }6263 for _, tt := range tests {64 t.Run(tt.name, func(t *testing.T) {65 got := laneImportEdge(laneOf(tt.from), tt.from, tt.imports)66 assertMessage(t, got, tt.want)67 })68 }69}7071func TestHostenvEdge(t *testing.T) {72 tests := []struct {73 name string74 from string75 imports string76 want string77 }{78 {"the handler may not read the environment", "internal/app/handler", "os", "must not import \"os\""},79 {"nor shell out", "internal/app/session", "os/exec", "must not import \"os/exec\""},80 {"nor build paths", "internal/app/handler", "path/filepath", "must not import \"path/filepath\""},81 {"nor know its window system", "internal/app/handler", "github.com/wailsapp/wails/v2", "host toolkit"},82 {"hostenv implementations may do all three", "internal/app/hostenv", "os", ""},83 {"including the Wails host", "internal/app/hostenv/wailshost", "github.com/wailsapp/wails/v2/pkg/runtime", ""},84 {"io/fs is the portable shape", "internal/app/handler", "io/fs", ""},85 {"the rule is about the app", "internal/generate/crawl", "os", ""},86 // A host entry is where the machine is reached: which directories the87 // library and the session records live in, and which window the88 // handler is drawn in, are exactly the decisions it exists to make.89 {"the desktop shell is a host entry", "", "os", ""},90 {"and opens the window itself", "", "github.com/wailsapp/wails/v2", ""},91 // The workbench is a developer's tool on a developer's machine, and it92 // exists to run the lane CLIs (issue #5 §3.1, §5.6): shelling out is93 // its contract, not a leak. The portability amendment is about the94 // application the three hosts mount, and only about that.95 {"the workbench shells out by contract", "internal/workbench/oprunner", "os/exec", ""},96 {"and reads the registry it measures", "internal/workbench", "os", ""},97 }9899 for _, tt := range tests {100 t.Run(tt.name, func(t *testing.T) {101 got := hostenvEdge(laneOf(tt.from), tt.from, tt.imports)102 assertMessage(t, got, tt.want)103 })104 }105}106107func TestAtlasKeyLiteral(t *testing.T) {108 tests := []struct {109 literal string110 want bool111 }{112 {"atlas.world.slug", true},113 {"atlas.render.as", true},114 {"atlas.hydro.huc12", true},115 {"atlas", false},116 {"atlas.", false},117 {"atlas.json", false},118 {"atlasing.away", false},119 {"the atlas.world.slug key", false},120 {"Atlas.World", false},121 }122123 for _, tt := range tests {124 t.Run(tt.literal, func(t *testing.T) {125 if got := atlasKey.MatchString(tt.literal); got != tt.want {126 t.Errorf("atlasKey.MatchString(%q) = %v, want %v", tt.literal, got, tt.want)127 }128 })129 }130}131132// The server half of net/http must stay usable: the application *is* an133// http.Handler, so a rule that flagged every mention of the package would be a134// rule nobody could keep.135func TestOutboundSymbols(t *testing.T) {136 tests := []struct {137 pkg, name string138 want bool139 }{140 {"net/http", "Get", true},141 {"net/http", "Client", true},142 {"net/http", "DefaultTransport", true},143 {"net", "Dialer", true},144 {"net/http", "Handler", false},145 {"net/http", "ServeMux", false},146 {"net/http", "StatusOK", false},147 {"net/http", "ResponseWriter", false},148 {"net/http/httptest", "NewRequest", false},149 }150151 for _, tt := range tests {152 t.Run(tt.pkg+"."+tt.name, func(t *testing.T) {153 if got := outbound[tt.pkg][tt.name]; got != tt.want {154 t.Errorf("outbound[%q][%q] = %v, want %v", tt.pkg, tt.name, got, tt.want)155 }156 })157 }158}159160func TestLaneOf(t *testing.T) {161 tests := []struct {162 path string163 want Lane164 }{165 {"", LaneShell},166 {".", LaneShell},167 {"format", LaneFormat},168 {"format/bundle", LaneFormat},169 {"formatting", LaneOutside},170 {"internal/generate/sources/mapgenie", LaneGenerate},171 {"internal/enrich/maturity", LaneEnrich},172 {"internal/app/hostenv", LaneApp},173 {"internal/workbench", LaneWorkbench},174 {"cmd/atlas", LaneCLI},175 {"tests/cells", LaneTests},176 {"internal/logging", LaneLogging},177 // Archived names, kept as the recognisable examples of a path the178 // matrix does not own: they are on the golden-reference tag, and the179 // answer for any path like them has to be the same.180 {"cmd/cartograph", LaneOutside},181 {"internal/bundle", LaneOutside},182 {"tools/tiles", LaneOutside},183 }184185 for _, tt := range tests {186 t.Run(tt.path, func(t *testing.T) {187 if got := laneOf(tt.path); got != tt.want {188 t.Errorf("laneOf(%q) = %q, want %q", tt.path, got, tt.want)189 }190 })191 }192}193194// Every message names the contract and cites the issue section, because a195// violation should teach the boundary rather than merely block it (§9).196func assertMessage(t *testing.T, got, want string) {197 t.Helper()198 switch {199 case want == "" && got != "":200 t.Fatalf("edge rejected, want allowed: %s", got)201 case want == "":202 return203 case got == "":204 t.Fatalf("edge allowed, want a finding containing %q", want)205 case !strings.Contains(got, want):206 t.Fatalf("finding %q does not contain %q", got, want)207 case !strings.Contains(got, "issue #5 §"):208 t.Fatalf("finding %q does not cite an issue section", got)209 }210}