Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add performance tests #384

Merged
merged 25 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions packages/fleather/example/integration_test/editing_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:fleather/fleather.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test_robots/flutter_test_robots.dart';
import 'package:integration_test/integration_test.dart';
import 'package:parchment/codecs.dart';

void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('Enter some text at the end', (tester) async {
final document = const ParchmentMarkdownCodec().decode(markdown * 100);
final controller = FleatherController(document: document);
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: FleatherEditor(controller: controller),
),
));

await binding.traceAction(
() async {
await tester.tap(find.byType(RawEditor));
await tester.pump();
controller.updateSelection(
TextSelection.collapsed(offset: document.length - 1));
await tester.pump();
await tester.ime.typeText(iputText, finder: find.byType(RawEditor));
},
reportKey: 'timeline',
);
});
}

const iputText =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';

const markdown = '''
# Fleather

_Soft and gentle rich text editing for Flutter applications._

Fleather is an **early preview** open source library.

- [ ] That even supports
- [X] Checklists

### Documentation

* Quick Start
* Data format and Document Model
* Style attributes
* Heuristic rules

## Clean and modern look

Fleather’s rich text editor is built with _simplicity and flexibility_ in mind. It provides clean interface for distraction-free editing. Think `Medium.com`-like experience.

```
import ‘package:flutter/material.dart’;
import ‘package:parchment/parchment.dart’;

void main() {
print(“Hello world!”);
}
```

''';
69 changes: 69 additions & 0 deletions packages/fleather/example/integration_test/scrolling_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:fleather/fleather.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:parchment/codecs.dart';

void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('Scroll to the end', (tester) async {
final document = const ParchmentMarkdownCodec().decode(markdown * 100);
final controller = FleatherController(document: document);
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: FleatherEditor(controller: controller),
),
));

final scrollableFinder = find.byType(Scrollable);
final scrollable = tester.widget<Scrollable>(scrollableFinder);
final scrollController = scrollable.controller!;

await binding.traceAction(
() async {
while (scrollController.position.extentAfter != 0) {
await tester.drag(scrollableFinder, const Offset(0, -500));
await tester.pump();
}
while (scrollController.position.extentBefore != 0) {
await tester.drag(scrollableFinder, const Offset(0, 500));
await tester.pump();
}
},
reportKey: 'timeline',
);
});
}

const markdown = '''
# Fleather

_Soft and gentle rich text editing for Flutter applications._

Fleather is an **early preview** open source library.

- [ ] That even supports
- [X] Checklists

### Documentation

* Quick Start
* Data format and Document Model
* Style attributes
* Heuristic rules

## Clean and modern look

Fleather’s rich text editor is built with _simplicity and flexibility_ in mind. It provides clean interface for distraction-free editing. Think `Medium.com`-like experience.

```
import ‘package:flutter/material.dart’;
import ‘package:parchment/parchment.dart’;

void main() {
print(“Hello world!”);
}
```

''';
8 changes: 8 additions & 0 deletions packages/fleather/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ dependencies:
image_picker: ^1.0.7
fleather:
path: ../
parchment:
amantoux marked this conversation as resolved.
Show resolved Hide resolved
path: ../../parchment

dependency_overrides:
parchment:
Expand All @@ -23,6 +25,12 @@ dependency_overrides:
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
flutter_driver:
sdk: flutter
flutter_lints: ^4.0.0
flutter_test_robots: ^0.0.24

flutter:
uses-material-design: true
Expand Down
29 changes: 0 additions & 29 deletions packages/fleather/example/test/widget_test.dart

This file was deleted.

19 changes: 19 additions & 0 deletions packages/fleather/example/test_driver/performance_driver.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'dart:io';

import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';

Future<void> main() {
final outputName = Platform.environment['FLEATHER_PERF_TEST_OUTPUT_NAME']!;
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline =
driver.Timeline.fromJson(data['timeline'] as Map<String, dynamic>);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(outputName,
pretty: true, includeSummary: true);
}
},
);
}
93 changes: 93 additions & 0 deletions packages/fleather/example/test_utils/analyze_performance.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// ignore_for_file: avoid_print

import 'dart:convert';
import 'dart:io';

class Criteria {
final String key, title;
final double threshold;

const Criteria(this.key, this.title, {this.threshold = 0.1});

bool isRegression(double target, double reference) {
final double changeRatio = (target - reference) / reference;
return changeRatio > threshold;
}

bool isWorse(double target, double reference) => target > reference;
}

const _criterias = [
Criteria('average_frame_build_time_millis', 'Average Frame Build Time'),
Criteria('90th_percentile_frame_build_time_millis',
'90th Percentile Frame Build Time'),
Criteria('99th_percentile_frame_build_time_millis',
'90th Percentile Frame Build Time'),
Criteria('average_gpu_frame_time', 'Average GPU Frame Time'),
Criteria('average_memory_usage', 'Average Memory Usage'),
Criteria('average_cpu_usage', 'Average CPU Usage'),
];

void main(List<String> args) {
final outputBuffer = StringBuffer();
bool hasRegression = false;

final reference = args[0];
final target = args[1];

final result = _analyze(reference, target);
hasRegression = hasRegression || result.$2;
outputBuffer.writeln(result.$1);

print(outputBuffer.toString());
exit(hasRegression ? 1 : 0);
}

(String, bool) _analyze(String reference, String target) {
final Map<String, dynamic> referenceSummary =
jsonDecode(File(reference).readAsStringSync());
final Map<String, dynamic> targetSummary =
jsonDecode(File(target).readAsStringSync());

bool testHasRegression = false;
final outputBuffer = StringBuffer();

for (final criteria in _criterias) {
if (!targetSummary.containsKey(criteria.key)) continue;
bool criteriaHasRegression = false;
final double targetValue = targetSummary[criteria.key];
final double referenceValue = referenceSummary[criteria.key];
criteriaHasRegression = criteria.isRegression(targetValue, referenceValue);
outputBuffer.write('${criteria.title}: Target: ');
if (criteria.isWorse(targetValue, referenceValue)) {
outputBuffer.write(buildErrorMessage(targetValue.toStringAsFixed(2)));
} else {
outputBuffer.write(targetValue.toStringAsFixed(2));
}
outputBuffer.writeln(' Reference: ${referenceValue.toStringAsFixed(2)}');
testHasRegression = testHasRegression || criteriaHasRegression;
}

outputBuffer.write('Performance tests found ');
if (testHasRegression) {
outputBuffer.writeln(buildErrorMessage('regression'));
} else {
outputBuffer.writeln(buildSuccessMessage('no regression'));
}

return (outputBuffer.toString(), testHasRegression);
}

void assertTrueOrExit(bool assertion, [String? message]) {
if (!assertion) {
if (message != null) {
error(message);
}
exit(1);
}
}

void error(String message) => print(buildErrorMessage(message));

String buildErrorMessage(String message) => '\x1B[31m$message\x1B[0m';
String buildSuccessMessage(String message) => '\x1B[32m$message\x1B[0m';
Loading
Loading