mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-19 23:29:56 +00:00
161 lines
4.6 KiB
Go
161 lines
4.6 KiB
Go
package server
|
||
|
||
import (
|
||
"image"
|
||
"testing"
|
||
)
|
||
|
||
// fillTile paints a tileSize×tileSize block of img at (x,y) with the colour
|
||
// derived from (r,g,b) so the test can construct distinct-content tiles.
|
||
func fillTile(img *image.RGBA, x, y, ts int, r, g, b byte) {
|
||
for row := 0; row < ts; row++ {
|
||
off := (y+row)*img.Stride + x*4
|
||
for col := 0; col < ts; col++ {
|
||
img.Pix[off+col*4+0] = r
|
||
img.Pix[off+col*4+1] = g
|
||
img.Pix[off+col*4+2] = b
|
||
img.Pix[off+col*4+3] = 0xff
|
||
}
|
||
}
|
||
}
|
||
|
||
// copyTile copies a tileSize×tileSize block from src(sx,sy) to dst(dx,dy).
|
||
func copyTile(dst, src *image.RGBA, sx, sy, dx, dy, ts int) {
|
||
for row := 0; row < ts; row++ {
|
||
srcOff := (sy+row)*src.Stride + sx*4
|
||
dstOff := (dy+row)*dst.Stride + dx*4
|
||
copy(dst.Pix[dstOff:dstOff+ts*4], src.Pix[srcOff:srcOff+ts*4])
|
||
}
|
||
}
|
||
|
||
func TestCopyRectDetector_DetectsVerticalScroll(t *testing.T) {
|
||
const w, h = 256, 192 // 4×3 tiles at 64px
|
||
const ts = 64
|
||
|
||
prev := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
cur := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
|
||
// prev: 12 tiles each with a unique colour.
|
||
for ty := 0; ty < 3; ty++ {
|
||
for tx := 0; tx < 4; tx++ {
|
||
fillTile(prev, tx*ts, ty*ts, ts, byte(tx*40), byte(ty*60), 0x80)
|
||
}
|
||
}
|
||
// cur: simulate a single-tile-row scroll upward — every tile copied from
|
||
// the row below in prev, top row is new content.
|
||
for ty := 0; ty < 2; ty++ {
|
||
for tx := 0; tx < 4; tx++ {
|
||
copyTile(cur, prev, tx*ts, (ty+1)*ts, tx*ts, ty*ts, ts)
|
||
}
|
||
}
|
||
// Bottom row of cur: new colour, not a match.
|
||
for tx := 0; tx < 4; tx++ {
|
||
fillTile(cur, tx*ts, 2*ts, ts, 0xff, 0xff, 0xff)
|
||
}
|
||
|
||
d := newCopyRectDetector(ts)
|
||
d.rebuild(prev, w, h)
|
||
|
||
tiles := diffTiles(prev, cur, w, h, ts)
|
||
moves, remaining := d.extractCopyRectTiles(cur, tiles)
|
||
|
||
// Expect 8 CopyRect moves (top two rows) and 4 residual tiles (bottom row).
|
||
if len(moves) != 8 {
|
||
t.Fatalf("moves: want 8, got %d", len(moves))
|
||
}
|
||
if len(remaining) != 4 {
|
||
t.Fatalf("remaining: want 4, got %d", len(remaining))
|
||
}
|
||
// Spot-check one move: cur (0, 0) should map to prev (0, 64).
|
||
var found bool
|
||
for _, m := range moves {
|
||
if m.dstX == 0 && m.dstY == 0 {
|
||
if m.srcX != 0 || m.srcY != ts {
|
||
t.Fatalf("move at (0,0): src=(%d,%d), want (0,%d)", m.srcX, m.srcY, ts)
|
||
}
|
||
found = true
|
||
}
|
||
}
|
||
if !found {
|
||
t.Fatalf("no move for dst (0,0)")
|
||
}
|
||
}
|
||
|
||
func TestCopyRectDetector_RejectsSelfMatch(t *testing.T) {
|
||
const w, h = 128, 128
|
||
const ts = 64
|
||
|
||
prev := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
cur := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
|
||
// prev: 4 tiles, all unique
|
||
fillTile(prev, 0, 0, ts, 0x10, 0x20, 0x30)
|
||
fillTile(prev, ts, 0, ts, 0x40, 0x50, 0x60)
|
||
fillTile(prev, 0, ts, ts, 0x70, 0x80, 0x90)
|
||
fillTile(prev, ts, ts, ts, 0xa0, 0xb0, 0xc0)
|
||
|
||
// cur: tile (0,0) unchanged, others changed but content same as prev's (0,0).
|
||
fillTile(cur, 0, 0, ts, 0x10, 0x20, 0x30) // self-match
|
||
fillTile(cur, ts, 0, ts, 0xff, 0xff, 0xff)
|
||
fillTile(cur, 0, ts, ts, 0xff, 0xff, 0xff)
|
||
fillTile(cur, ts, ts, ts, 0xff, 0xff, 0xff)
|
||
|
||
d := newCopyRectDetector(ts)
|
||
d.rebuild(prev, w, h)
|
||
|
||
// Tile (0,0) is not in the dirty list (it's unchanged) so it should not
|
||
// produce a move even though its hash matches prev (0,0).
|
||
tiles := diffTiles(prev, cur, w, h, ts)
|
||
moves, _ := d.extractCopyRectTiles(cur, tiles)
|
||
for _, m := range moves {
|
||
if m.dstX == 0 && m.dstY == 0 {
|
||
t.Fatalf("unexpected move at (0,0)")
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestCopyRectDetector_PassThroughWhenNoMatch(t *testing.T) {
|
||
const w, h = 64, 64
|
||
const ts = 64
|
||
|
||
prev := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
cur := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
fillTile(prev, 0, 0, ts, 0x11, 0x22, 0x33)
|
||
fillTile(cur, 0, 0, ts, 0xaa, 0xbb, 0xcc) // wholly different
|
||
|
||
d := newCopyRectDetector(ts)
|
||
d.rebuild(prev, w, h)
|
||
tiles := diffTiles(prev, cur, w, h, ts)
|
||
moves, remaining := d.extractCopyRectTiles(cur, tiles)
|
||
|
||
if len(moves) != 0 {
|
||
t.Fatalf("expected 0 moves, got %d", len(moves))
|
||
}
|
||
if len(remaining) != 1 {
|
||
t.Fatalf("expected 1 residual tile, got %d", len(remaining))
|
||
}
|
||
}
|
||
|
||
func TestEncodeCopyRectBody_Layout(t *testing.T) {
|
||
got := encodeCopyRectBody(100, 200, 300, 400, 64, 48)
|
||
if len(got) != 16 {
|
||
t.Fatalf("CopyRect body length: want 16, got %d", len(got))
|
||
}
|
||
// Dest position
|
||
if got[0] != 0x01 || got[1] != 0x2c || got[2] != 0x01 || got[3] != 0x90 {
|
||
t.Fatalf("bad dest bytes: % x", got[0:4])
|
||
}
|
||
// Width, height
|
||
if got[4] != 0 || got[5] != 64 || got[6] != 0 || got[7] != 48 {
|
||
t.Fatalf("bad size bytes: % x", got[4:8])
|
||
}
|
||
// Encoding = 1
|
||
if got[11] != 0x01 {
|
||
t.Fatalf("bad encoding byte: 0x%02x", got[11])
|
||
}
|
||
// Source position
|
||
if got[12] != 0 || got[13] != 100 || got[14] != 0 || got[15] != 200 {
|
||
t.Fatalf("bad src bytes: % x", got[12:16])
|
||
}
|
||
}
|