// 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:async'; import 'package:file/file.dart'; import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service_io.dart'; import '../src/common.dart'; import 'test_data/basic_project.dart'; import 'test_driver.dart'; import 'test_utils.dart'; void main() { late Directory tempDir; late FlutterRunTestDriver flutter; late VmService vmService; setUp(() async { tempDir = createResolvedTempDirectorySync('vmservice_integration_test.'); final BasicProjectWithTimelineTraces project = BasicProjectWithTimelineTraces(); await project.setUpIn(tempDir); flutter = FlutterRunTestDriver(tempDir); await flutter.run(withDebugger: true); final int? port = flutter.vmServicePort; vmService = await vmServiceConnectUri('ws://localhost:$port/ws'); }); tearDown(() async { await flutter.stop(); tryToDelete(tempDir); }); // Regression test for https://github.com/flutter/flutter/issues/79498 testWithoutContext('Can connect to the timeline without getting ANR from the application', () async { final Timer timer = Timer(const Duration(minutes: 5), () { // This message is intended to show up in CI logs. // ignore: avoid_print print( 'Warning: test isolate is still active after 5 minutes. This is likely an ' 'app-not-responding error and not a flake. See https://github.com/flutter/flutter/issues/79498 ' 'for the bug this test is attempting to exercise.' ); }); // Subscribe to all available streams. await Future.wait(<Future<void>>[ vmService.streamListen(EventStreams.kVM), vmService.streamListen(EventStreams.kIsolate), vmService.streamListen(EventStreams.kDebug), vmService.streamListen(EventStreams.kGC), vmService.streamListen(EventStreams.kExtension), vmService.streamListen(EventStreams.kTimeline), vmService.streamListen(EventStreams.kLogging), vmService.streamListen(EventStreams.kService), vmService.streamListen(EventStreams.kHeapSnapshot), vmService.streamListen(EventStreams.kStdout), vmService.streamListen(EventStreams.kStderr), ]); // Verify that the app can be interacted with by querying the brightness // for 30 seconds. Once this time has elapsed, wait for any pending requests and // exit. If the app stops responding, the requests made will hang. bool interactionCompleted = false; Timer(const Duration(seconds: 30), () { interactionCompleted = true; }); final Isolate isolate = await waitForExtension(vmService, 'ext.flutter.brightnessOverride'); while (!interactionCompleted) { final Response response = await vmService.callServiceExtension( 'ext.flutter.brightnessOverride', isolateId: isolate.id, ); expect(response.json!['value'], 'Brightness.light'); } timer.cancel(); // Verify that all duration events on the timeline are properly nested. final Response response = await vmService.callServiceExtension('getVMTimeline'); final List<TimelineEvent>? events = (response as Timeline).traceEvents; final Map<int, List<String>> threadDurationEventStack = <int, List<String>>{}; for (final TimelineEvent e in events!) { final Map<String, dynamic> event = e.json!; final String phase = event['ph'] as String; final int tid = event['tid'] as int; final String name = event['name'] as String; final List<String> stack = threadDurationEventStack.putIfAbsent(tid, () => <String>[]); if (phase == 'B') { stack.add(name); } else if (phase == 'E') { // The downloaded part of the timeline may contain an end event whose // corresponding begin event happened before the start of the timeline. if (stack.isNotEmpty) { expect(stack.removeLast(), name); } } } }); }