mirror of
https://github.com/flutter/flutter
synced 2024-09-29 21:14:10 +00:00
Remove android stack_size_test (#153695)
This test expects to run on 32-bit hardware, but the devicelab no longer has 32-bit Android hardware. In particular, the test contains hand-coded 32-bit arm assembly for reading the stack pointer from Dart code via `dart:ffi`. This test was added in response to a framework change that caused stack frames to be larger than expected while building widgets, leading apps to crash with StackOverflow exceptions unexpectedly. Since then (>3 years ago) this test has not prevented any similar issue, so I believe deleting it rather than fixing it is a better use of resources.
This commit is contained in:
parent
3a6053208b
commit
88123277fc
11
.ci.yaml
11
.ci.yaml
|
@ -2116,17 +2116,6 @@ targets:
|
||||||
["devicelab", "android", "linux", "pixel", "7pro"]
|
["devicelab", "android", "linux", "pixel", "7pro"]
|
||||||
task_name: android_semantics_integration_test
|
task_name: android_semantics_integration_test
|
||||||
|
|
||||||
# linux mokey test
|
|
||||||
- name: Linux_mokey android_stack_size_test
|
|
||||||
recipe: devicelab/devicelab_drone
|
|
||||||
presubmit: false
|
|
||||||
bringup: true # https://github.com/flutter/flutter/issues/148085
|
|
||||||
timeout: 60
|
|
||||||
properties:
|
|
||||||
tags: >
|
|
||||||
["devicelab", "android", "linux", "mokey"]
|
|
||||||
task_name: android_stack_size_test
|
|
||||||
|
|
||||||
# linux mokey benchmark
|
# linux mokey benchmark
|
||||||
- name: Linux_mokey android_view_scroll_perf__timeline_summary
|
- name: Linux_mokey android_view_scroll_perf__timeline_summary
|
||||||
recipe: devicelab/devicelab_drone
|
recipe: devicelab/devicelab_drone
|
||||||
|
|
|
@ -27,7 +27,6 @@ const String kHeavyGridViewRouteName = '/heavy_gridview';
|
||||||
const String kRasterCacheUseMemory = '/raster_cache_use_memory';
|
const String kRasterCacheUseMemory = '/raster_cache_use_memory';
|
||||||
const String kShaderMaskCacheRouteName = '/shader_mask_cache';
|
const String kShaderMaskCacheRouteName = '/shader_mask_cache';
|
||||||
const String kSimpleScrollRouteName = '/simple_scroll';
|
const String kSimpleScrollRouteName = '/simple_scroll';
|
||||||
const String kStackSizeRouteName = '/stack_size';
|
|
||||||
const String kAnimationWithMicrotasksRouteName = '/animation_with_microtasks';
|
const String kAnimationWithMicrotasksRouteName = '/animation_with_microtasks';
|
||||||
const String kAnimatedImageRouteName = '/animated_image';
|
const String kAnimatedImageRouteName = '/animated_image';
|
||||||
const String kOpacityPeepholeRouteName = '/opacity_peephole';
|
const String kOpacityPeepholeRouteName = '/opacity_peephole';
|
||||||
|
@ -62,5 +61,3 @@ const String kGradientPerfStaticConsistentRouteName = '$kGradientPerfRouteName/s
|
||||||
const String kScrollableName = '/macrobenchmark_listview';
|
const String kScrollableName = '/macrobenchmark_listview';
|
||||||
const String kOpacityScrollableName = '$kOpacityPeepholeRouteName/listview';
|
const String kOpacityScrollableName = '$kOpacityPeepholeRouteName/listview';
|
||||||
const String kGradientPerfScrollableName = '$kGradientPerfRouteName/listview';
|
const String kGradientPerfScrollableName = '$kGradientPerfRouteName/listview';
|
||||||
|
|
||||||
const String kStackSizeKey = 'stack_size';
|
|
||||||
|
|
|
@ -41,7 +41,6 @@ import 'src/shader_mask_cache.dart';
|
||||||
import 'src/simple_animation.dart';
|
import 'src/simple_animation.dart';
|
||||||
import 'src/simple_scroll.dart';
|
import 'src/simple_scroll.dart';
|
||||||
import 'src/sliders.dart';
|
import 'src/sliders.dart';
|
||||||
import 'src/stack_size.dart';
|
|
||||||
import 'src/text.dart';
|
import 'src/text.dart';
|
||||||
import 'src/very_long_picture_scrolling.dart';
|
import 'src/very_long_picture_scrolling.dart';
|
||||||
|
|
||||||
|
@ -83,7 +82,6 @@ class MacrobenchmarksApp extends StatelessWidget {
|
||||||
kRasterCacheUseMemory: (BuildContext context) => const RasterCacheUseMemory(),
|
kRasterCacheUseMemory: (BuildContext context) => const RasterCacheUseMemory(),
|
||||||
kShaderMaskCacheRouteName: (BuildContext context) => const ShaderMaskCachePage(),
|
kShaderMaskCacheRouteName: (BuildContext context) => const ShaderMaskCachePage(),
|
||||||
kSimpleScrollRouteName: (BuildContext context) => const SimpleScroll(),
|
kSimpleScrollRouteName: (BuildContext context) => const SimpleScroll(),
|
||||||
kStackSizeRouteName: (BuildContext context) => const StackSizePage(),
|
|
||||||
kAnimationWithMicrotasksRouteName: (BuildContext context) => const AnimationWithMicrotasks(),
|
kAnimationWithMicrotasksRouteName: (BuildContext context) => const AnimationWithMicrotasks(),
|
||||||
kAnimatedImageRouteName: (BuildContext context) => const AnimatedImagePage(),
|
kAnimatedImageRouteName: (BuildContext context) => const AnimatedImagePage(),
|
||||||
kOpacityPeepholeRouteName: (BuildContext context) => const OpacityPeepholePage(),
|
kOpacityPeepholeRouteName: (BuildContext context) => const OpacityPeepholePage(),
|
||||||
|
@ -279,13 +277,6 @@ class HomePage extends StatelessWidget {
|
||||||
Navigator.pushNamed(context, kLargeImageChangerRouteName);
|
Navigator.pushNamed(context, kLargeImageChangerRouteName);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
|
||||||
key: const Key(kStackSizeRouteName),
|
|
||||||
child: const Text('Stack Size'),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pushNamed(context, kStackSizeRouteName);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
key: const Key(kAnimationWithMicrotasksRouteName),
|
key: const Key(kAnimationWithMicrotasksRouteName),
|
||||||
child: const Text('Animation With Microtasks'),
|
child: const Text('Animation With Microtasks'),
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
// 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 'dart:ffi' as ffi;
|
|
||||||
import 'dart:io' as io;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../common.dart';
|
|
||||||
|
|
||||||
typedef GetStackPointerCallback = int Function();
|
|
||||||
|
|
||||||
// c interop function:
|
|
||||||
// void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);
|
|
||||||
typedef CMmap = ffi.Pointer<ffi.Void> Function(
|
|
||||||
ffi.Pointer<ffi.Void>, ffi.IntPtr, ffi.Int32, ffi.Int32, ffi.Int32, ffi.IntPtr);
|
|
||||||
typedef DartMmap = ffi.Pointer<ffi.Void> Function(
|
|
||||||
ffi.Pointer<ffi.Void>, int, int, int, int, int);
|
|
||||||
final DartMmap mmap = ffi.DynamicLibrary.process().lookupFunction<CMmap, DartMmap>('mmap');
|
|
||||||
|
|
||||||
// c interop function:
|
|
||||||
// int mprotect(void* addr, size_t len, int prot);
|
|
||||||
typedef CMprotect = ffi.Int32 Function(ffi.Pointer<ffi.Void>, ffi.IntPtr, ffi.Int32);
|
|
||||||
typedef DartMprotect = int Function(ffi.Pointer<ffi.Void>, int, int);
|
|
||||||
final DartMprotect mprotect = ffi.DynamicLibrary.process()
|
|
||||||
.lookupFunction<CMprotect, DartMprotect>('mprotect');
|
|
||||||
|
|
||||||
const int kProtRead = 1;
|
|
||||||
const int kProtWrite = 2;
|
|
||||||
const int kProtExec = 4;
|
|
||||||
|
|
||||||
const int kMapPrivate = 0x02;
|
|
||||||
const int kMapJit = 0x0;
|
|
||||||
const int kMapAnon = 0x20;
|
|
||||||
|
|
||||||
const int kMemorySize = 16;
|
|
||||||
const int kInvalidFileDescriptor = -1;
|
|
||||||
const int kkFileMappingOffset = 0;
|
|
||||||
|
|
||||||
const int kMemoryStartingIndex = 0;
|
|
||||||
|
|
||||||
const int kExitCodeSuccess = 0;
|
|
||||||
|
|
||||||
final GetStackPointerCallback getStackPointer = () {
|
|
||||||
// Makes sure we are running on an Android arm64 device.
|
|
||||||
if (!io.Platform.isAndroid) {
|
|
||||||
throw 'This benchmark test can only be run on Android arm devices.';
|
|
||||||
}
|
|
||||||
final io.ProcessResult result = io.Process.runSync('getprop', <String>['ro.product.cpu.abi']);
|
|
||||||
if (result.exitCode != 0) {
|
|
||||||
throw 'Failed to retrieve CPU information.';
|
|
||||||
}
|
|
||||||
if (!result.stdout.toString().contains('armeabi')) {
|
|
||||||
throw 'This benchmark test can only be run on Android arm devices.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a block of memory to store the assembly code.
|
|
||||||
final ffi.Pointer<ffi.Void> region = mmap(ffi.nullptr, kMemorySize, kProtRead | kProtWrite,
|
|
||||||
kMapPrivate | kMapAnon | kMapJit, kInvalidFileDescriptor, kkFileMappingOffset);
|
|
||||||
if (region == ffi.nullptr) {
|
|
||||||
throw 'Failed to acquire memory for the test.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes the assembly code into the memory block. This assembly code returns
|
|
||||||
// the memory address of the stack pointer.
|
|
||||||
region.cast<ffi.Uint8>().asTypedList(kMemorySize).setAll(
|
|
||||||
kMemoryStartingIndex,
|
|
||||||
<int>[
|
|
||||||
// "mov r0, sp" in machine code: 0D00A0E1.
|
|
||||||
0x0d, 0x00, 0xa0, 0xe1,
|
|
||||||
// "bx lr" in machine code: 1EFF2FE1.
|
|
||||||
0x1e, 0xff, 0x2f, 0xe1,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Makes sure the memory block is executable.
|
|
||||||
if (mprotect(region, kMemorySize, kProtRead | kProtExec) != kExitCodeSuccess) {
|
|
||||||
throw 'Failed to write executable code to the memory.';
|
|
||||||
}
|
|
||||||
return region
|
|
||||||
.cast<ffi.NativeFunction<ffi.IntPtr Function()>>()
|
|
||||||
.asFunction<int Function()>();
|
|
||||||
}();
|
|
||||||
|
|
||||||
class StackSizePage extends StatelessWidget {
|
|
||||||
const StackSizePage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const Material(
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
SizedBox(
|
|
||||||
width: 200,
|
|
||||||
height: 100,
|
|
||||||
child: ParentWidget(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ParentWidget extends StatelessWidget {
|
|
||||||
const ParentWidget({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final int myStackSize = getStackPointer();
|
|
||||||
return ChildWidget(parentStackSize: myStackSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChildWidget extends StatelessWidget {
|
|
||||||
const ChildWidget({required this.parentStackSize, super.key});
|
|
||||||
final int parentStackSize;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final int myStackSize = getStackPointer();
|
|
||||||
// Captures the stack size difference between parent widget and child widget
|
|
||||||
// during the rendering pipeline, i.e. one layer of stateless widget.
|
|
||||||
return Text(
|
|
||||||
'${parentStackSize - myStackSize}',
|
|
||||||
key: const ValueKey<String>(kStackSizeKey),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
// 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 'dart:convert' show JsonEncoder;
|
|
||||||
|
|
||||||
import 'package:file/file.dart';
|
|
||||||
import 'package:flutter_driver/flutter_driver.dart';
|
|
||||||
import 'package:macrobenchmarks/common.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
|
||||||
|
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
const JsonEncoder _prettyEncoder = JsonEncoder.withIndent(' ');
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('stack_size', () async {
|
|
||||||
late int stackSizeInBytes;
|
|
||||||
await runDriverTestForRoute(kStackSizeRouteName, (FlutterDriver driver) async {
|
|
||||||
final String stackSize = await driver.getText(find.byValueKey(kStackSizeKey));
|
|
||||||
expect(stackSize.isNotEmpty, isTrue);
|
|
||||||
stackSizeInBytes = int.parse(stackSize);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(stackSizeInBytes > 0, isTrue);
|
|
||||||
|
|
||||||
await fs.directory(testOutputsDirectory).create(recursive: true);
|
|
||||||
final File file = fs.file(path.join(testOutputsDirectory, 'stack_size.json'));
|
|
||||||
await file.writeAsString(_encodeJson(<String, dynamic>{
|
|
||||||
'stack_size': stackSizeInBytes,
|
|
||||||
}));
|
|
||||||
}, timeout: Timeout.none);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _encodeJson(Map<String, dynamic> jsonObject) {
|
|
||||||
return _prettyEncoder.convert(jsonObject);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
// 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_devicelab/framework/devices.dart';
|
|
||||||
import 'package:flutter_devicelab/framework/framework.dart';
|
|
||||||
import 'package:flutter_devicelab/tasks/perf_tests.dart';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
|
||||||
deviceOperatingSystem = DeviceOperatingSystem.androidArm;
|
|
||||||
await task(createStackSizeTest());
|
|
||||||
}
|
|
|
@ -353,43 +353,6 @@ TaskFunction createSlidersPerfTest() {
|
||||||
).run;
|
).run;
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskFunction createStackSizeTest() {
|
|
||||||
final String testDirectory =
|
|
||||||
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks';
|
|
||||||
const String testTarget = 'test_driver/run_app.dart';
|
|
||||||
const String testDriver = 'test_driver/stack_size_perf_test.dart';
|
|
||||||
return () {
|
|
||||||
return inDirectory<TaskResult>(testDirectory, () async {
|
|
||||||
final Device device = await devices.workingDevice;
|
|
||||||
await device.unlock();
|
|
||||||
final String deviceId = device.deviceId;
|
|
||||||
await flutter('packages', options: <String>['get']);
|
|
||||||
|
|
||||||
await flutter('drive', options: <String>[
|
|
||||||
'--no-android-gradle-daemon',
|
|
||||||
'-v',
|
|
||||||
'--verbose-system-logs',
|
|
||||||
'--profile',
|
|
||||||
'-t', testTarget,
|
|
||||||
'--driver', testDriver,
|
|
||||||
'-d',
|
|
||||||
deviceId,
|
|
||||||
]);
|
|
||||||
final Map<String, dynamic> data = json.decode(
|
|
||||||
file('${_testOutputDirectory(testDirectory)}/stack_size.json').readAsStringSync(),
|
|
||||||
) as Map<String, dynamic>;
|
|
||||||
|
|
||||||
final Map<String, dynamic> result = <String, dynamic>{
|
|
||||||
'stack_size_per_nesting_level': data['stack_size'],
|
|
||||||
};
|
|
||||||
return TaskResult.success(
|
|
||||||
result,
|
|
||||||
benchmarkScoreKeys: result.keys.toList(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskFunction createFullscreenTextfieldPerfTest() {
|
TaskFunction createFullscreenTextfieldPerfTest() {
|
||||||
return PerfTest(
|
return PerfTest(
|
||||||
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
|
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
|
||||||
|
|
Loading…
Reference in a new issue