turbo-editors/turbo-pythonpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-python.git
git clone ssh://git@rickub.com/turbo-editors/turbo-python.git

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

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