| @@ -1,355 +0,0 @@ |
| 1 | - | package atspi | | | |
| 2 | - | | | | |
| 3 | - | import ( | | | |
| 4 | - | "context" | | | |
| 5 | - | "errors" | | | |
| 6 | - | "fmt" | | | |
| 7 | - | "runtime" | | | |
| 8 | - | "testing" | | | |
| 9 | - | "time" | | | |
| 10 | - | | | | |
| 11 | - | "github.com/stretchr/testify/mock" | | | |
| 12 | - | "github.com/stretchr/testify/require" | | | |
| 13 | - | | | | |
| 14 | - | "github.com/bnema/neferwl/internal/ports" | | | |
| 15 | - | ) | | | |
| 16 | - | | | | |
| 17 | - | const ( | | | |
| 18 | - | stActive = 1 << stateActive | | | |
| 19 | - | stShowing = 1 << stateShowing | | | |
| 20 | - | ) | | | |
| 21 | - | | | | |
| 22 | - | // tree answers calls like an app: registry with one app (:1.5, pid 77) with | | | |
| 23 | - | // one showing frame whose links sit at window-relative extents. | | | |
| 24 | - | func answerTree(frameExt [4]int32, matches []ref, ext map[string][4]int32, roles map[string]uint32, noCollection bool) func(time.Time, time.Duration, []call) []reply { | | | |
| 25 | - | return func(_ time.Time, _ time.Duration, calls []call) []reply { | | | |
| 26 | - | out := make([]reply, len(calls)) | | | |
| 27 | - | for i, c := range calls { | | | |
| 28 | - | switch { | | | |
| 29 | - | case c.op == opChildren && c.name == registryName: | | | |
| 30 | - | out[i].refs = []ref{{":1.5", "/root"}, {":1.9", "/root"}} | | | |
| 31 | - | case c.op == opPID: | | | |
| 32 | - | out[i].u = map[string]uint32{":1.5": 77, ":1.9": 99}[c.name] | | | |
| 33 | - | case c.op == opChildren && c.path == "/root": | | | |
| 34 | - | out[i].refs = []ref{{":1.5", "/frame"}} | | | |
| 35 | - | case c.op == opRole && c.path == "/frame": | | | |
| 36 | - | out[i].u = roleFrame | | | |
| 37 | - | case c.op == opState && c.path == "/frame": | | | |
| 38 | - | out[i].state = stShowing | stActive | | | |
| 39 | - | case c.op == opExtents && c.path == "/frame": | | | |
| 40 | - | out[i].rect = frameExt | | | |
| 41 | - | case c.op == opMatches: | | | |
| 42 | - | if noCollection { | | | |
| 43 | - | out[i].err = &busError{name: "org.freedesktop.DBus.Error.UnknownMethod"} | | | |
| 44 | - | } else { | | | |
| 45 | - | out[i].refs = matches | | | |
| 46 | - | } | | | |
| 47 | - | case c.op == opChildren && c.path == "/frame": | | | |
| 48 | - | out[i].refs = matches | | | |
| 49 | - | case c.op == opChildren: | | | |
| 50 | - | case c.op == opRole: | | | |
| 51 | - | out[i].u = roles[c.path] | | | |
| 52 | - | case c.op == opState: | | | |
| 53 | - | out[i].state = stShowing | 1<<stateVisible | | | |
| 54 | - | case c.op == opExtents: | | | |
| 55 | - | out[i].rect = ext[c.path] | | | |
| 56 | - | } | | | |
| 57 | - | } | | | |
| 58 | - | return out | | | |
| 59 | - | } | | | |
| 60 | - | } | | | |
| 61 | - | | | | |
| 62 | - | func TestTargetsCoversWindowAndConverts(t *testing.T) { | | | |
| 63 | - | tr := newMocktransport(t) | | | |
| 64 | - | matches := []ref{{":1.5", "/a"}, {":1.5", "/b"}, {":1.5", "/zero"}} | | | |
| 65 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree( | | | |
| 66 | - | [4]int32{4, 30, 800, 600}, matches, | | | |
| 67 | - | map[string][4]int32{"/a": {14, 130, 50, 16}, "/b": {104, 230, 20, 20}, "/zero": {5, 5, 0, 0}}, | | | |
| 68 | - | map[string]uint32{"/a": roleLink, "/b": rolePushButton, "/zero": roleLink}, false)) | | | |
| 69 | - | s := &Source{tr: tr} | | | |
| 70 | - | | | | |
| 71 | - | windows := []ports.HintWindow{ | | | |
| 72 | - | {ID: 1, PID: 77, Rect: ports.Rect{X: 100, Y: 200, W: 800, H: 600}, Focused: true}, | | | |
| 73 | - | {ID: 2, PID: 5, Rect: ports.Rect{X: 0, Y: 0, W: 50, H: 50}}, | | | |
| 74 | - | } | | | |
| 75 | - | targets, covered, err := s.Targets(context.Background(), windows, false) | | | |
| 76 | - | require.NoError(t, err) | | | |
| 77 | - | require.Equal(t, map[ports.WindowID]bool{1: true}, covered) | | | |
| 78 | - | require.Equal(t, []ports.HintTarget{ | | | |
| 79 | - | {Rect: ports.Rect{X: 110, Y: 300, W: 50, H: 16}, Kind: ports.HintLink, Window: 1}, | | | |
| 80 | - | {Rect: ports.Rect{X: 200, Y: 400, W: 20, H: 20}, Kind: ports.HintButton, Window: 1}, | | | |
| 81 | - | }, targets) | | | |
| 82 | - | } | | | |
| 83 | - | | | | |
| 84 | - | func TestTargetsLinksOnlyDropsButtons(t *testing.T) { | | | |
| 85 | - | tr := newMocktransport(t) | | | |
| 86 | - | matches := []ref{{":1.5", "/a"}, {":1.5", "/b"}} | | | |
| 87 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree( | | | |
| 88 | - | [4]int32{0, 0, 800, 600}, matches, | | | |
| 89 | - | map[string][4]int32{"/a": {14, 130, 50, 16}, "/b": {104, 230, 20, 20}}, | | | |
| 90 | - | map[string]uint32{"/a": roleLink, "/b": rolePushButton}, false)) | | | |
| 91 | - | s := &Source{tr: tr} | | | |
| 92 | - | targets, _, err := s.Targets(context.Background(), []ports.HintWindow{{ID: 1, PID: 77, Rect: ports.Rect{W: 800, H: 600}}}, true) | | | |
| 93 | - | require.NoError(t, err) | | | |
| 94 | - | require.Len(t, targets, 1) | | | |
| 95 | - | require.Equal(t, ports.HintLink, targets[0].Kind) | | | |
| 96 | - | } | | | |
| 97 | - | | | | |
| 98 | - | func TestTargetsWalkWithoutCollection(t *testing.T) { | | | |
| 99 | - | tr := newMocktransport(t) | | | |
| 100 | - | matches := []ref{{":1.5", "/a"}} | | | |
| 101 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree( | | | |
| 102 | - | [4]int32{0, 0, 800, 600}, matches, | | | |
| 103 | - | map[string][4]int32{"/a": {14, 130, 50, 16}}, | | | |
| 104 | - | map[string]uint32{"/a": roleLink}, true)) | | | |
| 105 | - | s := &Source{tr: tr} | | | |
| 106 | - | targets, covered, err := s.Targets(context.Background(), []ports.HintWindow{{ID: 1, PID: 77, Rect: ports.Rect{W: 800, H: 600}}}, false) | | | |
| 107 | - | require.NoError(t, err) | | | |
| 108 | - | require.Len(t, targets, 1) | | | |
| 109 | - | require.True(t, covered[1]) | | | |
| 110 | - | } | | | |
| 111 | - | | | | |
| 112 | - | func TestTargetsNoPIDMatchCoversNothing(t *testing.T) { | | | |
| 113 | - | tr := newMocktransport(t) | | | |
| 114 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree([4]int32{}, nil, nil, nil, false)) | | | |
| 115 | - | s := &Source{tr: tr} | | | |
| 116 | - | targets, covered, err := s.Targets(context.Background(), []ports.HintWindow{{ID: 1, PID: 1234, Rect: ports.Rect{W: 8, H: 6}}}, false) | | | |
| 117 | - | require.NoError(t, err) | | | |
| 118 | - | require.Empty(t, targets) | | | |
| 119 | - | require.Empty(t, covered) | | | |
| 120 | - | } | | | |
| 121 | - | | | | |
| 122 | - | func TestTargetsWithoutPIDsAsksNothing(t *testing.T) { | | | |
| 123 | - | s := &Source{tr: newMocktransport(t)} | | | |
| 124 | - | _, covered, err := s.Targets(context.Background(), []ports.HintWindow{{ID: 1, Rect: ports.Rect{W: 8, H: 6}}}, false) | | | |
| 125 | - | require.NoError(t, err) | | | |
| 126 | - | require.Empty(t, covered) | | | |
| 127 | - | } | | | |
| 128 | - | | | | |
| 129 | - | func TestCoverageRequiresLinkOrDocument(t *testing.T) { | | | |
| 130 | - | for _, collection := range []bool{true, false} { | | | |
| 131 | - | for _, role := range []uint32{rolePushButton, roleDocumentFrame, roleDocumentWeb, roleLink} { | | | |
| 132 | - | t.Run(fmt.Sprintf("collection=%t/role=%d", collection, role), func(t *testing.T) { | | | |
| 133 | - | tr := newMocktransport(t) | | | |
| 134 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree( | | | |
| 135 | - | [4]int32{0, 0, 800, 600}, []ref{{":1.5", "/a"}}, map[string][4]int32{"/a": {10, 20, 30, 40}}, map[string]uint32{"/a": role}, !collection)) | | | |
| 136 | - | s := &Source{tr: tr} | | | |
| 137 | - | targets, covered, err := s.Targets(context.Background(), []ports.HintWindow{{ID: 1, PID: 77, Rect: ports.Rect{W: 800, H: 600}}}, false) | | | |
| 138 | - | require.NoError(t, err) | | | |
| 139 | - | require.Equal(t, role != rolePushButton, covered[1]) | | | |
| 140 | - | if isDocument(role) { | | | |
| 141 | - | require.Empty(t, targets) | | | |
| 142 | - | } else { | | | |
| 143 | - | require.Len(t, targets, 1) | | | |
| 144 | - | } | | | |
| 145 | - | }) | | | |
| 146 | - | } | | | |
| 147 | - | } | | | |
| 148 | - | } | | | |
| 149 | - | | | | |
| 150 | - | var oneWindow = []ports.HintWindow{{ID: 1, PID: 77, Rect: ports.Rect{W: 800, H: 600}}} | | | |
| 151 | - | | | | |
| 152 | - | // New does not touch the bus; the first Targets call connects, once. | | | |
| 153 | - | func TestConnectsLazilyOnFirstTargets(t *testing.T) { | | | |
| 154 | - | tr := newMocktransport(t) | | | |
| 155 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree( | | | |
| 156 | - | [4]int32{0, 0, 800, 600}, []ref{{":1.5", "/a"}}, map[string][4]int32{"/a": {1, 1, 9, 9}}, map[string]uint32{"/a": roleLink}, false)) | | | |
| 157 | - | c := newMockconnector(t) | | | |
| 158 | - | c.EXPECT().Connect(mock.Anything).Return(tr, nil).Once() | | | |
| 159 | - | s := &Source{dial: c} | | | |
| 160 | - | for range 2 { | | | |
| 161 | - | targets, _, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 162 | - | require.NoError(t, err) | | | |
| 163 | - | require.Len(t, targets, 1) | | | |
| 164 | - | } | | | |
| 165 | - | } | | | |
| 166 | - | | | | |
| 167 | - | // Without PIDs nothing is asked and nothing is connected. | | | |
| 168 | - | func TestNoConnectWithoutWindows(t *testing.T) { | | | |
| 169 | - | s := &Source{dial: newMockconnector(t)} | | | |
| 170 | - | _, _, err := s.Targets(context.Background(), []ports.HintWindow{{ID: 1}}, false) | | | |
| 171 | - | require.NoError(t, err) | | | |
| 172 | - | } | | | |
| 173 | - | | | | |
| 174 | - | // A failed connection is retried no sooner than retryEvery; meanwhile no | | | |
| 175 | - | // targets are returned. | | | |
| 176 | - | func TestConnectRetriesWithBackoff(t *testing.T) { | | | |
| 177 | - | c := newMockconnector(t) | | | |
| 178 | - | c.EXPECT().Connect(mock.Anything).Return(nil, errors.New("down")).Once() | | | |
| 179 | - | s := &Source{dial: c} | | | |
| 180 | - | for range 3 { | | | |
| 181 | - | targets, covered, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 182 | - | require.NoError(t, err) | | | |
| 183 | - | require.Empty(t, targets) | | | |
| 184 | - | require.Empty(t, covered) | | | |
| 185 | - | } | | | |
| 186 | - | tr := newMocktransport(t) | | | |
| 187 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree( | | | |
| 188 | - | [4]int32{0, 0, 800, 600}, []ref{{":1.5", "/a"}}, map[string][4]int32{"/a": {1, 1, 9, 9}}, map[string]uint32{"/a": roleLink}, false)) | | | |
| 189 | - | c.EXPECT().Connect(mock.Anything).Return(tr, nil).Once() | | | |
| 190 | - | s.retryAt = time.Now().Add(-time.Second) | | | |
| 191 | - | targets, _, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 192 | - | require.NoError(t, err) | | | |
| 193 | - | require.Len(t, targets, 1) | | | |
| 194 | - | } | | | |
| 195 | - | | | | |
| 196 | - | // A broken connection is closed and dropped; the next attempt reconnects. | | | |
| 197 | - | func TestBrokenConnectionIsDropped(t *testing.T) { | | | |
| 198 | - | tr := newMocktransport(t) | | | |
| 199 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( | | | |
| 200 | - | func(_ time.Time, _ time.Duration, calls []call) []reply { | | | |
| 201 | - | out := make([]reply, len(calls)) | | | |
| 202 | - | for i := range out { | | | |
| 203 | - | out[i].err = ErrBroken | | | |
| 204 | - | } | | | |
| 205 | - | return out | | | |
| 206 | - | }).Once() | | | |
| 207 | - | tr.EXPECT().Close().Return(nil).Once() | | | |
| 208 | - | c := newMockconnector(t) | | | |
| 209 | - | c.EXPECT().Connect(mock.Anything).Return(tr, nil).Once() | | | |
| 210 | - | s := &Source{dial: c} | | | |
| 211 | - | _, _, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 212 | - | require.Error(t, err) | | | |
| 213 | - | require.Nil(t, s.tr) | | | |
| 214 | - | targets, _, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 215 | - | require.NoError(t, err) | | | |
| 216 | - | require.Empty(t, targets) // backoff: no new attempt yet | | | |
| 217 | - | } | | | |
| 218 | - | | | | |
| 219 | - | const testWait = 20 * time.Millisecond | | | |
| 220 | - | | | | |
| 221 | - | // blockedConnector returns a connector whose Connect blocks until release is | | | |
| 222 | - | func blockedConnector(t *testing.T, release <-chan struct{}, tr transport, err error, times int) *mockconnector { | | | |
| 223 | - | t.Helper() | | | |
| 224 | - | c := newMockconnector(t) | | | |
| 225 | - | c.EXPECT().Connect(mock.Anything).RunAndReturn(func(context.Context) (transport, error) { | | | |
| 226 | - | <-release | | | |
| 227 | - | return tr, err | | | |
| 228 | - | }).Times(times) | | | |
| 229 | - | return c | | | |
| 230 | - | } | | | |
| 231 | - | | | | |
| 232 | - | // A silent service cannot hold Targets beyond the wait, is not doubled while | | | |
| 233 | - | // in flight, and its late transport is used. | | | |
| 234 | - | func TestConnectIsBoundedAndNotDoubled(t *testing.T) { | | | |
| 235 | - | release := make(chan struct{}) | | | |
| 236 | - | tr := newMocktransport(t) | | | |
| 237 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree( | | | |
| 238 | - | [4]int32{0, 0, 800, 600}, []ref{{":1.5", "/a"}}, map[string][4]int32{"/a": {1, 1, 9, 9}}, map[string]uint32{"/a": roleLink}, false)) | | | |
| 239 | - | c := blockedConnector(t, release, tr, nil, 1) | | | |
| 240 | - | s := &Source{dial: c, wait: testWait} | | | |
| 241 | - | t0 := time.Now() | | | |
| 242 | - | targets, _, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 243 | - | require.NoError(t, err) | | | |
| 244 | - | require.Empty(t, targets) | | | |
| 245 | - | require.Less(t, time.Since(t0), testWait+200*time.Millisecond) | | | |
| 246 | - | require.NotNil(t, s.attempt) | | | |
| 247 | - | // Still in flight: no second Connect (Times(1)), and no blocking. | | | |
| 248 | - | targets, _, err = s.Targets(context.Background(), oneWindow, false) | | | |
| 249 | - | require.NoError(t, err) | | | |
| 250 | - | require.Empty(t, targets) | | | |
| 251 | - | // It connects late: the next call uses it. | | | |
| 252 | - | close(release) | | | |
| 253 | - | require.Eventually(t, func() bool { return len(s.attempt) == 1 }, time.Second, time.Millisecond) | | | |
| 254 | - | targets, _, err = s.Targets(context.Background(), oneWindow, false) | | | |
| 255 | - | require.NoError(t, err) | | | |
| 256 | - | require.Len(t, targets, 1) | | | |
| 257 | - | require.Nil(t, s.attempt) | | | |
| 258 | - | } | | | |
| 259 | - | | | | |
| 260 | - | // An abandoned attempt that fails applies the backoff, then one new attempt. | | | |
| 261 | - | func TestAbandonedAttemptErrorBacksOff(t *testing.T) { | | | |
| 262 | - | release := make(chan struct{}) | | | |
| 263 | - | tr := newMocktransport(t) | | | |
| 264 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(answerTree( | | | |
| 265 | - | [4]int32{0, 0, 800, 600}, []ref{{":1.5", "/a"}}, map[string][4]int32{"/a": {1, 1, 9, 9}}, map[string]uint32{"/a": roleLink}, false)) | | | |
| 266 | - | c := newMockconnector(t) | | | |
| 267 | - | c.EXPECT().Connect(mock.Anything).RunAndReturn(func(context.Context) (transport, error) { | | | |
| 268 | - | <-release | | | |
| 269 | - | return nil, errors.New("down") | | | |
| 270 | - | }).Once() | | | |
| 271 | - | c.EXPECT().Connect(mock.Anything).Return(tr, nil).Once() | | | |
| 272 | - | s := &Source{dial: c, wait: testWait} | | | |
| 273 | - | targets, _, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 274 | - | require.NoError(t, err) | | | |
| 275 | - | require.Empty(t, targets) | | | |
| 276 | - | close(release) | | | |
| 277 | - | require.Eventually(t, func() bool { return len(s.attempt) == 1 }, time.Second, time.Millisecond) | | | |
| 278 | - | // The error is collected; the backoff runs from its finish time. | | | |
| 279 | - | targets, _, err = s.Targets(context.Background(), oneWindow, false) | | | |
| 280 | - | require.NoError(t, err) | | | |
| 281 | - | require.Empty(t, targets) | | | |
| 282 | - | require.Nil(t, s.attempt) | | | |
| 283 | - | require.WithinDuration(t, time.Now().Add(retryEvery), s.retryAt, time.Second) | | | |
| 284 | - | targets, _, err = s.Targets(context.Background(), oneWindow, false) // backoff: no Connect | | | |
| 285 | - | require.NoError(t, err) | | | |
| 286 | - | require.Empty(t, targets) | | | |
| 287 | - | s.retryAt = time.Now().Add(-time.Second) | | | |
| 288 | - | targets, _, err = s.Targets(context.Background(), oneWindow, false) | | | |
| 289 | - | require.NoError(t, err) | | | |
| 290 | - | require.Len(t, targets, 1) | | | |
| 291 | - | } | | | |
| 292 | - | | | | |
| 293 | - | // Close with an attempt in flight closes the late transport; afterwards | | | |
| 294 | - | // Targets finds nothing and never connects. | | | |
| 295 | - | func TestCloseReapsLateTransport(t *testing.T) { | | | |
| 296 | - | release := make(chan struct{}) | | | |
| 297 | - | closed := make(chan struct{}) | | | |
| 298 | - | tr := newMocktransport(t) | | | |
| 299 | - | tr.EXPECT().Close().RunAndReturn(func() error { close(closed); return nil }).Once() | | | |
| 300 | - | c := blockedConnector(t, release, tr, nil, 1) | | | |
| 301 | - | s := &Source{dial: c, wait: testWait} | | | |
| 302 | - | _, _, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 303 | - | require.NoError(t, err) | | | |
| 304 | - | require.NoError(t, s.Close()) | | | |
| 305 | - | close(release) | | | |
| 306 | - | select { | | | |
| 307 | - | case <-closed: | | | |
| 308 | - | case <-time.After(5 * time.Second): | | | |
| 309 | - | t.Fatal("late transport not closed") | | | |
| 310 | - | } | | | |
| 311 | - | targets, covered, err := s.Targets(context.Background(), oneWindow, false) | | | |
| 312 | - | require.NoError(t, err) | | | |
| 313 | - | require.Empty(t, targets) | | | |
| 314 | - | require.Empty(t, covered) | | | |
| 315 | - | require.NoError(t, s.Close()) // idempotent | | | |
| 316 | - | } | | | |
| 317 | - | | | | |
| 318 | - | func TestWalkWideTreeStaysWithinBudget(t *testing.T) { | | | |
| 319 | - | tr := newMocktransport(t) | | | |
| 320 | - | children := make([]ref, 4096) | | | |
| 321 | - | for i := range children { | | | |
| 322 | - | children[i] = ref{":1.5", fmt.Sprintf("/node%d", i)} | | | |
| 323 | - | } | | | |
| 324 | - | visited := 0 | | | |
| 325 | - | tr.EXPECT().Do(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( | | | |
| 326 | - | func(_ time.Time, _ time.Duration, calls []call) []reply { | | | |
| 327 | - | visited += len(calls) / 3 | | | |
| 328 | - | require.LessOrEqual(t, visited, maxWalkNodes) | | | |
| 329 | - | rep := make([]reply, len(calls)) | | | |
| 330 | - | for i, c := range calls { | | | |
| 331 | - | switch c.op { | | | |
| 332 | - | case opChildren: | | | |
| 333 | - | rep[i].refs = children | | | |
| 334 | - | case opRole: | | | |
| 335 | - | rep[i].u = roleLink | | | |
| 336 | - | case opState: | | | |
| 337 | - | rep[i].state = stShowing | 1<<stateVisible | | | |
| 338 | - | } | | | |
| 339 | - | } | | | |
| 340 | - | return rep | | | |
| 341 | - | }).Twice() | | | |
| 342 | - | s := &Source{tr: tr} | | | |
| 343 | - | // A full second level replies with another 4096 children each. There is | | | |
| 344 | - | // no remaining budget, so none of those millions of refs may be copied. | | | |
| 345 | - | var out []ref | | | |
| 346 | - | var before, after runtime.MemStats | | | |
| 347 | - | runtime.ReadMemStats(&before) | | | |
| 348 | - | out = s.walk(func(calls []call) []reply { return tr.Do(time.Time{}, time.Second, calls) }, ref{":1.5", "/frame"}, false) | | | |
| 349 | - | runtime.ReadMemStats(&after) | | | |
| 350 | - | require.Len(t, out, maxWalkNodes) | | | |
| 351 | - | require.Equal(t, maxWalkNodes, visited) | | | |
| 352 | - | // Normal replies and call batches take only a few MB; the old append- | | | |
| 353 | - | // then-truncate path copied hundreds of MB for this tree. | | | |
| 354 | - | require.Less(t, after.TotalAlloc-before.TotalAlloc, uint64(32<<20)) | | | |
| 355 | - | } | | | |