Former/marker_test.go

189 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"strings"
"testing"
)
func TestBullseyePattern(t *testing.T) {
p := bullseyePattern()
// Center should be black
if !p[2][2] {
t.Error("center should be black")
}
// Ring 1 should be white
if p[2][1] || p[1][2] || p[2][3] || p[3][2] {
t.Error("ring 1 should be white")
}
// Ring 2 (corners) should be black
if !p[0][0] || !p[0][4] || !p[4][0] || !p[4][4] {
t.Error("outer ring corners should be black")
}
// 4-fold rotational symmetry
for rot := 0; rot < 3; rot++ {
var rotated [bullseyeN][bullseyeN]bool
for r := 0; r < bullseyeN; r++ {
for c := 0; c < bullseyeN; c++ {
rotated[c][bullseyeN-1-r] = p[r][c]
}
}
if rotated != p {
t.Errorf("bullseye not symmetric under 90° rotation %d", rot+1)
}
p = rotated
}
}
func TestEncodeDecodeMarkerBits(t *testing.T) {
tests := []MarkerData{
{PageNum: 0, CornerID: CornerTL, NumFaces: 6, LongestMM: 50},
{PageNum: 3, CornerID: CornerBR, NumFaces: 12, LongestMM: 120},
{PageNum: 15, CornerID: CornerCenter, NumFaces: 31, LongestMM: 635},
{PageNum: 1, CornerID: CornerTR, NumFaces: 1, LongestMM: 5},
}
for _, tt := range tests {
bits := encodeMarkerBits(tt)
got, ok := decodeMarkerBits(bits)
if !ok {
t.Errorf("decode failed for %+v", tt)
continue
}
if got.PageNum != tt.PageNum || got.CornerID != tt.CornerID ||
got.NumFaces != tt.NumFaces || got.LongestMM != tt.LongestMM {
t.Errorf("roundtrip mismatch: input=%+v got=%+v", tt, got)
}
}
}
func TestEncodeDecodeParityCheck(t *testing.T) {
bits := encodeMarkerBits(MarkerData{PageNum: 1, CornerID: 2, NumFaces: 6, LongestMM: 50})
// Flip one bit — should fail parity
bits[10] = !bits[10]
_, ok := decodeMarkerBits(bits)
if ok {
t.Error("corrupted bits should fail parity check")
}
}
func TestEncodeMarkerGrid(t *testing.T) {
grid := encodeMarkerGrid(MarkerData{PageNum: 1, CornerID: CornerTL, NumFaces: 6, LongestMM: 50})
// Verify bullseye core in center
bull := bullseyePattern()
for r := 0; r < bullseyeN; r++ {
for c := 0; c < bullseyeN; c++ {
if grid[r+1][c+1] != bull[r][c] {
t.Errorf("bullseye mismatch at (%d,%d): got %v want %v", r, c, grid[r+1][c+1], bull[r][c])
}
}
}
}
func TestDecodeFromGridWithRotation(t *testing.T) {
data := MarkerData{PageNum: 2, CornerID: CornerBL, NumFaces: 8, LongestMM: 100}
grid := encodeMarkerGrid(data)
// Unrotated should decode
got, rot, ok := decodeMarkerFromGrid(grid)
if !ok {
t.Fatal("decode failed for unrotated grid")
}
if rot != 0 {
t.Errorf("expected rotation 0, got %d", rot)
}
if got.CornerID != data.CornerID || got.PageNum != data.PageNum {
t.Errorf("data mismatch: got=%+v want=%+v", got, data)
}
// Rotate 90° and decode
rotated := rotateGrid90CW(grid)
got2, rot2, ok2 := decodeMarkerFromGrid(rotated)
if !ok2 {
t.Fatal("decode failed for 90° rotated grid")
}
if got2.CornerID != data.CornerID {
t.Errorf("rotated decode data mismatch: got=%+v", got2)
}
_ = rot2
}
func TestDataRingCellCount(t *testing.T) {
cells := dataRingCells()
// 7×7 perimeter = (7-1)*4 = 24
if len(cells) != 24 {
t.Errorf("expected 24 data ring cells, got %d", len(cells))
}
// All cells should be on the perimeter (row 0, row 6, col 0, or col 6)
for i, c := range cells {
if c[0] != 0 && c[0] != markerN-1 && c[1] != 0 && c[1] != markerN-1 {
t.Errorf("cell %d at (%d,%d) is not on perimeter", i, c[0], c[1])
}
}
// No duplicates
seen := map[[2]int]bool{}
for _, c := range cells {
if seen[c] {
t.Errorf("duplicate cell at (%d,%d)", c[0], c[1])
}
seen[c] = true
}
}
func TestRenderMarkerSVG(t *testing.T) {
var b strings.Builder
renderMarkerSVG(&b, 50, 50, MarkerData{PageNum: 1, CornerID: 0, NumFaces: 6, LongestMM: 50})
svg := b.String()
// Should contain black cells
if !strings.Contains(svg, `fill="black"`) {
t.Error("marker SVG should contain black cells")
}
if strings.Count(svg, "<rect") < 10 {
t.Errorf("expected at least 10 rect elements for marker, got %d", strings.Count(svg, "<rect"))
}
}
func TestRenderCalibBarsSVG(t *testing.T) {
var b strings.Builder
renderCalibBarsSVG(&b, 10, 200, calibBarSpecs())
svg := b.String()
if !strings.Contains(svg, "5mm") {
t.Error("missing 5mm bar label")
}
if !strings.Contains(svg, "50mm") {
t.Error("missing 50mm bar label")
}
// Encoded bars should have multiple rect elements (not just one per bar)
rectCount := strings.Count(svg, "<rect")
if rectCount < 8 {
t.Errorf("expected at least 8 rect elements for encoded bars, got %d", rectCount)
}
}
func TestCalibBarEncodeDecode(t *testing.T) {
for _, bar := range calibBarSpecs() {
cells := encodeCalibBar(bar.WidthMM)
if !cells[0] {
t.Errorf("bar %d: start cell should be black", bar.WidthMM)
}
if !cells[calibBarCells-1] {
t.Errorf("bar %d: stop cell should be black", bar.WidthMM)
}
val, ok := decodeCalibBar(cells)
if !ok {
t.Errorf("bar %d: decode failed", bar.WidthMM)
}
if val != bar.WidthMM {
t.Errorf("bar %d: decoded %d", bar.WidthMM, val)
}
}
}
func TestMarkerPositions(t *testing.T) {
pos := markerPositionsMM(215.9, 279.4)
if pos[0][0] > 20 || pos[0][1] > 20 {
t.Errorf("TL marker too far from corner: (%.1f, %.1f)", pos[0][0], pos[0][1])
}
if pos[3][0] < 195 || pos[3][1] < 260 {
t.Errorf("BR marker too far from corner: (%.1f, %.1f)", pos[3][0], pos[3][1])
}
}