package main import ( "encoding/xml" "html" "os" "os/exec" "regexp" "slices" "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. Another editor's 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 Golo'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)(.*?)`) // ourPackages are the two packages of this module, by the name the diagram // draws them under and the import path `go list` takes. var ourPackages = map[string]string{"main": ".", "internal/jslang": "./internal/jslang"} // 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-js/"): names = append(names, strings.TrimPrefix(line, "rickub.com/turbo-editors/turbo-js/")) case strings.HasPrefix(line, "github.com/gdamore/tcell/"): names = append(names, "tcell/v2") } } slices.Sort(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{} for name, pkg := range ourPackages { want[name] = true for _, imported := range imports(t, pkg) { want[imported] = 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 } 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 := ourPackages[from] if !ok { continue } if to := byID[cell.Target]; !slices.Contains(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-js" { t.Errorf("%s was drawn for %q, not turbo-js", 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) { others := []string{ "gololang", "moonbitlang", "pythonlang", "rustlang", "golang", "Golo", "MoonBit", "Python", "Rust", "Go ", "turbo-golo", "turbo-moonbit", "turbo-python", "turbo-rust", "turbo-go", } for _, cell := range readDiagram(t).Cells { label := html.UnescapeString(cell.Value) for _, other := range others { if strings.Contains(label, other) { t.Errorf("%s labels a cell %q, which names %q", diagramFile, label, other) } } } }