turbo-editors/turbo-jspublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-js.git
git clone ssh://git@rickub.com/turbo-editors/turbo-js.git

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

diagram_test.go · 197 lines · 6.3 KBGo Blame HistoryRaw
📦 Turbo JS 91999d1 k33g 11h ago1package main
2
3import (
4 "encoding/xml"
5 "html"
6 "os"
7 "os/exec"
8 "regexp"
9 "slices"
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. Another editor's shipped
16// naming internal/rustlang and "the Rust scanner" — an error no test could
17// 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 Golo'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// ourPackages are the two packages of this module, by the name the diagram
47// draws them under and the import path `go list` takes.
48var ourPackages = map[string]string{"main": ".", "internal/jslang": "./internal/jslang"}
49
50// readDiagram parses the diagram, failing the test rather than returning an
51// error — a diagram that will not parse is not a case any caller can handle.
52func readDiagram(t *testing.T) mxFile {
53 t.Helper()
54
55 raw, err := os.ReadFile(diagramFile)
56 if err != nil {
57 t.Fatalf("reading %s: %v", diagramFile, err)
58 }
59
60 var file mxFile
61 if err := xml.Unmarshal(raw, &file); err != nil {
62 t.Fatalf("parsing %s: %v", diagramFile, err)
63 }
64 return file
65}
66
67// boxes maps each box's package name to the id the arrows use for it.
68func boxes(t *testing.T, file mxFile) map[string]string {
69 t.Helper()
70
71 found := map[string]string{}
72 for _, cell := range file.Cells {
73 if cell.Edge == "1" || cell.Value == "" {
74 continue
75 }
76 label := html.UnescapeString(cell.Value)
77 // A box's package name is the part in bold, where there is one; the
78 // third-party box carries its name plain, with nothing to tell apart
79 // from it.
80 if match := boldLabel.FindStringSubmatch(label); match != nil {
81 label = match[1]
82 }
83 found[label] = cell.ID
84 }
85 return found
86}
87
88// imports asks the toolchain what a package imports, shortened to the names the
89// diagram uses: the last element for a turbo-core package, the module-relative
90// path for one of ours, and "tcell/v2" for the one third-party dependency.
91func imports(t *testing.T, pkg string) []string {
92 t.Helper()
93
94 out, err := exec.Command("go", "list", "-f", `{{join .Imports "\n"}}`, pkg).Output()
95 if err != nil {
96 t.Fatalf("go list %s: %v", pkg, err)
97 }
98
99 var names []string
100 for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
101 switch {
102 case strings.HasPrefix(line, "rickub.com/turbo-editors/turbo-core/"):
103 names = append(names, strings.TrimPrefix(line, "rickub.com/turbo-editors/turbo-core/"))
104 case strings.HasPrefix(line, "rickub.com/turbo-editors/turbo-js/"):
105 names = append(names, strings.TrimPrefix(line, "rickub.com/turbo-editors/turbo-js/"))
106 case strings.HasPrefix(line, "github.com/gdamore/tcell/"):
107 names = append(names, "tcell/v2")
108 }
109 }
110 slices.Sort(names)
111 return names
112}
113
114// The boxes are exactly the packages the two packages of this module import,
115// plus the two packages themselves. A box for a package nothing imports is as
116// wrong as a missing one: both tell a reader something untrue about the code.
117func TestTheDiagramDrawsExactlyThePackagesThisModuleImports(t *testing.T) {
118 drawn := boxes(t, readDiagram(t))
119
120 want := map[string]bool{}
121 for name, pkg := range ourPackages {
122 want[name] = true
123 for _, imported := range imports(t, pkg) {
124 want[imported] = true
125 }
126 }
127
128 for name := range want {
129 if _, ok := drawn[name]; !ok {
130 t.Errorf("%s draws no box for %q", diagramFile, name)
131 }
132 }
133 for name := range drawn {
134 if !want[name] {
135 t.Errorf("%s draws a box for %q, which nothing in this module imports", diagramFile, name)
136 }
137 }
138}
139
140// Every arrow leaving one of our two boxes is an import that exists. This is
141// the half that caught the copied diagram: an arrow drawn out of a box labelled
142// internal/rustlang cannot be checked at all until the box is named right.
143func TestEveryArrowOutOfOurPackagesIsARealImport(t *testing.T) {
144 file := readDiagram(t)
145 drawn := boxes(t, file)
146
147 byID := map[string]string{}
148 for name, id := range drawn {
149 byID[id] = name
150 }
151
152 for _, cell := range file.Cells {
153 if cell.Edge != "1" {
154 continue
155 }
156 from, ok := byID[cell.Source]
157 if !ok {
158 t.Errorf("%s draws an arrow out of unknown cell %q", diagramFile, cell.Source)
159 continue
160 }
161 pkg, ok := ourPackages[from]
162 if !ok {
163 continue
164 }
165
166 if to := byID[cell.Target]; !slices.Contains(imports(t, pkg), to) {
167 t.Errorf("%s draws %s → %s, but %s imports no such package", diagramFile, from, to, from)
168 }
169 }
170}
171
172// The file's host attribute names the project it was drawn for. It is the one
173// field a reader never sees and a copy always keeps.
174func TestTheDiagramSaysWhichProjectItWasDrawnFor(t *testing.T) {
175 if host := readDiagram(t).Host; host != "turbo-js" {
176 t.Errorf("%s was drawn for %q, not turbo-js", diagramFile, host)
177 }
178}
179
180// No label anywhere in the diagram names another editor in the family, or the
181// language it edits. The copied diagram said "the Rust scanner" in prose that
182// no identifier check would have looked at.
183func TestNoLabelInTheDiagramNamesAnotherEditorsLanguage(t *testing.T) {
184 others := []string{
185 "gololang", "moonbitlang", "pythonlang", "rustlang", "golang",
186 "Golo", "MoonBit", "Python", "Rust", "Go ",
187 "turbo-golo", "turbo-moonbit", "turbo-python", "turbo-rust", "turbo-go",
188 }
189 for _, cell := range readDiagram(t).Cells {
190 label := html.UnescapeString(cell.Value)
191 for _, other := range others {
192 if strings.Contains(label, other) {
193 t.Errorf("%s labels a cell %q, which names %q", diagramFile, label, other)
194 }
195 }
196 }
197}