timeline_test.dart 3.96 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 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() {
17 18 19
  late Directory tempDir;
  late FlutterRunTestDriver flutter;
  late VmService vmService;
20 21 22 23 24 25 26 27 28

  setUp(() async {
    tempDir = createResolvedTempDirectorySync('vmservice_integration_test.');

    final BasicProjectWithTimelineTraces project = BasicProjectWithTimelineTraces();
    await project.setUpIn(tempDir);

    flutter = FlutterRunTestDriver(tempDir);
    await flutter.run(withDebugger: true);
29
    final int? port = flutter.vmServicePort;
30 31 32 33
    vmService = await vmServiceConnectUri('ws://localhost:$port/ws');
  });

  tearDown(() async {
34
    await flutter.stop();
35 36 37 38 39 40
    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), () {
41 42
      // This message is intended to show up in CI logs.
      // ignore: avoid_print
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
      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,
      );
79
      expect(response.json!['value'], 'Brightness.light');
80 81
    }
    timer.cancel();
82 83 84

    // Verify that all duration events on the timeline are properly nested.
    final Response response = await vmService.callServiceExtension('getVMTimeline');
85
    final List<TimelineEvent>? events = (response as Timeline).traceEvents;
86
    final Map<int, List<String>> threadDurationEventStack = <int, List<String>>{};
87 88
    for (final TimelineEvent e in events!) {
      final Map<String, dynamic> event = e.json!;
89 90 91 92 93 94 95 96 97 98 99 100 101 102
      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);
        }
      }
    }
103 104
  });
}