Adding first semantics perf test (#10649)
* Adding first semantics perf test * review commnts and analyzer fixes * fix analyzer warning
This commit is contained in:
parent
0774c5194b
commit
8bf17192f6
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2017 The Chromium 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_driver/driver_extension.dart';
|
||||||
|
import 'package:complex_layout/main.dart' as app;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
enableFlutterDriverExtension();
|
||||||
|
app.main();
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2017 The Chromium 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:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter_driver/flutter_driver.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('semantics performance test', () {
|
||||||
|
FlutterDriver driver;
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
driver = await FlutterDriver.connect(printCommunication: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
if (driver != null)
|
||||||
|
driver.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('inital tree creation', () async {
|
||||||
|
// Let app become fully idle.
|
||||||
|
await new Future<Null>.delayed(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
final Timeline timeline = await driver.traceAction(() async {
|
||||||
|
expect(await driver.setSemantics(true), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
final Iterable<TimelineEvent> semanticsEvents = timeline.events.where((TimelineEvent event) => event.name == "Semantics");
|
||||||
|
if (semanticsEvents.length != 1)
|
||||||
|
fail('Expected exactly one semantics event, got ${semanticsEvents.length}');
|
||||||
|
final Duration semanticsTreeCreation = semanticsEvents.first.duration;
|
||||||
|
|
||||||
|
final String json = JSON.encode(<String, dynamic>{'initialSemanticsTreeCreation': semanticsTreeCreation.inMilliseconds});
|
||||||
|
new File(p.join(testOutputsDirectory, 'complex_layout_semantics_perf.json')).writeAsStringSync(json);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
38
dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart
Normal file
38
dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2017 The Chromium 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/adb.dart';
|
||||||
|
import 'package:flutter_devicelab/framework/framework.dart';
|
||||||
|
import 'package:flutter_devicelab/framework/utils.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
task(() async {
|
||||||
|
deviceOperatingSystem = DeviceOperatingSystem.android;
|
||||||
|
|
||||||
|
final Device device = await devices.workingDevice;
|
||||||
|
await device.unlock();
|
||||||
|
final String deviceId = device.deviceId;
|
||||||
|
await flutter('packages', options: <String>['get']);
|
||||||
|
|
||||||
|
final String complexLayoutPath = p.join(flutterDirectory.path, 'dev', 'benchmarks', 'complex_layout');
|
||||||
|
|
||||||
|
await inDirectory(complexLayoutPath, () async {
|
||||||
|
await flutter('drive', options: <String>[
|
||||||
|
'-v',
|
||||||
|
'--profile',
|
||||||
|
'--trace-startup', // Enables "endless" timeline event buffering.
|
||||||
|
'-t',
|
||||||
|
p.join(complexLayoutPath, 'test_driver', 'semantics_perf.dart'),
|
||||||
|
'-d',
|
||||||
|
deviceId,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
final String dataPath = p.join(complexLayoutPath, 'build', 'complex_layout_semantics_perf.json');
|
||||||
|
return new TaskResult.successFromFile(file(dataPath), benchmarkScoreKeys: <String>[
|
||||||
|
'initialSemanticsTreeCreation',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
@ -133,6 +133,13 @@ tasks:
|
|||||||
required_agent_capabilities: ["linux/android"]
|
required_agent_capabilities: ["linux/android"]
|
||||||
flaky: true
|
flaky: true
|
||||||
|
|
||||||
|
complex_layout_semantics_perf:
|
||||||
|
description: >
|
||||||
|
Measures duration of building the initial semantics tree.
|
||||||
|
stage: devicelab
|
||||||
|
required_agent_capabilities: ["linux/android"]
|
||||||
|
flaky: true
|
||||||
|
|
||||||
# iOS on-device tests
|
# iOS on-device tests
|
||||||
|
|
||||||
channels_integration_test_ios:
|
channels_integration_test_ios:
|
||||||
|
@ -21,6 +21,7 @@ import 'gesture.dart';
|
|||||||
import 'health.dart';
|
import 'health.dart';
|
||||||
import 'message.dart';
|
import 'message.dart';
|
||||||
import 'render_tree.dart';
|
import 'render_tree.dart';
|
||||||
|
import 'semantics.dart';
|
||||||
import 'timeline.dart';
|
import 'timeline.dart';
|
||||||
|
|
||||||
/// Timeline stream identifier.
|
/// Timeline stream identifier.
|
||||||
@ -383,6 +384,15 @@ class FlutterDriver {
|
|||||||
return GetTextResult.fromJson(await _sendCommand(new GetText(finder, timeout: timeout))).text;
|
return GetTextResult.fromJson(await _sendCommand(new GetText(finder, timeout: timeout))).text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns semantics on or off in the Flutter app under test.
|
||||||
|
///
|
||||||
|
/// Returns `true` when the call actually changed the state from on to off or
|
||||||
|
/// vice versa.
|
||||||
|
Future<bool> setSemantics(bool enabled, { Duration timeout: _kShortTimeout }) async {
|
||||||
|
final SetSemanticsResult result = SetSemanticsResult.fromJson(await _sendCommand(new SetSemantics(enabled, timeout: timeout)));
|
||||||
|
return result.changedState;
|
||||||
|
}
|
||||||
|
|
||||||
/// Take a screenshot. The image will be returned as a PNG.
|
/// Take a screenshot. The image will be returned as a PNG.
|
||||||
Future<List<int>> screenshot({ Duration timeout }) async {
|
Future<List<int>> screenshot({ Duration timeout }) async {
|
||||||
timeout ??= _kLongTimeout;
|
timeout ??= _kLongTimeout;
|
||||||
|
@ -8,7 +8,7 @@ import 'package:meta/meta.dart';
|
|||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart' show RendererBinding;
|
import 'package:flutter/rendering.dart' show RendererBinding, SemanticsHandle;
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@ -20,6 +20,7 @@ import 'gesture.dart';
|
|||||||
import 'health.dart';
|
import 'health.dart';
|
||||||
import 'message.dart';
|
import 'message.dart';
|
||||||
import 'render_tree.dart';
|
import 'render_tree.dart';
|
||||||
|
import 'semantics.dart';
|
||||||
|
|
||||||
const String _extensionMethodName = 'driver';
|
const String _extensionMethodName = 'driver';
|
||||||
const String _extensionMethod = 'ext.flutter.$_extensionMethodName';
|
const String _extensionMethod = 'ext.flutter.$_extensionMethodName';
|
||||||
@ -70,6 +71,7 @@ class FlutterDriverExtension {
|
|||||||
'tap': _tap,
|
'tap': _tap,
|
||||||
'get_text': _getText,
|
'get_text': _getText,
|
||||||
'set_frame_sync': _setFrameSync,
|
'set_frame_sync': _setFrameSync,
|
||||||
|
'set_semantics': _setSemantics,
|
||||||
'scroll': _scroll,
|
'scroll': _scroll,
|
||||||
'scrollIntoView': _scrollIntoView,
|
'scrollIntoView': _scrollIntoView,
|
||||||
'waitFor': _waitFor,
|
'waitFor': _waitFor,
|
||||||
@ -82,6 +84,7 @@ class FlutterDriverExtension {
|
|||||||
'tap': (Map<String, String> params) => new Tap.deserialize(params),
|
'tap': (Map<String, String> params) => new Tap.deserialize(params),
|
||||||
'get_text': (Map<String, String> params) => new GetText.deserialize(params),
|
'get_text': (Map<String, String> params) => new GetText.deserialize(params),
|
||||||
'set_frame_sync': (Map<String, String> params) => new SetFrameSync.deserialize(params),
|
'set_frame_sync': (Map<String, String> params) => new SetFrameSync.deserialize(params),
|
||||||
|
'set_semantics': (Map<String, String> params) => new SetSemantics.deserialize(params),
|
||||||
'scroll': (Map<String, String> params) => new Scroll.deserialize(params),
|
'scroll': (Map<String, String> params) => new Scroll.deserialize(params),
|
||||||
'scrollIntoView': (Map<String, String> params) => new ScrollIntoView.deserialize(params),
|
'scrollIntoView': (Map<String, String> params) => new ScrollIntoView.deserialize(params),
|
||||||
'waitFor': (Map<String, String> params) => new WaitFor.deserialize(params),
|
'waitFor': (Map<String, String> params) => new WaitFor.deserialize(params),
|
||||||
@ -271,4 +274,27 @@ class FlutterDriverExtension {
|
|||||||
_frameSync = setFrameSyncCommand.enabled;
|
_frameSync = setFrameSyncCommand.enabled;
|
||||||
return new SetFrameSyncResult();
|
return new SetFrameSyncResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SemanticsHandle _semantics;
|
||||||
|
bool get _semanticsIsEnabled => RendererBinding.instance.pipelineOwner.semanticsOwner != null;
|
||||||
|
|
||||||
|
Future<SetSemanticsResult> _setSemantics(Command command) async {
|
||||||
|
final SetSemantics setSemanticsCommand = command;
|
||||||
|
final bool semanticsWasEnabled = _semanticsIsEnabled;
|
||||||
|
if (setSemanticsCommand.enabled && _semantics == null) {
|
||||||
|
_semantics = RendererBinding.instance.pipelineOwner.ensureSemantics();
|
||||||
|
if (!semanticsWasEnabled) {
|
||||||
|
// wait for the first frame where semantics is enabled.
|
||||||
|
final Completer<Null> completer = new Completer<Null>();
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((Duration d) {
|
||||||
|
completer.complete();
|
||||||
|
});
|
||||||
|
await completer.future;
|
||||||
|
}
|
||||||
|
} else if (!setSemanticsCommand.enabled && _semantics != null) {
|
||||||
|
_semantics.dispose();
|
||||||
|
_semantics = null;
|
||||||
|
}
|
||||||
|
return new SetSemanticsResult(semanticsWasEnabled != _semanticsIsEnabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
/// An object sent from the Flutter Driver to a Flutter application to instruct
|
/// An object sent from the Flutter Driver to a Flutter application to instruct
|
||||||
/// the application to perform a task.
|
/// the application to perform a task.
|
||||||
abstract class Command {
|
abstract class Command {
|
||||||
@ -20,6 +22,7 @@ abstract class Command {
|
|||||||
String get kind;
|
String get kind;
|
||||||
|
|
||||||
/// Serializes this command to parameter name/value pairs.
|
/// Serializes this command to parameter name/value pairs.
|
||||||
|
@mustCallSuper
|
||||||
Map<String, String> serialize() => <String, String>{
|
Map<String, String> serialize() => <String, String>{
|
||||||
'command': kind,
|
'command': kind,
|
||||||
'timeout': '${timeout.inMilliseconds}',
|
'timeout': '${timeout.inMilliseconds}',
|
||||||
|
43
packages/flutter_driver/lib/src/semantics.dart
Normal file
43
packages/flutter_driver/lib/src/semantics.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2017 The Chromium 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 'message.dart';
|
||||||
|
|
||||||
|
/// Enables or disables semantics.
|
||||||
|
class SetSemantics extends Command {
|
||||||
|
@override
|
||||||
|
final String kind = 'set_semantics';
|
||||||
|
|
||||||
|
/// Whether semantics should be enabled or disabled.
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
|
SetSemantics(this.enabled, { Duration timeout }) : super(timeout: timeout);
|
||||||
|
|
||||||
|
/// Deserializes this command from the value generated by [serialize].
|
||||||
|
SetSemantics.deserialize(Map<String, String> params)
|
||||||
|
: this.enabled = params['enabled'].toLowerCase() == 'true',
|
||||||
|
super.deserialize(params);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
|
||||||
|
'enabled': '$enabled',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of a [SetSemantics] command.
|
||||||
|
class SetSemanticsResult extends Result {
|
||||||
|
SetSemanticsResult(this.changedState);
|
||||||
|
|
||||||
|
final bool changedState;
|
||||||
|
|
||||||
|
/// Deserializes this result from JSON.
|
||||||
|
static SetSemanticsResult fromJson(Map<String, dynamic> json) {
|
||||||
|
return new SetSemanticsResult(json['changedState']);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||||
|
'changedState': changedState,
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user