internal/screen/screen_test.go
Rama main · réplica de lectura del repositorio original.
// SPDX-FileCopyrightText: 2026 Rashik Adhikari
// SPDX-License-Identifier: Apache-2.0
package screen
import (
"bytes"
"math/rand/v2"
"strings"
"testing"
)
// editor builds something the shape of a code editor: a dark background,
// a slightly different gutter, and text-like runs in two or three colours.
//
// Synthetic, and deliberately so. A screenshot checked into a test is a
// licence question and a megabyte in the repository; what matters here is the
// structure — flat colour, hard edges, a small palette per region — and that
// is reproducible.
func editor(w, h int) Frame {
f := Frame{Width: w, Height: h, Pix: make([]byte, w*h*4)}
bg := colour{0x1e, 0x1e, 0x2e, 0xff}
gutter := colour{0x18, 0x18, 0x25, 0xff}
text := colour{0xcd, 0xd6, 0xf4, 0xff}
keyword := colour{0xcb, 0xa6, 0xf7, 0xff}
set := func(x, y int, c colour) {
if x < 0 || y < 0 || x >= w || y >= h {
return
}
copy(f.Pix[(y*w+x)*4:], c[:])
}
for y := range h {
for x := range w {
c := bg
if x < 48 {
c = gutter
}
set(x, y, c)
}
}
// Lines of "text": eight pixels tall, every sixteenth row, with runs of
// glyph-shaped pixels. Deterministic, so the test is too.
r := rand.New(rand.NewPCG(7, 11))
for line := 0; line*16+12 < h; line++ {
y := line*16 + 4
x := 56
for x < w-8 {
run := 3 + r.IntN(6)
c := text
if r.IntN(5) == 0 {
c = keyword
}
for dx := range run {
for dy := range 7 {
if (dx+dy)%3 != 0 {
set(x+dx, y+dy, c)
}
}
}
x += run + 1 + r.IntN(4)
}
}
return f
}
// photo builds something with no palette at all: every pixel different.
func photo(w, h int) Frame {
f := Frame{Width: w, Height: h, Pix: make([]byte, w*h*4)}
r := rand.New(rand.NewPCG(3, 5))
for i := 0; i+4 <= len(f.Pix); i += 4 {
f.Pix[i] = byte(r.IntN(256))
f.Pix[i+1] = byte(r.IntN(256))
f.Pix[i+2] = byte(r.IntN(256))
f.Pix[i+3] = 0xff
}
return f
}
func roundTrip(t *testing.T, e *Encoder, d *Decoder, f Frame) Stats {
t.Helper()
encoded, stats, err := e.Encode(f)
if err != nil {
t.Fatalf("encode: %v", err)
}
back, err := d.Decode(encoded)
if err != nil {
t.Fatalf("decode: %v", err)
}
if back.Width != f.Width || back.Height != f.Height {
t.Fatalf("decoded %dx%d, want %dx%d", back.Width, back.Height,
f.Width, f.Height)
}
if !bytes.Equal(back.Pix, f.Pix) {
var first int
for i := range f.Pix {
if f.Pix[i] != back.Pix[i] {
first = i
break
}
}
t.Fatalf("the decoded frame differs from the original, first at "+
"byte %d (pixel %d)", first, first/4)
}
return stats
}
// Text is either right or it is not. This is the whole argument for the
// package, so it is the first test and it checks every byte.
func TestAFrameSurvivesExactly(t *testing.T) {
f := editor(1280, 720)
stats := roundTrip(t, NewEncoder(), NewDecoder(), f)
if !stats.Key {
t.Error("the first frame is not a key frame")
}
if stats.Changed != stats.Tiles {
t.Errorf("a key frame sent %d of %d tiles", stats.Changed,
stats.Tiles)
}
if stats.Ratio() < 5 {
t.Errorf("an editor frame compressed %.1fx, which is not worth "+
"having", stats.Ratio())
}
t.Logf("key frame: %d bytes from %d, %.0fx, %s", stats.Bytes,
stats.Source, stats.Ratio(), stats.Why())
}
// Typing a character changes one tile out of five hundred, and that is where
// the compression comes from.
func TestTypingSendsOneTile(t *testing.T) {
e, d := NewEncoder(), NewDecoder()
f := editor(1920, 1080)
first := roundTrip(t, e, d, f)
// A character appears: a few pixels in one tile.
next := Frame{Width: f.Width, Height: f.Height,
Pix: append([]byte(nil), f.Pix...)}
for dy := range 7 {
for dx := range 5 {
copy(next.Pix[((404+dy)*next.Width+612+dx)*4:],
[]byte{0xa6, 0xe3, 0xa1, 0xff})
}
}
stats := roundTrip(t, e, d, next)
if stats.Key {
t.Fatal("the second frame is a key frame")
}
if stats.Changed != 1 {
t.Fatalf("typing changed %d tile(s) of %d", stats.Changed,
stats.Tiles)
}
if stats.Bytes > 400 {
t.Errorf("one character cost %d bytes", stats.Bytes)
}
t.Logf("key frame %d bytes, then one character %d bytes (%d tiles)",
first.Bytes, stats.Bytes, stats.Tiles)
}
// A still screen is a header, which is what a meeting looks like for most of
// its length.
func TestAnUnchangedFrameIsAlmostNothing(t *testing.T) {
e, d := NewEncoder(), NewDecoder()
f := editor(1920, 1080)
roundTrip(t, e, d, f)
stats := roundTrip(t, e, d, f)
if stats.Changed != 0 {
t.Fatalf("an identical frame changed %d tile(s)", stats.Changed)
}
if stats.Bytes > 200 {
t.Errorf("an unchanged 1920x1080 frame cost %d bytes", stats.Bytes)
}
if !strings.Contains(stats.Why(), "the frame is a header") {
t.Errorf("the summary is %q", stats.Why())
}
t.Logf("still frame: %d bytes for %d pixels", stats.Bytes,
f.Width*f.Height)
}
// A codec that silently emits fifty megabytes a second when pointed at the
// wrong content is worse than one that says it is the wrong tool.
func TestAPhotographIsReportedAsTheWrongContent(t *testing.T) {
e, d := NewEncoder(), NewDecoder()
f := photo(640, 480)
stats := roundTrip(t, e, d, f)
if !stats.Photographic() {
t.Fatalf("random pixels were not reported as photographic: %+v",
stats)
}
if stats.Raw*2 <= stats.Changed {
t.Errorf("%d of %d tiles went out raw", stats.Raw, stats.Changed)
}
if !strings.Contains(stats.Why(), "worst at") {
t.Errorf("the summary is %q", stats.Why())
}
// It is still lossless, which roundTrip has already checked. It is just
// large — and the point is that it says so.
t.Logf("photograph: %d bytes from %d, %.2fx — %s", stats.Bytes,
stats.Source, stats.Ratio(), stats.Why())
// And an editor frame is not reported that way.
e2, d2 := NewEncoder(), NewDecoder()
if roundTrip(t, e2, d2, editor(640, 480)).Photographic() {
t.Error("an editor frame was called photographic")
}
}
// A terminal is flat colour, and a tile of one colour should cost five bytes
// before deflate.
func TestFlatColourCostsAlmostNothing(t *testing.T) {
f := Frame{Width: 256, Height: 256, Pix: make([]byte, 256*256*4)}
for i := 0; i+4 <= len(f.Pix); i += 4 {
copy(f.Pix[i:], []byte{0x00, 0x00, 0x00, 0xff})
}
stats := roundTrip(t, NewEncoder(), NewDecoder(), f)
if stats.Solid != stats.Tiles {
t.Errorf("%d of %d tiles were solid", stats.Solid, stats.Tiles)
}
// Sixteen tiles at five bytes each, a bitmap, an eighteen-byte header,
// and deflate over the lot.
if stats.Bytes > 150 {
t.Errorf("a blank 256x256 screen cost %d bytes", stats.Bytes)
}
}
// Two colours at one bit a pixel is the text case and is where most of the
// win is.
func TestTwoColourTilesUseOneBitAPixel(t *testing.T) {
f := Frame{Width: 128, Height: 128, Pix: make([]byte, 128*128*4)}
for y := range 128 {
for x := range 128 {
c := []byte{0x1e, 0x1e, 0x2e, 0xff}
if (x/2+y/3)%2 == 0 {
c = []byte{0xcd, 0xd6, 0xf4, 0xff}
}
copy(f.Pix[(y*128+x)*4:], c)
}
}
stats := roundTrip(t, NewEncoder(), NewDecoder(), f)
if stats.Mono != stats.Tiles {
t.Errorf("%d of %d tiles were two-colour", stats.Mono, stats.Tiles)
}
if !strings.Contains(stats.Why(), "which is text") {
t.Errorf("the summary is %q", stats.Why())
}
}
// Sixteen colours is the ceiling, and the seventeenth sends the tile raw.
func TestThePaletteCeilingIsWhereRawBegins(t *testing.T) {
build := func(colours int) Frame {
f := Frame{Width: 64, Height: 64, Pix: make([]byte, 64*64*4)}
for i := 0; i+4 <= len(f.Pix); i += 4 {
v := byte((i / 4) % colours)
copy(f.Pix[i:], []byte{v, v, v, 0xff})
}
return f
}
at := roundTrip(t, NewEncoder(), NewDecoder(), build(MaxPalette))
if at.Palette != 1 {
t.Errorf("%d colours gave %d palette tile(s)", MaxPalette,
at.Palette)
}
over := roundTrip(t, NewEncoder(), NewDecoder(), build(MaxPalette+1))
if over.Raw != 1 {
t.Errorf("%d colours gave %d raw tile(s)", MaxPalette+1, over.Raw)
}
}
// The last column and row are partial, which is where an off-by-one in the
// packing would live.
func TestPartialTilesAtTheEdgesRoundTrip(t *testing.T) {
for _, size := range [][2]int{
{1, 1}, {63, 63}, {65, 65}, {100, 37}, {TileSize, TileSize},
{TileSize + 1, TileSize - 1}, {1920, 1080},
} {
f := editor(size[0], size[1])
roundTrip(t, NewEncoder(), NewDecoder(), f)
}
}
// A receiver that missed a tile is wrong until it is sent another key frame,
// and this codec has no concealment. Saying so is better than pretending a
// lost packet is survivable.
func TestAKeyframeCanBeAskedFor(t *testing.T) {
e, d := NewEncoder(), NewDecoder()
f := editor(320, 240)
roundTrip(t, e, d, f)
if s := roundTrip(t, e, d, f); s.Key {
t.Fatal("the second frame was a key frame unasked")
}
e.Keyframe()
s := roundTrip(t, e, d, f)
if !s.Key {
t.Fatal("a requested key frame was not one")
}
if s.Changed != s.Tiles {
t.Errorf("a key frame sent %d of %d tiles", s.Changed, s.Tiles)
}
}
// A resize is a key frame whether anybody asked or not, because a delta
// against a different shape has nowhere to land.
func TestAResizeForcesAKeyframe(t *testing.T) {
e, d := NewEncoder(), NewDecoder()
roundTrip(t, e, d, editor(320, 240))
s := roundTrip(t, e, d, editor(640, 480))
if !s.Key {
t.Fatal("a resize produced a delta")
}
}
// A decoder that allocated whatever it was told to would be a way to
// exhaust memory from the network.
func TestADecoderRefusesWhatItCannotHold(t *testing.T) {
e := NewEncoder()
good, _, err := e.Encode(editor(128, 128))
if err != nil {
t.Fatal(err)
}
for name, spoil := range map[string]func([]byte) []byte{
"a different magic": func(b []byte) []byte {
out := append([]byte(nil), b...)
out[0] = 'X'
return out
},
"a future version": func(b []byte) []byte {
out := append([]byte(nil), b...)
out[4] = 99
return out
},
"an enormous width": func(b []byte) []byte {
out := append([]byte(nil), b...)
out[6], out[7], out[8], out[9] = 0x7f, 0xff, 0xff, 0xff
return out
},
"a zero height": func(b []byte) []byte {
out := append([]byte(nil), b...)
out[10], out[11], out[12], out[13] = 0, 0, 0, 0
return out
},
"an enormous payload": func(b []byte) []byte {
out := append([]byte(nil), b...)
out[14], out[15], out[16], out[17] = 0x7f, 0xff, 0xff, 0xff
return out
},
"a truncated frame": func(b []byte) []byte { return b[:len(b)/2] },
"nothing at all": func([]byte) []byte { return nil },
} {
if _, err := NewDecoder().Decode(spoil(good)); err == nil {
t.Errorf("a frame with %s was decoded", name)
}
}
// The ceiling message says why it exists rather than just refusing.
bad := append([]byte(nil), good...)
bad[14], bad[15], bad[16], bad[17] = 0x7f, 0xff, 0xff, 0xff
_, err = NewDecoder().Decode(bad)
if !strings.Contains(err.Error(), "exhaust memory") {
t.Errorf("the refusal is %v", err)
}
}
// A delta with no key frame before it has nothing to apply to, and saying so
// is better than painting on an empty buffer.
func TestADeltaWithoutAKeyframeIsRefused(t *testing.T) {
e := NewEncoder()
f := editor(320, 240)
if _, _, err := e.Encode(f); err != nil {
t.Fatal(err)
}
next := Frame{Width: f.Width, Height: f.Height,
Pix: append([]byte(nil), f.Pix...)}
copy(next.Pix[0:], []byte{1, 2, 3, 4})
delta, _, err := e.Encode(next)
if err != nil {
t.Fatal(err)
}
// A fresh decoder has never seen the key frame.
_, err = NewDecoder().Decode(delta)
if err == nil {
t.Fatal("a delta applied to nothing")
}
if !strings.Contains(err.Error(), "missed a key frame") {
t.Errorf("the refusal is %v", err)
}
}
func TestAMalformedFrameIsRefused(t *testing.T) {
for name, f := range map[string]Frame{
"no pixels": {Width: 0, Height: 10, Pix: nil},
"wrong length": {Width: 4, Height: 4, Pix: make([]byte, 10)},
"absurdly wide": {Width: MaxDimension + 1, Height: 1},
} {
if _, _, err := NewEncoder().Encode(f); err == nil {
t.Errorf("a frame with %s was encoded", name)
}
}
}
// The numbers the package exists for, printed so a reader can see them
// rather than take the doc comment's word.
func TestTheArithmeticIsWorthIt(t *testing.T) {
e, d := NewEncoder(), NewDecoder()
f := editor(1920, 1080)
key := roundTrip(t, e, d, f)
next := Frame{Width: f.Width, Height: f.Height,
Pix: append([]byte(nil), f.Pix...)}
for dy := range 7 {
for dx := range 5 {
copy(next.Pix[((404+dy)*next.Width+612+dx)*4:],
[]byte{0xa6, 0xe3, 0xa1, 0xff})
}
}
typed := roundTrip(t, e, d, next)
still := roundTrip(t, e, d, next)
t.Logf("1920x1080, four bytes a pixel: %d bytes raw", key.Source)
t.Logf(" key frame %7d bytes (%.0fx)", key.Bytes, key.Ratio())
t.Logf(" a keystroke %7d bytes", typed.Bytes)
t.Logf(" still %7d bytes", still.Bytes)
t.Logf(" at 30fps, a session of typing is about %d KB/s",
typed.Bytes*30/1024)
if typed.Bytes*30 > key.Bytes {
t.Error("a second of typing costs more than a whole key frame, " +
"which means the delta is not doing its job")
}
}