image_cache_test.dart 24.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:typed_data';
6 7
import 'dart:ui' as ui;

8
import 'package:flutter/foundation.dart';
9
import 'package:flutter/painting.dart';
10
import 'package:flutter/scheduler.dart';
11
import 'package:flutter_test/flutter_test.dart';
12

13
import '../rendering/rendering_tester.dart';
14 15 16
import 'mocks_for_image_cache.dart';

void main() {
17
  TestRenderingFlutterBinding();
18

19
  tearDown(() {
20 21 22 23 24
    imageCache!
      ..clear()
      ..clearLiveImages()
      ..maximumSize = 1000
      ..maximumSizeBytes = 10485760;
25
  });
26

27
  test('maintains cache size', () async {
28
    imageCache!.maximumSize = 3;
29

30
    final TestImageInfo a = await extractOneFrame(TestImageProvider(1, 1, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
31
    expect(a.value, equals(1));
32
    final TestImageInfo b = await extractOneFrame(TestImageProvider(1, 2, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
33
    expect(b.value, equals(1));
34
    final TestImageInfo c = await extractOneFrame(TestImageProvider(1, 3, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
35
    expect(c.value, equals(1));
36
    final TestImageInfo d = await extractOneFrame(TestImageProvider(1, 4, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
37
    expect(d.value, equals(1));
38
    final TestImageInfo e = await extractOneFrame(TestImageProvider(1, 5, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
39
    expect(e.value, equals(1));
40
    final TestImageInfo f = await extractOneFrame(TestImageProvider(1, 6, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
41
    expect(f.value, equals(1));
42

43
    expect(f, equals(a));
44

45
    // cache still only has one entry in it: 1(1)
46

47
    final TestImageInfo g = await extractOneFrame(TestImageProvider(2, 7, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
48
    expect(g.value, equals(7));
49

50
    // cache has two entries in it: 1(1), 2(7)
51

52
    final TestImageInfo h = await extractOneFrame(TestImageProvider(1, 8, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
53
    expect(h.value, equals(1));
54

55
    // cache still has two entries in it: 2(7), 1(1)
56

57
    final TestImageInfo i = await extractOneFrame(TestImageProvider(3, 9, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
58
    expect(i.value, equals(9));
59

60
    // cache has three entries in it: 2(7), 1(1), 3(9)
61

62
    final TestImageInfo j = await extractOneFrame(TestImageProvider(1, 10, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
63
    expect(j.value, equals(1));
64

65
    // cache still has three entries in it: 2(7), 3(9), 1(1)
66

67
    final TestImageInfo k = await extractOneFrame(TestImageProvider(4, 11, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
68
    expect(k.value, equals(11));
69

70
    // cache has three entries: 3(9), 1(1), 4(11)
71

72
    final TestImageInfo l = await extractOneFrame(TestImageProvider(1, 12, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
73
    expect(l.value, equals(1));
74

75
    // cache has three entries: 3(9), 4(11), 1(1)
76

77
    final TestImageInfo m = await extractOneFrame(TestImageProvider(2, 13, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
78
    expect(m.value, equals(13));
79

80
    // cache has three entries: 4(11), 1(1), 2(13)
81

82
    final TestImageInfo n = await extractOneFrame(TestImageProvider(3, 14, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
83
    expect(n.value, equals(14));
84

85
    // cache has three entries: 1(1), 2(13), 3(14)
86

87
    final TestImageInfo o = await extractOneFrame(TestImageProvider(4, 15, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
88
    expect(o.value, equals(15));
89

90
    // cache has three entries: 2(13), 3(14), 4(15)
91

92
    final TestImageInfo p = await extractOneFrame(TestImageProvider(1, 16, image: await createTestImage()).resolve(ImageConfiguration.empty)) as TestImageInfo;
93
    expect(p.value, equals(16));
94

95 96
    // cache has three entries: 3(14), 4(15), 1(16)
  });
97

98
  test('clear removes all images and resets cache size', () async {
99
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
100

101 102
    expect(imageCache!.currentSize, 0);
    expect(imageCache!.currentSizeBytes, 0);
103

104 105
    await extractOneFrame(TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
    await extractOneFrame(TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
106

107 108
    expect(imageCache!.currentSize, 2);
    expect(imageCache!.currentSizeBytes, 256 * 2);
109

110
    imageCache!.clear();
111

112 113
    expect(imageCache!.currentSize, 0);
    expect(imageCache!.currentSizeBytes, 0);
114
  });
115

116
  test('evicts individual images', () async {
117 118 119
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
    await extractOneFrame(TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
    await extractOneFrame(TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
120

121 122 123 124 125
    expect(imageCache!.currentSize, 2);
    expect(imageCache!.currentSizeBytes, 256 * 2);
    expect(imageCache!.evict(1), true);
    expect(imageCache!.currentSize, 1);
    expect(imageCache!.currentSizeBytes, 256);
126
  });
127

128
  test('Do not cache large images', () async {
129
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
130

131
    imageCache!.maximumSizeBytes = 1;
132
    await extractOneFrame(TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
133 134 135
    expect(imageCache!.currentSize, 0);
    expect(imageCache!.currentSizeBytes, 0);
    expect(imageCache!.maximumSizeBytes, 1);
136
  });
137

138
  test('Returns null if an error is caught resolving an image', () {
139
    Future<ui.Codec> _basicDecoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
140
      return PaintingBinding.instance!.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
141
    }
142
    final ErrorImageProvider errorImage = ErrorImageProvider();
143
    expect(() => imageCache!.putIfAbsent(errorImage, () => errorImage.load(errorImage, _basicDecoder)), throwsA(isA<Error>()));
144
    bool caughtError = false;
145 146 147 148 149 150 151
    final ImageStreamCompleter? result = imageCache!.putIfAbsent(
      errorImage,
      () => errorImage.load(errorImage, _basicDecoder),
      onError: (dynamic error, StackTrace? stackTrace) {
       caughtError = true;
      },
    );
152 153 154
    expect(result, null);
    expect(caughtError, true);
  });
155

156
  test('already pending image is returned when it is put into the cache again', () async {
157
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
158

159 160
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
    final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
161

162
    final TestImageStreamCompleter resultingCompleter1 = imageCache!.putIfAbsent(testImage, () {
163
      return completer1;
164
    })! as TestImageStreamCompleter;
165
    final TestImageStreamCompleter resultingCompleter2 = imageCache!.putIfAbsent(testImage, () {
166
      return completer2;
167
    })! as TestImageStreamCompleter;
168

169 170 171
    expect(resultingCompleter1, completer1);
    expect(resultingCompleter2, completer1);
  });
172

173
  test('pending image is removed when cache is cleared', () async {
174
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
175

176 177
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
    final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
178

179
    final TestImageStreamCompleter resultingCompleter1 = imageCache!.putIfAbsent(testImage, () {
180
      return completer1;
181
    })! as TestImageStreamCompleter;
182

183 184 185 186 187 188 189 190
    expect(imageCache!.statusForKey(testImage).pending, true);
    expect(imageCache!.statusForKey(testImage).live, true);
    imageCache!.clear();
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    imageCache!.clearLiveImages();
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, false);
191

192
    final TestImageStreamCompleter resultingCompleter2 = imageCache!.putIfAbsent(testImage, () {
193
      return completer2;
194
    })! as TestImageStreamCompleter;
195

196 197 198
    expect(resultingCompleter1, completer1);
    expect(resultingCompleter2, completer2);
  });
199

200
  test('pending image is removed when image is evicted', () async {
201
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
202

203 204
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
    final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
205

206
    final TestImageStreamCompleter resultingCompleter1 = imageCache!.putIfAbsent(testImage, () {
207
      return completer1;
208
    })! as TestImageStreamCompleter;
209

210
    imageCache!.evict(testImage);
211

212
    final TestImageStreamCompleter resultingCompleter2 = imageCache!.putIfAbsent(testImage, () {
213
      return completer2;
214
    })! as TestImageStreamCompleter;
215

216 217 218
    expect(resultingCompleter1, completer1);
    expect(resultingCompleter2, completer2);
  });
219

220
  test("failed image can successfully be removed from the cache's pending images", () async {
221
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
222

223
    FailingTestImageProvider(1, 1, image: testImage)
224 225
        .resolve(ImageConfiguration.empty)
        .addListener(ImageStreamListener(
226 227 228
          (ImageInfo image, bool synchronousCall) {
            fail('Image should not complete successfully');
           },
229 230
          onError: (dynamic exception, StackTrace? stackTrace) {
            final bool evictionResult = imageCache!.evict(1);
231
            expect(evictionResult, isTrue);
232 233
          },
        ));
234 235
    // yield an event turn so that async work can complete.
    await null;
236
  });
237

238
  test('containsKey - pending', () async {
239
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
240

241
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
242

243
    final TestImageStreamCompleter resultingCompleter1 = imageCache!.putIfAbsent(testImage, () {
244
      return completer1;
245
    })! as TestImageStreamCompleter;
246

247
    expect(resultingCompleter1, completer1);
248
    expect(imageCache!.containsKey(testImage), true);
249
  });
250

251
  test('containsKey - completed', () async {
252
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
253

254
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
255

256
    final TestImageStreamCompleter resultingCompleter1 = imageCache!.putIfAbsent(testImage, () {
257
      return completer1;
258
    })! as TestImageStreamCompleter;
259

260 261
    // Mark as complete
    completer1.testSetImage(testImage);
262

263
    expect(resultingCompleter1, completer1);
264
    expect(imageCache!.containsKey(testImage), true);
265
  });
Dan Field's avatar
Dan Field committed
266

267
  test('putIfAbsent updates LRU properties of a live image', () async {
268
    imageCache!.maximumSize = 1;
269 270
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
    final ui.Image testImage2 = await createTestImage(width: 10, height: 10);
Dan Field's avatar
Dan Field committed
271

272 273
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()..testSetImage(testImage);
    final TestImageStreamCompleter completer2 = TestImageStreamCompleter()..testSetImage(testImage2);
Dan Field's avatar
Dan Field committed
274

275
    completer1.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
Dan Field's avatar
Dan Field committed
276

277
    final TestImageStreamCompleter resultingCompleter1 = imageCache!.putIfAbsent(testImage, () {
278
      return completer1;
279
    })! as TestImageStreamCompleter;
Dan Field's avatar
Dan Field committed
280

281 282 283 284 285
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).keepAlive, true);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage2).untracked, true);
    final TestImageStreamCompleter resultingCompleter2 = imageCache!.putIfAbsent(testImage2, () {
286
      return completer2;
287
    })! as TestImageStreamCompleter;
Dan Field's avatar
Dan Field committed
288 289


290 291 292 293 294 295
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).keepAlive, false); // evicted
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage2).pending, false);
    expect(imageCache!.statusForKey(testImage2).keepAlive, true); // took the LRU spot.
    expect(imageCache!.statusForKey(testImage2).live, false); // no listeners
Dan Field's avatar
Dan Field committed
296

297 298 299
    expect(resultingCompleter1, completer1);
    expect(resultingCompleter2, completer2);
  });
Dan Field's avatar
Dan Field committed
300

301
  test('Live image cache avoids leaks of unlistened streams', () async {
302
    imageCache!.maximumSize = 3;
Dan Field's avatar
Dan Field committed
303

304 305 306 307 308 309
    TestImageProvider(1, 1, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(2, 2, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(3, 3, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(4, 4, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(5, 5, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(6, 6, image: await createTestImage()).resolve(ImageConfiguration.empty);
Dan Field's avatar
Dan Field committed
310

311 312
    // wait an event loop to let image resolution process.
    await null;
Dan Field's avatar
Dan Field committed
313

314 315
    expect(imageCache!.currentSize, 3);
    expect(imageCache!.liveImageCount, 0);
316
  });
Dan Field's avatar
Dan Field committed
317

318
  test('Disabled image cache does not leak live images', () async {
319
    imageCache!.maximumSize = 0;
Dan Field's avatar
Dan Field committed
320

321 322 323 324 325 326
    TestImageProvider(1, 1, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(2, 2, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(3, 3, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(4, 4, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(5, 5, image: await createTestImage()).resolve(ImageConfiguration.empty);
    TestImageProvider(6, 6, image: await createTestImage()).resolve(ImageConfiguration.empty);
Dan Field's avatar
Dan Field committed
327

328 329
    // wait an event loop to let image resolution process.
    await null;
Dan Field's avatar
Dan Field committed
330

331 332
    expect(imageCache!.currentSize, 0);
    expect(imageCache!.liveImageCount, 0);
333
  });
Dan Field's avatar
Dan Field committed
334

335
  test('Evicting a pending image clears the live image by default', () async {
336
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
Dan Field's avatar
Dan Field committed
337

338
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
Dan Field's avatar
Dan Field committed
339

340 341 342 343
    imageCache!.putIfAbsent(testImage, () => completer1);
    expect(imageCache!.statusForKey(testImage).pending, true);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
Dan Field's avatar
Dan Field committed
344

345 346
    imageCache!.evict(testImage);
    expect(imageCache!.statusForKey(testImage).untracked, true);
347
  });
Dan Field's avatar
Dan Field committed
348

349
  test('Evicting a pending image does clear the live image when includeLive is false and only cache listening', () async {
350
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
Dan Field's avatar
Dan Field committed
351

352
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
Dan Field's avatar
Dan Field committed
353

354 355 356 357
    imageCache!.putIfAbsent(testImage, () => completer1);
    expect(imageCache!.statusForKey(testImage).pending, true);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
Dan Field's avatar
Dan Field committed
358

359 360 361 362
    imageCache!.evict(testImage, includeLive: false);
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, false);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
363
  });
Dan Field's avatar
Dan Field committed
364

365
  test('Evicting a pending image does clear the live image when includeLive is false and some other listener', () async {
366
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
Dan Field's avatar
Dan Field committed
367

368
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
Dan Field's avatar
Dan Field committed
369

370 371 372 373
    imageCache!.putIfAbsent(testImage, () => completer1);
    expect(imageCache!.statusForKey(testImage).pending, true);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
Dan Field's avatar
Dan Field committed
374

375
    completer1.addListener(ImageStreamListener((_, __) {}));
376 377 378 379
    imageCache!.evict(testImage, includeLive: false);
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
380
  });
Dan Field's avatar
Dan Field committed
381

382
  test('Evicting a completed image does clear the live image by default', () async {
383
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
Dan Field's avatar
Dan Field committed
384

385 386 387
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
      ..testSetImage(testImage)
      ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
Dan Field's avatar
Dan Field committed
388

389 390 391 392
    imageCache!.putIfAbsent(testImage, () => completer1);
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, true);
Dan Field's avatar
Dan Field committed
393

394 395
    imageCache!.evict(testImage);
    expect(imageCache!.statusForKey(testImage).untracked, true);
396
  });
Dan Field's avatar
Dan Field committed
397

398
  test('Evicting a completed image does not clear the live image when includeLive is set to false', () async {
399
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
Dan Field's avatar
Dan Field committed
400

401 402 403
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
      ..testSetImage(testImage)
      ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
Dan Field's avatar
Dan Field committed
404

405 406 407 408
    imageCache!.putIfAbsent(testImage, () => completer1);
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, true);
409

410 411 412 413
    imageCache!.evict(testImage, includeLive: false);
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
414
  });
415

416
  test('Clearing liveImages removes callbacks', () async {
417
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
418

419
    final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
420

421 422 423
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
      ..testSetImage(testImage)
      ..addListener(listener);
424

425 426 427
    final TestImageStreamCompleter completer2 = TestImageStreamCompleter()
      ..testSetImage(testImage)
      ..addListener(listener);
428

429 430 431 432
    imageCache!.putIfAbsent(testImage, () => completer1);
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, true);
433

434 435 436 437 438
    imageCache!.clear();
    imageCache!.clearLiveImages();
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, false);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
439

440 441 442 443
    imageCache!.putIfAbsent(testImage, () => completer2);
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, true);
444

445
    completer1.removeListener(listener);
446

447 448 449
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, true);
450
  });
451

452 453 454 455 456 457 458
  test('Live image gets size updated', () async {
    // Add an image to the cache in pending state
    // Complete it once it is in there as live
    // Evict it but leave the live one.
    // Add it again.
    // If the live image did not track the size properly, the last line of
    // this test will fail.
459

460
    final ui.Image testImage = await createTestImage(width: 8, height: 8);
461
    const int testImageSize = 8 * 8 * 4;
462

463
    final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
464

465 466
    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
      ..addListener(listener);
467

468 469 470 471 472
    imageCache!.putIfAbsent(testImage, () => completer1);
    expect(imageCache!.statusForKey(testImage).pending, true);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
    expect(imageCache!.currentSizeBytes, 0);
473

474
    completer1.testSetImage(testImage);
475

476 477 478 479
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, true);
    expect(imageCache!.currentSizeBytes, testImageSize);
480

481
    imageCache!.evict(testImage, includeLive: false);
482

483 484 485 486
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, false);
    expect(imageCache!.currentSizeBytes, 0);
487

488
    imageCache!.putIfAbsent(testImage, () => completer1);
489

490 491 492 493
    expect(imageCache!.statusForKey(testImage).pending, false);
    expect(imageCache!.statusForKey(testImage).live, true);
    expect(imageCache!.statusForKey(testImage).keepAlive, true);
    expect(imageCache!.currentSizeBytes, testImageSize);
494
  });
495 496 497 498

  test('Image is obtained and disposed of properly for cache', () async {
    const int key = 1;
    final ui.Image testImage = await createTestImage(width: 8, height: 8, cache: false);
499
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 1);
500

501
    late ImageInfo imageInfo;
502 503 504 505 506 507 508
    final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {
      imageInfo = info;
    });

    final TestImageStreamCompleter completer = TestImageStreamCompleter();

    completer.addListener(listener);
509
    imageCache!.putIfAbsent(key, () => completer);
510

511
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 1);
512 513 514 515 516

    // This should cause keepAlive to be set to true.
    completer.testSetImage(testImage);
    expect(imageInfo, isNotNull);
    // +1 ImageStreamCompleter
517
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 2);
518 519 520 521

    completer.removeListener(listener);

    // Force us to the end of the frame.
522 523
    SchedulerBinding.instance!.scheduleFrame();
    await SchedulerBinding.instance!.endOfFrame;
524

525
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 2);
526

527
    expect(imageCache!.evict(key), true);
528 529

    // Force us to the end of the frame.
530 531
    SchedulerBinding.instance!.scheduleFrame();
    await SchedulerBinding.instance!.endOfFrame;
532 533 534

    // -1 _CachedImage
    // -1 ImageStreamCompleter
535
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 1);
536 537

    imageInfo.dispose();
538
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 0);
539 540 541 542 543
  }, skip: kIsWeb); // Web does not care about image handles.

  test('Image is obtained and disposed of properly for cache when listener is still active', () async {
    const int key = 1;
    final ui.Image testImage = await createTestImage(width: 8, height: 8, cache: false);
544
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 1);
545

546
    late ImageInfo imageInfo;
547 548 549 550 551 552 553
    final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {
      imageInfo = info;
    });

    final TestImageStreamCompleter completer = TestImageStreamCompleter();

    completer.addListener(listener);
554
    imageCache!.putIfAbsent(key, () => completer);
555

556
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 1);
557 558 559 560 561

    // This should cause keepAlive to be set to true.
    completer.testSetImage(testImage);
    expect(imageInfo, isNotNull);
    // Just our imageInfo and the completer.
562
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 2);
563

564
    expect(imageCache!.evict(key), true);
565 566

    // Force us to the end of the frame.
567 568
    SchedulerBinding.instance!.scheduleFrame();
    await SchedulerBinding.instance!.endOfFrame;
569 570 571

    // Live image still around since there's still a listener, and the listener
    // should be holding a handle.
572
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 2);
573 574
    completer.removeListener(listener);

575
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 1);
576
    imageInfo.dispose();
577
    expect(testImage.debugGetOpenHandleStackTraces()!.length, 0);
578
  }, skip: kIsWeb); // Web does not care about open image handles.
579
}