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, "CARGO": sim.PortCargo, "CARGOCAP": sim.PortCargoCap, "CARGO0": sim.PortCargoOre, "CARGO1": sim.PortCargoOre + 1, "CARGO2": sim.PortCargoOre + 2, "CARGO3": sim.PortCargoOre + 3, "STNX": sim.PortStnRelX, "STNY": sim.PortStnRelY, "STNZ": sim.PortStnRelZ, "STNVX": sim.PortStnRelVX, "STNVY": sim.PortStnRelVY, "STNVZ": sim.PortStnRelVZ, "SELL": sim.PortSell, "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) } } }