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