flutter_test_adapter.dart 5.43 KB
Newer Older
1 2 3 4 5
// 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';
6
import 'dart:math' as math;
7

8
import 'package:dds/dap.dart' hide PidTracker;
9 10 11 12 13

import '../base/io.dart';
import '../cache.dart';
import '../convert.dart';
import 'flutter_adapter_args.dart';
14
import 'flutter_base_adapter.dart';
15 16

/// A DAP Debug Adapter for running and debugging Flutter tests.
17
class FlutterTestDebugAdapter extends FlutterBaseDebugAdapter with TestAdapter {
18
  FlutterTestDebugAdapter(
19
    super.channel, {
20 21
    required super.fileSystem,
    required super.platform,
22
    super.ipv6,
23
    super.enableFlutterDds = true,
24 25
    super.enableAuthCodes,
    super.logger,
26
    super.onError,
27
  });
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

  /// Called by [attachRequest] to request that we actually connect to the app to be debugged.
  @override
  Future<void> attachImpl() async {
    sendOutput('console', '\nAttach is not currently supported');
    handleSessionTerminate();
  }

  /// Called by [launchRequest] to request that we actually start the tests to be run/debugged.
  ///
  /// For debugging, this should start paused, connect to the VM Service, set
  /// breakpoints, and resume.
  @override
  Future<void> launchImpl() async {
    final FlutterLaunchRequestArguments args = this.args as FlutterLaunchRequestArguments;

44
    final bool debug = enableDebugger;
45 46 47 48 49
    final String? program = args.program;

    final List<String> toolArgs = <String>[
      'test',
      '--machine',
50
      if (!enableFlutterDds) '--no-dds',
51 52
      if (debug) '--start-paused',
    ];
53 54 55 56 57 58 59 60

    // Handle customTool and deletion of any arguments for it.
    final String executable = args.customTool ?? fileSystem.path.join(Cache.flutterRoot!, 'bin', platform.isWindows ? 'flutter.bat' : 'flutter');
    final int? removeArgs = args.customToolReplacesArgs;
    if (args.customTool != null && removeArgs != null) {
      toolArgs.removeRange(0, math.min(removeArgs, toolArgs.length));
    }

61 62 63 64 65 66 67
    final List<String> processArgs = <String>[
      ...toolArgs,
      ...?args.toolArgs,
      if (program != null) program,
      ...?args.args,
    ];

68 69 70 71 72
    await launchAsProcess(
      executable: executable,
      processArgs: processArgs,
      env: args.env,
    );
73 74 75 76 77 78 79

    // Delay responding until the debugger is connected.
    if (debug) {
      await debuggerInitialized;
    }
  }

80 81 82 83
  /// Called by [terminateRequest] to request that we gracefully shut down the app being run (or in the case of an attach, disconnect).
  @override
  Future<void> terminateImpl() async {
    terminatePids(ProcessSignal.sigterm);
84
    await process?.exitCode;
85 86 87
  }

  /// Handles the Flutter process exiting, terminating the debug session if it has not already begun terminating.
88 89
  @override
  void handleExitCode(int code) {
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    final String codeSuffix = code == 0 ? '' : ' ($code)';
    logger?.call('Process exited ($code)');
    handleSessionTerminate(codeSuffix);
  }

  /// Handles incoming JSON events from `flutter test --machine`.
  bool _handleJsonEvent(String event, Map<String, Object?>? params) {
    params ??= <String, Object?>{};
    switch (event) {
      case 'test.startedProcess':
        _handleTestStartedProcess(params);
        return true;
    }

    return false;
  }

107 108
  @override
  void handleStderr(List<int> data) {
109 110 111 112 113
    logger?.call('stderr: $data');
    sendOutput('stderr', utf8.decode(data));
  }

  /// Handles stdout from the `flutter test --machine` process, decoding the JSON and calling the appropriate handlers.
114 115
  @override
  void handleStdout(String data) {
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
    // Output to stdout from `flutter test --machine` is either:
    //   1. JSON output from flutter_tools (eg. "test.startedProcess") which is
    //      wrapped in [] brackets and has an event/params.
    //   2. JSON output from package:test (not wrapped in brackets).
    //   3. Non-JSON output (user messages, or flutter_tools printing things like
    //      call stacks/error information).
    logger?.call('stdout: $data');

    Object? jsonData;
    try {
      jsonData = jsonDecode(data);
    } on FormatException {
      // If the output wasn't valid JSON, it was standard stdout that should
      // be passed through to the user.
      sendOutput('stdout', data);
      return;
    }

    // Check for valid flutter_tools JSON output (1) first.
    final Map<String, Object?>? flutterPayload = jsonData is List &&
            jsonData.length == 1 &&
            jsonData.first is Map<String, Object?>
        ? jsonData.first as Map<String, Object?>
        : null;
    final Object? event = flutterPayload?['event'];
    final Object? params = flutterPayload?['params'];

    if (event is String && params is Map<String, Object?>?) {
      _handleJsonEvent(event, params);
    } else if (jsonData != null) {
      // Handle package:test output (2).
      sendTestEvents(jsonData);
    } else {
      // Other output should just be passed straight through.
      sendOutput('stdout', data);
    }
  }

  /// Handles the test.processStarted event from Flutter that provides the VM Service URL.
  void _handleTestStartedProcess(Map<String, Object?> params) {
156
    final String? vmServiceUriString = params['vmServiceUri'] as String?;
157 158 159
    // For no-debug mode, this event may be still sent so ignore it if we know
    // we're not debugging, or its URI is null.
    if (!enableDebugger || vmServiceUriString == null) {
160 161 162
      return;
    }
    final Uri vmServiceUri = Uri.parse(vmServiceUriString);
163
    connectDebugger(vmServiceUri);
164 165
  }
}