okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
489999b
upstream
1package uv 2 3import ( 4"image/color" 5"testing" 6"time" 7) 8 9// recursiveColor is a color.Color implementation whose underlying struct 10// contains an interface field, triggering infinite recursion in 11// runtime.ifaceeq when compared with ==. 12// 13// This reproduces the bug where Cell.IsZero() and Style.IsZero() use 14// struct == comparison on types containing color.Color interface fields. 15type recursiveColor struct { 16 color.Color // embedded interface — ifaceeq recurses into this 17r ,g ,b ,a uint8 18} 19 20func (c * recursiveColor )RGBA () (uint32 ,uint32 ,uint32 ,uint32 ) { 21return uint32 (c .r ),uint32 (c .g ),uint32 (c .b ),uint32 (c .a ) 22} 23 24// TestCellIsZero_RecursiveColorPanicsOrHangs tests that Cell.IsZero() 25// does not hang when the cell's style contains a color.Color implementation 26// with an embedded interface. 27// 28// Before the fix, this test would hang forever (CPU 100%) because: 29// 30// Cell.IsZero() → *c == Cell{} 31// → .eq.Cell → .eq.Style → runtime.ifaceeq(color.Color, nil) 32// → recurse into embedded interface → infinite loop 33func TestCellIsZero_RecursiveColorPanicsOrHangs (t * testing.T ) { 34// Use a concrete non-nil color with embedded interface 35c := & Cell { 36Content :"x" , 37Width :1 , 38Style :Style { 39Fg :& recursiveColor {r :255 ,g :0 ,b :0 ,a :255 }, 40 }, 41 } 42 43done := make (chan struct {}) 44go func () { 45defer func () { 46recover ()// in case it panics instead of hanging 47close (done ) 48 }() 49_ = c .IsZero () 50 }() 51 52select { 53case <- done : 54// Good: returned without hanging 55case <- time .After (2 * time .Second ): 56t .Fatal ("Cell.IsZero() hung — likely infinite recursion in runtime.ifaceeq" ) 57 } 58} 59 60// TestStyleIsZero_RecursiveColorPanicsOrHangs tests the same issue for Style.IsZero(). 61func TestStyleIsZero_RecursiveColorPanicsOrHangs (t * testing.T ) { 62s := & Style { 63Fg :& recursiveColor {r :255 ,g :0 ,b :0 ,a :255 }, 64 } 65 66done := make (chan struct {}) 67go func () { 68defer func () { 69recover () 70close (done ) 71 }() 72_ = s .IsZero () 73 }() 74 75select { 76case <- done : 77case <- time .After (2 * time .Second ): 78t .Fatal ("Style.IsZero() hung — likely infinite recursion in runtime.ifaceeq" ) 79 } 80} 81 82// TestCellIsZero_NilColorDoesNotHang verifies the common case still works. 83func TestCellIsZero_NilColorDoesNotHang (t * testing.T ) { 84tests := []struct { 85name string 86cell * Cell 87want bool 88 }{ 89 {"zero cell" ,& Cell {},true }, 90 {"nil style colors" ,& Cell {Content :"x" ,Width :1 },false }, 91 {"empty content with nil colors" ,& Cell {Content :" " ,Width :1 },false }, 92 } 93 94for _ ,tt := range tests { 95t .Run (tt .name ,func (t * testing.T ) { 96got := tt .cell .IsZero () 97if got != tt .want { 98t .Errorf ("IsZero() = %v, want %v" ,got ,tt .want ) 99 } 100 }) 101 } 102}