test_server.dart 4.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
// 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 'dart:convert';
import 'dart:io';

import 'package:dds/src/dap/logging.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/debug_adapters/server.dart';
12
import 'package:flutter_tools/src/globals.dart' as globals;
13 14 15 16 17 18 19 20 21

/// Enable to run from local source when running out-of-process (useful in
/// development to avoid having to keep rebuilding the flutter tool).
const bool _runFromSource = false;

abstract class DapTestServer {
  Future<void> stop();
  StreamSink<List<int>> get sink;
  Stream<List<int>> get stream;
22
  Function(String message)? onStderrOutput;
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
}

/// An instance of a DAP server running in-process (to aid debugging).
///
/// All communication still goes over the socket to ensure all messages are
/// serialized and deserialized but it's not quite the same running out of
/// process.
class InProcessDapTestServer extends DapTestServer {
  InProcessDapTestServer._(List<String> args) {
    _server = DapServer(
      stdinController.stream,
      stdoutController.sink,
      fileSystem: globals.fs,
      platform: globals.platform,
      // Simulate flags based on the args to aid testing.
      enableDds: !args.contains('--no-dds'),
      ipv6: args.contains('--ipv6'),
40
      test: args.contains('--test'),
41 42 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
    );
  }

  late final DapServer _server;
  final StreamController<List<int>> stdinController = StreamController<List<int>>();
  final StreamController<List<int>> stdoutController = StreamController<List<int>>();

  @override
  StreamSink<List<int>> get sink => stdinController.sink;

  @override
  Stream<List<int>> get stream => stdoutController.stream;

  @override
  Future<void> stop() async {
    _server.stop();
  }

  static Future<InProcessDapTestServer> create({
    Logger? logger,
    List<String>? additionalArgs,
  }) async {
    return InProcessDapTestServer._(additionalArgs ?? <String>[]);
  }
}

/// An instance of a DAP server running out-of-process.
///
/// This is how an editor will usually consume DAP so is a more accurate test
/// but will be a little more difficult to debug tests as the debugger will not
/// be attached to the process.
class OutOfProcessDapTestServer extends DapTestServer {
  OutOfProcessDapTestServer._(
    this._process,
    Logger? logger,
  ) {
77 78 79
    // Unless we're given an error handler, treat anything written to stderr as
    // the DAP crashing and fail the test unless it's "Waiting for another
    // flutter command to release the startup lock" or we're tearing down.
80 81 82 83 84
    _process.stderr
        .transform(utf8.decoder)
        .where((String error) => !error.contains('Waiting for another flutter command to release the startup lock'))
        .listen((String error) {
      logger?.call(error);
85
      if (!_isShuttingDown) {
86 87 88 89 90 91
        final Function(String message)? stderrHandler = onStderrOutput;
        if (stderrHandler != null) {
          stderrHandler(error);
        } else {
          throw Exception(error);
        }
92
      }
93 94 95 96
    });
    unawaited(_process.exitCode.then((int code) {
      final String message = 'Out-of-process DAP server terminated with code $code';
      logger?.call(message);
97
      if (!_isShuttingDown && code != 0 && onStderrOutput == null) {
98
        throw Exception(message);
99 100 101 102 103 104 105
      }
    }));
  }

  bool _isShuttingDown = false;
  final Process _process;

106 107
  Future<int> get exitCode => _process.exitCode;

108 109 110 111 112 113 114 115 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
  @override
  StreamSink<List<int>> get sink => _process.stdin;

  @override
  Stream<List<int>> get stream => _process.stdout;

  @override
  Future<void> stop() async {
    _isShuttingDown = true;
    _process.kill();
    await _process.exitCode;
  }

  static Future<OutOfProcessDapTestServer> create({
    Logger? logger,
    List<String>? additionalArgs,
  }) async {
    // runFromSource=true will run "dart bin/flutter_tools.dart ..." to avoid
    // having to rebuild the flutter_tools snapshot.
    // runFromSource=false will run "flutter ..."

    final String flutterToolPath = globals.fs.path.join(Cache.flutterRoot!, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
    final String flutterToolsEntryScript = globals.fs.path.join(Cache.flutterRoot!, 'packages', 'flutter_tools', 'bin', 'flutter_tools.dart');

    // When running from source, run "dart bin/flutter_tools.dart debug_adapter"
    // instead of directly using "flutter debug_adapter".
    final String executable = _runFromSource
      ? Platform.resolvedExecutable
      : flutterToolPath;
    final List<String> args = <String>[
      if (_runFromSource) flutterToolsEntryScript,
      'debug-adapter',
      ...?additionalArgs,
    ];

143
    final Process process = await Process.start(executable, args);
144

145
    return OutOfProcessDapTestServer._(process, logger);
146 147
  }
}