turbo-editors/turbo-golopublic Fork 0
v1.0.2
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-golo.git
git clone ssh://git@rickub.com/turbo-editors/turbo-golo.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

diagram_test.go · 202 lines · 6.6 KBGo Blame HistoryRaw
📦 Turbo Golo d710c1b k33g 12h ago1package main
2
3import (
4 "encoding/xml"
5 "html"
6 "os"
7 "os/exec"
8 "regexp"
9 "sort"
10 "strings"
11 "testing"
12)
13
14// The package diagram is drawn by hand and read by people, so nothing in the
15// build notices when it stops describing the code. It started as Turbo Rust's
16// and shipped naming internal/rustlang and "the Rust scanner" — an error no
17// test could see, because a diagram is a file nothing imports.
18//
19// These tests hold it to `go list`: the boxes are the packages this module
20// actually imports, and the arrows between the two boxes that are ours are the
21// imports that really exist. This diagram began as Turbo MoonBit's, which is
22// exactly the provenance the four checks below exist to catch.
23
24// diagramFile is the drawio the documentation links to.
25const diagramFile = "docs/diagrams/packages.drawio"
26
27// mxFile is as much of drawio's format as these tests need: every cell, with
28// its label, and — for an arrow — the two cells it joins.
29type mxFile struct {
30 Host string `xml:"host,attr"`
31 Cells []mxCell `xml:"diagram>mxGraphModel>root>mxCell"`
32}
33
34type mxCell struct {
35 ID string `xml:"id,attr"`
36 Value string `xml:"value,attr"`
37 Edge string `xml:"edge,attr"`
38 Source string `xml:"source,attr"`
39 Target string `xml:"target,attr"`
40}
41
42// boldLabel is the package name inside a box: drawio stores the label as
43// escaped HTML, and the name is the part in bold.
44var boldLabel = regexp.MustCompile(`(?s)<b>(.*?)</b>`)
45
46// readDiagram parses the diagram, failing the test rather than returning an
47// error — a diagram that will not parse is not a case any caller can handle.
48func readDiagram(t *testing.T) mxFile {
49 t.Helper()
50
51 raw, err := os.ReadFile(diagramFile)
52 if err != nil {
53 t.Fatalf("reading %s: %v", diagramFile, err)
54 }
55
56 var file mxFile
57 if err := xml.Unmarshal(raw, &file); err != nil {
58 t.Fatalf("parsing %s: %v", diagramFile, err)
59 }
60 return file
61}
62
63// boxes maps each box's package name to the id the arrows use for it.
64func boxes(t *testing.T, file mxFile) map[string]string {
65 t.Helper()
66
67 found := map[string]string{}
68 for _, cell := range file.Cells {
69 if cell.Edge == "1" || cell.Value == "" {
70 continue
71 }
72 label := html.UnescapeString(cell.Value)
73 // A box's package name is the part in bold, where there is one; the
74 // third-party box carries its name plain, with nothing to tell apart
75 // from it.
76 if match := boldLabel.FindStringSubmatch(label); match != nil {
77 label = match[1]
78 }
79 found[label] = cell.ID
80 }
81 return found
82}
83
84// imports asks the toolchain what a package imports, shortened to the names the
85// diagram uses: the last element for a turbo-core package, the module-relative
86// path for one of ours, and "tcell/v2" for the one third-party dependency.
87func imports(t *testing.T, pkg string) []string {
88 t.Helper()
89
90 out, err := exec.Command("go", "list", "-f", `{{join .Imports "\n"}}`, pkg).Output()
91 if err != nil {
92 t.Fatalf("go list %s: %v", pkg, err)
93 }
94
95 var names []string
96 for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
97 switch {
98 case strings.HasPrefix(line, "rickub.com/turbo-editors/turbo-core/"):
99 names = append(names, strings.TrimPrefix(line, "rickub.com/turbo-editors/turbo-core/"))
100 case strings.HasPrefix(line, "rickub.com/turbo-editors/turbo-golo/"):
101 names = append(names, strings.TrimPrefix(line, "rickub.com/turbo-editors/turbo-golo/"))
102 case strings.HasPrefix(line, "github.com/gdamore/tcell/"):
103 names = append(names, "tcell/v2")
104 }
105 }
106 sort.Strings(names)
107 return names
108}
109
110// The boxes are exactly the packages the two packages of this module import,
111// plus the two packages themselves. A box for a package nothing imports is as
112// wrong as a missing one: both tell a reader something untrue about the code.
113func TestTheDiagramDrawsExactlyThePackagesThisModuleImports(t *testing.T) {
114 drawn := boxes(t, readDiagram(t))
115
116 want := map[string]bool{"main": true, "internal/gololang": true}
117 for _, pkg := range append(imports(t, "."), imports(t, "./internal/gololang")...) {
118 want[pkg] = true
119 }
120
121 for name := range want {
122 if _, ok := drawn[name]; !ok {
123 t.Errorf("%s draws no box for %q", diagramFile, name)
124 }
125 }
126 for name := range drawn {
127 if !want[name] {
128 t.Errorf("%s draws a box for %q, which nothing in this module imports", diagramFile, name)
129 }
130 }
131}
132
133// Every arrow leaving one of our two boxes is an import that exists. This is
134// the half that caught the copied diagram: an arrow drawn out of a box labelled
135// internal/rustlang cannot be checked at all until the box is named right.
136func TestEveryArrowOutOfOurPackagesIsARealImport(t *testing.T) {
137 file := readDiagram(t)
138 drawn := boxes(t, file)
139
140 byID := map[string]string{}
141 for name, id := range drawn {
142 byID[id] = name
143 }
144
145 ours := map[string]string{"main": ".", "internal/gololang": "./internal/gololang"}
146 for _, cell := range file.Cells {
147 if cell.Edge != "1" {
148 continue
149 }
150 from, ok := byID[cell.Source]
151 if !ok {
152 t.Errorf("%s draws an arrow out of unknown cell %q", diagramFile, cell.Source)
153 continue
154 }
155 pkg, ok := ours[from]
156 if !ok {
157 continue
158 }
159
160 to := byID[cell.Target]
161 if to == "internal/gololang" && from == "main" {
162 continue // main imports it under its full path, already shortened
163 }
164 if !slicesContain(imports(t, pkg), to) {
165 t.Errorf("%s draws %s → %s, but %s imports no such package", diagramFile, from, to, from)
166 }
167 }
168}
169
170// The file's host attribute names the project it was drawn for. It is the one
171// field a reader never sees and a copy always keeps.
172func TestTheDiagramSaysWhichProjectItWasDrawnFor(t *testing.T) {
173 if host := readDiagram(t).Host; host != "turbo-golo" {
174 t.Errorf("%s was drawn for %q, not turbo-golo", diagramFile, host)
175 }
176}
177
178// No label anywhere in the diagram names another editor in the family, or the
179// language it edits. The copied diagram said "the Rust scanner" in prose that
180// no identifier check would have looked at.
181func TestNoLabelInTheDiagramNamesAnotherEditorsLanguage(t *testing.T) {
182 for _, cell := range readDiagram(t).Cells {
183 label := html.UnescapeString(cell.Value)
184 for _, other := range []string{"moonbitlang", "pythonlang", "rustlang", "golang", "MoonBit", "Python", "Rust", "Go ", "turbo-moonbit", "turbo-python", "turbo-rust", "turbo-go"} {
185 if strings.Contains(label, other) {
186 t.Errorf("%s labels a cell %q, which names %q", diagramFile, label, other)
187 }
188 }
189 }
190}
191
192// slicesContain says whether a sorted list holds a value. It is here rather
193// than from the standard library's slices package so the test reads the same
194// way in a checkout of any Go version this module supports.
195func slicesContain(list []string, want string) bool {
196 for _, got := range list {
197 if got == want {
198 return true
199 }
200 }
201 return false
202}