Files
Halcyon/docs/docs_test.go
T
2026-10-01 22:16:48 +02:00

109 lines
3.8 KiB
Go

package docs_test
import (
"os"
"regexp"
"strconv"
"strings"
"testing"
"halcyon/docs"
"halcyon/sim"
)
// portValues maps the names used in the manual and in examples/programs/ports.s
// to the simulator's real port numbers, so the documentation cannot drift.
var portValues = map[string]int{
"TURN": sim.PortTurn, "DAY": sim.PortDay, "TURNS": sim.PortTurns,
"POSX": sim.PortPosX, "POSY": sim.PortPosY, "POSZ": sim.PortPosZ,
"VELX": sim.PortVelX, "VELY": sim.PortVelY, "VELZ": sim.PortVelZ,
"THROTTLE": sim.PortThrottle, "AZIMUTH": sim.PortAzimuth, "PITCH": sim.PortPitch,
"FUEL": sim.PortFuel, "MASS": sim.PortMass,
"SELECT": sim.PortScanSelect, "NEAREST": sim.PortScanNearest,
"RELX": sim.PortScanRelX, "RELY": sim.PortScanRelY, "RELZ": sim.PortScanRelZ,
"RELVX": sim.PortScanRelVX, "RELVY": sim.PortScanRelVY, "RELVZ": sim.PortScanRelVZ,
"DIST": sim.PortScanDist,
"ORE0": sim.PortScanOre, "ORE1": sim.PortScanOre + 1, "ORE2": sim.PortScanOre + 2, "ORE3": sim.PortScanOre + 3,
"MINE": sim.PortMine, "CONTS": sim.PortConts, "CONTKG": sim.PortContKg, "CONTROOM": sim.PortContRoom, "CONTMASS": sim.PortContMass,
"ATTACH": sim.PortAttach, "DETACH": sim.PortDetach, "NEARBY": sim.PortNearby,
"CONTORE0": sim.PortContOre, "CONTORE1": sim.PortContOre + 1, "CONTORE2": sim.PortContOre + 2, "CONTORE3": sim.PortContOre + 3,
"STNX": sim.PortStnRelX, "STNY": sim.PortStnRelY, "STNZ": sim.PortStnRelZ,
"STNVX": sim.PortStnRelVX, "STNVY": sim.PortStnRelVY, "STNVZ": sim.PortStnRelVZ,
"ORDER": sim.PortOrder, "EARNED": sim.PortCredits,
"UPNEW": sim.PortUplinkNew, "UPLEN": sim.PortUplinkLen,
"MATHX": sim.PortMathX, "MATHY": sim.PortMathY, "MATHZ": sim.PortMathZ,
"ATAN2": sim.PortMathAtan2, "HYPOT": sim.PortMathHypot, "NORM3": sim.PortMathNorm3,
}
func section(t *testing.T, md, from, to string) string {
t.Helper()
a := strings.Index(md, from)
if a < 0 {
t.Fatalf("manual has no %q", from)
}
b := len(md)
if to != "" {
b = a + strings.Index(md[a:], to)
}
return md[a:b]
}
func TestAppendixBMatchesSimulator(t *testing.T) {
md := string(docs.Markdown)
rows := regexp.MustCompile(`(?m)^0x([0-9A-F]{2})\s+(\w+)\s`).FindAllStringSubmatch(
section(t, md, "# Appendix B", "# Appendix C"), -1)
seen := map[string]bool{}
for _, r := range rows {
n, _ := strconv.ParseInt(r[1], 16, 32)
want, ok := portValues[r[2]]
if !ok || int(n) != want {
t.Errorf("Appendix B: %s is 0x%s, simulator says %#x (known=%v)", r[2], r[1], want, ok)
}
seen[r[2]] = true
}
for name := range portValues {
if !seen[name] {
t.Errorf("Appendix B does not document port %s", name)
}
}
}
func readExample(t *testing.T, name string) string {
t.Helper()
b, err := os.ReadFile("../examples/programs/" + name)
if err != nil {
t.Fatal(err)
}
return strings.TrimRight(string(b), "\n")
}
func TestEquateFileMatchesSimulatorAndManual(t *testing.T) {
ports := readExample(t, "ports.s")
seen := map[string]bool{}
for _, m := range regexp.MustCompile(`(?m)^\s*\.equ\s+P_(\w+)\s+0x([0-9A-F]{2})\b`).FindAllStringSubmatch(ports, -1) {
n, _ := strconv.ParseInt(m[2], 16, 32)
if want, ok := portValues[m[1]]; !ok || int(n) != want {
t.Errorf("ports.s: P_%s = 0x%s, simulator says %#x (known=%v)", m[1], m[2], want, ok)
}
seen[m[1]] = true
}
for name := range portValues {
if !seen[name] {
t.Errorf("ports.s lacks P_%s", name)
}
}
// Appendix C prints that file.
if !strings.Contains(string(docs.Markdown), "```asm\n"+ports+"\n```") {
t.Error("Appendix C is out of date with examples/programs/ports.s")
}
}
func TestSampleProgramsMatchExamples(t *testing.T) {
md := string(docs.Markdown)
for _, name := range []string{"echo", "telemetry", "pursue"} {
if !strings.Contains(md, "```asm\n"+readExample(t, name+".s")+"\n```") {
t.Errorf("Chapter 9 listing of %s is out of date with examples/programs/%s.s", name, name)
}
}
}