Skip to content

Commit

Permalink
Reland "integrate testWidgets with leak tracking" (flutter#140521) (f…
Browse files Browse the repository at this point in the history
  • Loading branch information
polina-c authored and victoreronmosele committed Jan 6, 2024
1 parent dfc0f38 commit a557940
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 14 deletions.
29 changes: 15 additions & 14 deletions packages/flutter/test/flutter_test_config.dart
Expand Up @@ -34,23 +34,24 @@ Future<void> testExecutable(FutureOr<void> Function() testMain) {
// receive the event.
WidgetController.hitTestWarningShouldBeFatal = true;

LeakTracking.warnForUnsupportedPlatforms = false;

LeakTesting.settings = LeakTesting
.settings
// TODO(polina-c): clean up leaks and stop ignoring them.
// https://github.com/flutter/flutter/issues/137311
.withIgnored(
allNotGCed: true,
notDisposed: <String, int?>{
'OverlayEntry': null,
},
);

// Leak tracking is off by default.
// To enable it, follow doc for [_kLeakTracking].
if (_kLeakTracking) {
LeakTesting.settings = LeakTesting.settings.withTrackedAll();
LeakTesting.enable();

LeakTracking.warnForUnsupportedPlatforms = false;

LeakTesting.settings = LeakTesting
.settings
.withTrackedAll()
// TODO(polina-c): clean up leaks and stop ignoring them.
// https://github.com/flutter/flutter/issues/137311
.withIgnored(
allNotGCed: true,
notDisposed: <String, int?>{
'OverlayEntry': null,
},
);
}

// Enable golden file testing using Skia Gold.
Expand Down
30 changes: 30 additions & 0 deletions packages/flutter_test/lib/src/test_compat.dart
Expand Up @@ -4,6 +4,7 @@

import 'dart:async';

import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import 'package:meta/meta.dart';
import 'package:test_api/scaffolding.dart' show Timeout;
import 'package:test_api/src/backend/declarer.dart'; // ignore: implementation_imports
Expand Down Expand Up @@ -163,6 +164,7 @@ void test(
Map<String, dynamic>? onPlatform,
int? retry,
}) {
_maybeConfigureTearDownForTestFile();
_declarer.test(
description.toString(),
body,
Expand All @@ -186,6 +188,7 @@ void test(
/// of running the group's tests.
@isTestGroup
void group(Object description, void Function() body, { dynamic skip, int? retry }) {
_maybeConfigureTearDownForTestFile();
_declarer.group(description.toString(), body, skip: skip, retry: retry);
}

Expand All @@ -201,6 +204,7 @@ void group(Object description, void Function() body, { dynamic skip, int? retry
/// Each callback at the top level or in a given group will be run in the order
/// they were declared.
void setUp(dynamic Function() body) {
_maybeConfigureTearDownForTestFile();
_declarer.setUp(body);
}

Expand All @@ -218,6 +222,7 @@ void setUp(dynamic Function() body) {
///
/// See also [addTearDown], which adds tear-downs to a running test.
void tearDown(dynamic Function() body) {
_maybeConfigureTearDownForTestFile();
_declarer.tearDown(body);
}

Expand All @@ -235,6 +240,7 @@ void tearDown(dynamic Function() body) {
/// prefer [setUp], and only use [setUpAll] if the callback is prohibitively
/// slow.
void setUpAll(dynamic Function() body) {
_maybeConfigureTearDownForTestFile();
_declarer.setUpAll(body);
}

Expand All @@ -250,9 +256,33 @@ void setUpAll(dynamic Function() body) {
/// prefer [tearDown], and only use [tearDownAll] if the callback is
/// prohibitively slow.
void tearDownAll(dynamic Function() body) {
_maybeConfigureTearDownForTestFile();
_declarer.tearDownAll(body);
}

bool _isTearDownForTestFileConfigured = false;

/// If needed, configures `tearDownAll` after all user defined `tearDownAll` in the test file.
///
/// This function should be invoked in all functions, that may be invoked by user in the test file,
/// to be invoked before any other `tearDownAll`.
void _maybeConfigureTearDownForTestFile() {
if (_isTearDownForTestFileConfigured || !_shouldConfigureTearDownForTestFile()) {
return;
}
_declarer.tearDownAll(_tearDownForTestFile);
_isTearDownForTestFileConfigured = true;
}

/// Returns true if tear down for the test file needs to be configured.
bool _shouldConfigureTearDownForTestFile() {
return LeakTesting.enabled;
}

/// Tear down that should happen after all user defined tear down.
Future<void> _tearDownForTestFile() async {
await maybeTearDownLeakTrackingForAll();
}

/// A reporter that prints each test on its own line.
///
Expand Down
16 changes: 16 additions & 0 deletions packages/flutter_test/lib/src/widget_tester.dart
Expand Up @@ -9,6 +9,7 @@ import 'package:flutter/material.dart' show Tooltip;
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import 'package:matcher/expect.dart' as matcher_expect;
import 'package:meta/meta.dart';
import 'package:test_api/scaffolding.dart' as test_package;
Expand Down Expand Up @@ -116,6 +117,18 @@ E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) {
/// If the [tags] are passed, they declare user-defined tags that are implemented by
/// the `test` package.
///
/// The argument [experimentalLeakTesting] is experimental and is not recommended
/// for use outside of the Flutter framework.
/// When [experimentalLeakTesting] is set, it is used to leak track objects created
/// during test execution.
/// Otherwise [LeakTesting.settings] is used.
/// Adjust [LeakTesting.settings] in flutter_test_config.dart
/// (see https://github.com/flutter/flutter/blob/master/packages/flutter_test/lib/flutter_test.dart)
/// for the entire package or folder, or in the test's main for a test file
/// (don't use [setUp] or [setUpAll]).
/// To turn off leak tracking just for one test, set [experimentalLeakTesting] to
/// `LeakTrackingForTests.ignore()`.
///
/// ## Sample code
///
/// ```dart
Expand All @@ -135,6 +148,7 @@ void testWidgets(
TestVariant<Object?> variant = const DefaultTestVariant(),
dynamic tags,
int? retry,
LeakTesting? experimentalLeakTesting,
}) {
assert(variant.values.isNotEmpty, 'There must be at least one value to test in the testing variant.');
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
Expand Down Expand Up @@ -165,9 +179,11 @@ void testWidgets(
Object? memento;
try {
memento = await variant.setUp(value);
maybeSetupLeakTrackingForTest(experimentalLeakTesting, combinedDescription);
await callback(tester);
} finally {
await variant.tearDown(value, memento);
maybeTearDownLeakTrackingForTest();
}
semanticsHandle?.dispose();
},
Expand Down
54 changes: 54 additions & 0 deletions packages/flutter_test/test/utils/leaking_classes.dart
@@ -0,0 +1,54 @@
// Copyright 2014 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:flutter/widgets.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';

class LeakTrackedClass {
LeakTrackedClass() {
LeakTracking.dispatchObjectCreated(
library: library,
className: '$LeakTrackedClass',
object: this,
);
}

static const String library = 'package:my_package/lib/src/my_lib.dart';

void dispose() {
LeakTracking.dispatchObjectDisposed(object: this);
}
}

final List<LeakTrackedClass> _notGCedObjects = <LeakTrackedClass>[];

class LeakingClass {
LeakingClass() {
_notGCedObjects.add(LeakTrackedClass()..dispose());
}
}

class StatelessLeakingWidget extends StatelessWidget {
StatelessLeakingWidget({
super.key,
this.notGCed = true,
this.notDisposed = true,
}) {
if (notGCed) {
_notGCedObjects.add(LeakTrackedClass()..dispose());
}
if (notDisposed) {
// ignore: unused_local_variable, it is unused intentionally, to illustrate not disposed object.
final LeakTrackedClass notDisposedObject = LeakTrackedClass();
}
}

final bool notGCed;
final bool notDisposed;

@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

0 comments on commit a557940

Please sign in to comment.