blob: 5f113891eaa9f3d592fe108386f1c9c6c82dadfc [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 'dart:js_interop';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
import '../common/spy.dart';
@JS('window._flutter_internal_on_benchmark')
external set jsBenchmarkValueCallback(JSAny? object);
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
group('$Profiler', () {
_profilerTests();
});
group('$Instrumentation', () {
_instrumentationTests();
});
}
void _profilerTests() {
final List<String> warnings = <String>[];
late void Function(String) oldPrintWarning;
setUpAll(() {
oldPrintWarning = printWarning;
printWarning = (String warning) {
warnings.add(warning);
};
});
setUp(() {
warnings.clear();
Profiler.isBenchmarkMode = true;
Profiler.ensureInitialized();
});
tearDownAll(() {
printWarning = oldPrintWarning;
});
tearDown(() {
jsBenchmarkValueCallback = null;
ui_web.benchmarkValueCallback = null;
Profiler.isBenchmarkMode = false;
});
test('works when there is no listener', () {
expect(() => Profiler.instance.benchmark('foo', 123), returnsNormally);
});
test('can listen to benchmarks', () {
final List<BenchmarkDatapoint> data = <BenchmarkDatapoint>[];
ui_web.benchmarkValueCallback = (String name, double value) {
data.add((name, value));
};
Profiler.instance.benchmark('foo', 123);
expect(data, <BenchmarkDatapoint>[('foo', 123)]);
data.clear();
Profiler.instance.benchmark('bar', 0.0125);
expect(data, <BenchmarkDatapoint>[('bar', 0.0125)]);
data.clear();
// Remove listener and make sure nothing breaks and the data isn't being
// sent to the old callback anymore.
ui_web.benchmarkValueCallback = null;
expect(() => Profiler.instance.benchmark('baz', 99.999), returnsNormally);
expect(data, isEmpty);
});
// TODO(mdebbar): Remove this group once the JS API is removed.
// https://github.com/flutter/flutter/issues/127395
group('[JS API]', () {
test('can listen to benchmarks', () {
final List<BenchmarkDatapoint> data = <BenchmarkDatapoint>[];
jsBenchmarkValueCallback = (String name, double value) {
data.add((name, value));
}.toJS;
Profiler.instance.benchmark('foo', 123);
expect(warnings, hasLength(1));
expect(warnings.single, contains('deprecated'));
expect(warnings.single, contains('benchmarkValueCallback'));
expect(warnings.single, contains('dart:ui_web'));
warnings.clear();
expect(data, <BenchmarkDatapoint>[('foo', 123)]);
data.clear();
Profiler.instance.benchmark('bar', 0.0125);
expect(data, <BenchmarkDatapoint>[('bar', 0.0125)]);
data.clear();
// Remove listener and make sure nothing breaks and the data isn't being
// sent to the old callback anymore.
jsBenchmarkValueCallback = null;
expect(() => Profiler.instance.benchmark('baz', 99.999), returnsNormally);
expect(data, isEmpty);
});
test('throws on wrong listener type', () {
final List<BenchmarkDatapoint> data = <BenchmarkDatapoint>[];
// Wrong callback signature.
jsBenchmarkValueCallback = (double value) {
data.add(('bad', value));
}.toJS;
expect(
() => Profiler.instance.benchmark('foo', 123),
// dart2js throws a NoSuchMethodError, dart2wasm throws a TypeError here.
// Just make sure it throws an error in this case.
throwsA(isA<Error>()),
);
expect(data, isEmpty);
// Not even a callback.
jsBenchmarkValueCallback = 'string'.toJS;
expect(
() => Profiler.instance.benchmark('foo', 123),
// dart2js throws a TypeError, while dart2wasm throws an explicit
// exception.
throwsA(anything),
);
});
test('can be combined with ui_web API', () {
final List<BenchmarkDatapoint> uiWebData = <BenchmarkDatapoint>[];
final List<BenchmarkDatapoint> jsData = <BenchmarkDatapoint>[];
ui_web.benchmarkValueCallback = (String name, double value) {
uiWebData.add((name, value));
};
jsBenchmarkValueCallback = (String name, double value) {
jsData.add((name, value));
}.toJS;
Profiler.instance.benchmark('foo', 123);
expect(warnings, hasLength(1));
expect(warnings.single, contains('deprecated'));
expect(warnings.single, contains('benchmarkValueCallback'));
expect(warnings.single, contains('dart:ui_web'));
warnings.clear();
expect(uiWebData, <BenchmarkDatapoint>[('foo', 123)]);
expect(jsData, <BenchmarkDatapoint>[('foo', 123)]);
uiWebData.clear();
jsData.clear();
Profiler.instance.benchmark('bar', 0.0125);
expect(uiWebData, <BenchmarkDatapoint>[('bar', 0.0125)]);
expect(jsData, <BenchmarkDatapoint>[('bar', 0.0125)]);
uiWebData.clear();
jsData.clear();
ui_web.benchmarkValueCallback = null;
jsBenchmarkValueCallback = null;
expect(() => Profiler.instance.benchmark('baz', 99.999), returnsNormally);
expect(uiWebData, isEmpty);
expect(jsData, isEmpty);
});
});
}
void _instrumentationTests() {
setUp(() {
Instrumentation.enabled = false;
});
tearDown(() {
Instrumentation.enabled = false;
});
test('when disabled throws instead of initializing', () {
expect(() => Instrumentation.instance, throwsStateError);
});
test('when disabled throws instead of incrementing counter', () {
Instrumentation.enabled = true;
final Instrumentation instrumentation = Instrumentation.instance;
Instrumentation.enabled = false;
expect(() => instrumentation.incrementCounter('test'), throwsStateError);
});
test('when enabled increments counter', () {
final ZoneSpy spy = ZoneSpy();
spy.run(() {
Instrumentation.enabled = true;
final Instrumentation instrumentation = Instrumentation.instance;
expect(instrumentation.debugPrintTimer, isNull);
instrumentation.incrementCounter('foo');
expect(instrumentation.debugPrintTimer, isNotNull);
instrumentation.incrementCounter('foo');
instrumentation.incrementCounter('bar');
expect(spy.printLog, isEmpty);
expect(instrumentation.debugPrintTimer, isNotNull);
spy.fakeAsync.elapse(const Duration(seconds: 2));
expect(instrumentation.debugPrintTimer, isNull);
expect(spy.printLog, hasLength(1));
expect(
spy.printLog.single,
'Engine counters:\n'
' bar: 1\n'
' foo: 2\n',
);
});
});
}
typedef BenchmarkDatapoint = (String, double);