blob: 45b3bbd66c8a9d7882f1b9c3b6f42946e4008867 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
/// Whether the current browser is Safari on iOS.
// TODO: https://github.com/flutter/flutter/issues/60040
bool get isIosSafari => browserEngine == BrowserEngine.webkit &&
operatingSystem == OperatingSystem.iOs;
/// Whether the current browser is Firefox.
bool get isFirefox => browserEngine == BrowserEngine.firefox;
/// Used in tests instead of [ProductionCollector] to control Skia object
/// collection explicitly, and to prevent leaks across tests.
///
/// See [TestCollector] for usage.
late TestCollector testCollector;
/// Common test setup for all CanvasKit unit-tests.
void setUpCanvasKitTest() {
setUpAll(() async {
expect(useCanvasKit, true,
reason: 'This test must run in CanvasKit mode.');
debugResetBrowserSupportsFinalizationRegistry();
await ui.webOnlyInitializePlatform(assetManager: WebOnlyMockAssetManager());
});
setUp(() async {
testCollector = TestCollector();
Collector.debugOverrideCollector(testCollector);
});
tearDown(() {
testCollector.cleanUpAfterTest();
debugResetBrowserSupportsFinalizationRegistry();
OverlayCache.instance.debugClear();
});
tearDownAll(() {
debugResetBrowserSupportsFinalizationRegistry();
});
}
/// Utility function for CanvasKit tests to draw pictures without
/// the [CkPictureRecorder] boilerplate.
CkPicture paintPicture(
ui.Rect cullRect, void Function(CkCanvas canvas) painter) {
final CkPictureRecorder recorder = CkPictureRecorder();
final CkCanvas canvas = recorder.beginRecording(cullRect);
painter(canvas);
return recorder.endRecording();
}
class _TestFinalizerRegistration {
_TestFinalizerRegistration(this.wrapper, this.deletable, this.stackTrace);
final Object wrapper;
final SkDeletable deletable;
final StackTrace stackTrace;
}
class _TestCollection {
_TestCollection(this.deletable, this.stackTrace);
final SkDeletable deletable;
final StackTrace stackTrace;
}
/// Provides explicit synchronous API for collecting Skia objects in tests.
///
/// [ProductionCollector] relies on `FinalizationRegistry` and timers to
/// delete Skia objects, which makes it more precise and efficient. However,
/// it also makes it unpredictable. For example, an object created in one
/// test may be collected while running another test because the timing is
/// subject to browser-specific GC scheduling.
///
/// Tests should use [collectNow] and [collectAfterTest] to trigger collections.
class TestCollector implements Collector {
final List<_TestFinalizerRegistration> _activeRegistrations = <_TestFinalizerRegistration>[];
final List<_TestFinalizerRegistration> _collectedRegistrations = <_TestFinalizerRegistration>[];
final List<_TestCollection> _pendingCollections = <_TestCollection>[];
final List<_TestCollection> _completedCollections = <_TestCollection>[];
@override
void register(Object wrapper, SkDeletable deletable) {
_activeRegistrations.add(
_TestFinalizerRegistration(wrapper, deletable, StackTrace.current),
);
}
@override
void collect(SkDeletable deletable) {
_pendingCollections.add(
_TestCollection(deletable, StackTrace.current),
);
}
/// Deletes all Skia objects scheduled for collection.
void collectNow() {
for (_TestCollection collection in _pendingCollections) {
late final _TestFinalizerRegistration? activeRegistration;
for (_TestFinalizerRegistration registration in _activeRegistrations) {
if (identical(registration.deletable, collection.deletable)) {
activeRegistration = registration;
break;
}
}
if (activeRegistration == null) {
late final _TestFinalizerRegistration? collectedRegistration;
for (_TestFinalizerRegistration registration in _collectedRegistrations) {
if (identical(registration.deletable, collection.deletable)) {
collectedRegistration = registration;
break;
}
}
if (collectedRegistration == null) {
fail(
'Attempted to collect an object that was never registered for finalization.\n'
'The collection was requested here:\n'
'${collection.stackTrace}'
);
} else {
final _TestCollection firstCollection = _completedCollections.firstWhere(
(_TestCollection completedCollection) {
return identical(completedCollection.deletable, collection.deletable);
}
);
fail(
'Attempted to collect an object that was previously collected.\n'
'The object was registered for finalization here:\n'
'${collection.stackTrace}\n\n'
'The first collection was requested here:\n'
'${firstCollection.stackTrace}\n\n'
'The second collection was requested here:\n'
'${collection.stackTrace}'
);
}
} else {
_collectedRegistrations.add(activeRegistration);
_activeRegistrations.remove(activeRegistration);
_completedCollections.add(collection);
if (!collection.deletable.isDeleted()) {
collection.deletable.delete();
}
}
}
_pendingCollections.clear();
}
/// Deletes all Skia objects with registered finalizers.
///
/// This also deletes active objects that have not been scheduled for
/// collection, to prevent objects leaking across tests.
void cleanUpAfterTest() {
for (_TestCollection collection in _pendingCollections) {
if (!collection.deletable.isDeleted()) {
collection.deletable.delete();
}
}
for (_TestFinalizerRegistration registration in _activeRegistrations) {
if (!registration.deletable.isDeleted()) {
registration.deletable.delete();
}
}
_activeRegistrations.clear();
_collectedRegistrations.clear();
_pendingCollections.clear();
_completedCollections.clear();
}
}