package main
import (
"encoding/xml"
"html"
"os"
"os/exec"
"regexp"
"sort"
"strings"
"testing"
)
// The package diagram is drawn by hand and read by people, so nothing in the
// build notices when it stops describing the code. It started as Turbo Rust's
// and shipped naming internal/rustlang and "the Rust scanner" — an error no
// test could see, because a diagram is a file nothing imports.
//
// These tests hold it to `go list`: the boxes are the packages this module
// actually imports, and the arrows between the two boxes that are ours are the
// imports that really exist. This diagram began as Turbo MoonBit's, which is
// exactly the provenance the four checks below exist to catch.
// diagramFile is the drawio the documentation links to.
const diagramFile = "docs/diagrams/packages.drawio"
// mxFile is as much of drawio's format as these tests need: every cell, with
// its label, and — for an arrow — the two cells it joins.
type mxFile struct {
Host string `xml:"host,attr"`
Cells []mxCell `xml:"diagram>mxGraphModel>root>mxCell"`
}
type mxCell struct {
ID string `xml:"id,attr"`
Value string `xml:"value,attr"`
Edge string `xml:"edge,attr"`
Source string `xml:"source,attr"`
Target string `xml:"target,attr"`
}
// boldLabel is the package name inside a box: drawio stores the label as
// escaped HTML, and the name is the part in bold.
var boldLabel = regexp.MustCompile(`(?s)(.*?)`)
// readDiagram parses the diagram, failing the test rather than returning an
// error — a diagram that will not parse is not a case any caller can handle.
func readDiagram(t *testing.T) mxFile {
t.Helper()
raw, err := os.ReadFile(diagramFile)
if err != nil {
t.Fatalf("reading %s: %v", diagramFile, err)
}
var file mxFile
if err := xml.Unmarshal(raw, &file); err != nil {
t.Fatalf("parsing %s: %v", diagramFile, err)
}
return file
}
// boxes maps each box's package name to the id the arrows use for it.
func boxes(t *testing.T, file mxFile) map[string]string {
t.Helper()
found := map[string]string{}
for _, cell := range file.Cells {
if cell.Edge == "1" || cell.Value == "" {
continue
}
label := html.UnescapeString(cell.Value)
// A box's package name is the part in bold, where there is one; the
// third-party box carries its name plain, with nothing to tell apart
// from it.
if match := boldLabel.FindStringSubmatch(label); match != nil {
label = match[1]
}
found[label] = cell.ID
}
return found
}
// imports asks the toolchain what a package imports, shortened to the names the
// diagram uses: the last element for a turbo-core package, the module-relative
// path for one of ours, and "tcell/v2" for the one third-party dependency.
func imports(t *testing.T, pkg string) []string {
t.Helper()
out, err := exec.Command("go", "list", "-f", `{{join .Imports "\n"}}`, pkg).Output()
if err != nil {
t.Fatalf("go list %s: %v", pkg, err)
}
var names []string
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
switch {
case strings.HasPrefix(line, "rickub.com/turbo-editors/turbo-core/"):
names = append(names, strings.TrimPrefix(line, "rickub.com/turbo-editors/turbo-core/"))
case strings.HasPrefix(line, "rickub.com/turbo-editors/turbo-golo/"):
names = append(names, strings.TrimPrefix(line, "rickub.com/turbo-editors/turbo-golo/"))
case strings.HasPrefix(line, "github.com/gdamore/tcell/"):
names = append(names, "tcell/v2")
}
}
sort.Strings(names)
return names
}
// The boxes are exactly the packages the two packages of this module import,
// plus the two packages themselves. A box for a package nothing imports is as
// wrong as a missing one: both tell a reader something untrue about the code.
func TestTheDiagramDrawsExactlyThePackagesThisModuleImports(t *testing.T) {
drawn := boxes(t, readDiagram(t))
want := map[string]bool{"main": true, "internal/gololang": true}
for _, pkg := range append(imports(t, "."), imports(t, "./internal/gololang")...) {
want[pkg] = true
}
for name := range want {
if _, ok := drawn[name]; !ok {
t.Errorf("%s draws no box for %q", diagramFile, name)
}
}
for name := range drawn {
if !want[name] {
t.Errorf("%s draws a box for %q, which nothing in this module imports", diagramFile, name)
}
}
}
// Every arrow leaving one of our two boxes is an import that exists. This is
// the half that caught the copied diagram: an arrow drawn out of a box labelled
// internal/rustlang cannot be checked at all until the box is named right.
func TestEveryArrowOutOfOurPackagesIsARealImport(t *testing.T) {
file := readDiagram(t)
drawn := boxes(t, file)
byID := map[string]string{}
for name, id := range drawn {
byID[id] = name
}
ours := map[string]string{"main": ".", "internal/gololang": "./internal/gololang"}
for _, cell := range file.Cells {
if cell.Edge != "1" {
continue
}
from, ok := byID[cell.Source]
if !ok {
t.Errorf("%s draws an arrow out of unknown cell %q", diagramFile, cell.Source)
continue
}
pkg, ok := ours[from]
if !ok {
continue
}
to := byID[cell.Target]
if to == "internal/gololang" && from == "main" {
continue // main imports it under its full path, already shortened
}
if !slicesContain(imports(t, pkg), to) {
t.Errorf("%s draws %s → %s, but %s imports no such package", diagramFile, from, to, from)
}
}
}
// The file's host attribute names the project it was drawn for. It is the one
// field a reader never sees and a copy always keeps.
func TestTheDiagramSaysWhichProjectItWasDrawnFor(t *testing.T) {
if host := readDiagram(t).Host; host != "turbo-golo" {
t.Errorf("%s was drawn for %q, not turbo-golo", diagramFile, host)
}
}
// No label anywhere in the diagram names another editor in the family, or the
// language it edits. The copied diagram said "the Rust scanner" in prose that
// no identifier check would have looked at.
func TestNoLabelInTheDiagramNamesAnotherEditorsLanguage(t *testing.T) {
for _, cell := range readDiagram(t).Cells {
label := html.UnescapeString(cell.Value)
for _, other := range []string{"moonbitlang", "pythonlang", "rustlang", "golang", "MoonBit", "Python", "Rust", "Go ", "turbo-moonbit", "turbo-python", "turbo-rust", "turbo-go"} {
if strings.Contains(label, other) {
t.Errorf("%s labels a cell %q, which names %q", diagramFile, label, other)
}
}
}
}
// slicesContain says whether a sorted list holds a value. It is here rather
// than from the standard library's slices package so the test reads the same
// way in a checkout of any Go version this module supports.
func slicesContain(list []string, want string) bool {
for _, got := range list {
if got == want {
return true
}
}
return false
}