test_support.dart 5.79 KB
Newer Older
1 2 3 4 5 6 7
// 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:io';

8 9 10 11 12
import 'package:dds/dap.dart';
import 'package:file/file.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
import 'package:test/test.dart';

import 'test_client.dart';
import 'test_server.dart';

/// Whether to run the DAP server in-process with the tests, or externally in
/// another process.
///
/// By default tests will run the DAP server out-of-process to match the real
/// use from editors, but this complicates debugging the adapter. Set this env
/// variables to run the server in-process for easier debugging (this can be
/// simplified in VS Code by using a launch config with custom CodeLens links).
final bool useInProcessDap = Platform.environment['DAP_TEST_INTERNAL'] == 'true';

/// Whether to print all protocol traffic to stdout while running tests.
///
/// This is useful for debugging locally or on the bots and will include both
/// DAP traffic (between the test DAP client and the DAP server) and the VM
/// Service traffic (wrapped in a custom 'dart.log' event).
final bool verboseLogging = Platform.environment['DAP_TEST_VERBOSE'] == 'true';

34 35
const String endOfErrorOutputMarker = '════════════════════════════════════════════════════════════════════════════════════════════════════';

36 37
/// Expects the lines in [actual] to match the relevant matcher in [expected],
/// ignoring differences in line endings and trailing whitespace.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
void expectLines(
  String actual,
  List<Object> expected, {
  bool allowExtras = false,
}) {
  if (allowExtras) {
    expect(
      actual.replaceAll('\r\n', '\n').trim().split('\n'),
      containsAllInOrder(expected),
    );
  } else {
    expect(
      actual.replaceAll('\r\n', '\n').trim().split('\n'),
      equals(expected),
    );
  }
54 55
}

56 57 58 59 60 61 62 63 64
/// Manages running a simple Flutter app to be used in tests that need to attach
/// to an existing process.
class SimpleFlutterRunner {
  SimpleFlutterRunner(this.process) {
    process.stdout.transform(ByteToLineTransformer()).listen(_handleStdout);
    process.stderr.transform(utf8.decoder).listen(_handleStderr);
    unawaited(process.exitCode.then(_handleExitCode));
  }

65 66 67 68 69
  final StreamController<String> _output = StreamController<String>.broadcast();

  /// A broadcast stream of any non-JSON output from the process.
  Stream<String> get output => _output.stream;

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
  void _handleExitCode(int code) {
      if (!_vmServiceUriCompleter.isCompleted) {
        _vmServiceUriCompleter.completeError('Flutter process ended without producing a VM Service URI');
      }
    }

  void _handleStderr(String err) {
    if (!_vmServiceUriCompleter.isCompleted) {
      _vmServiceUriCompleter.completeError(err);
    }
  }

  void _handleStdout(String outputLine) {
    try {
      final Object? json = jsonDecode(outputLine);
      // Flutter --machine output is wrapped in [brackets] so will deserialize
      // as a list with one item.
      if (json is List && json.length == 1) {
        final Object? message = json.single;
        // Parse the add.debugPort event which contains our VM Service URI.
        if (message is Map<String, Object?> && message['event'] == 'app.debugPort') {
          final String vmServiceUri = (message['params']! as Map<String, Object?>)['wsUri']! as String;
          if (!_vmServiceUriCompleter.isCompleted) {
            _vmServiceUriCompleter.complete(Uri.parse(vmServiceUri));
          }
        }
      }
    } on FormatException {
98 99 100
      // `flutter run` writes a lot of text to stdout that isn't daemon messages
      //  (not valid JSON), so just pass that one for tests that may want it.
      _output.add(outputLine);
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    }
  }

  final Process process;
  final Completer<Uri> _vmServiceUriCompleter = Completer<Uri>();
   Future<Uri> get vmServiceUri => _vmServiceUriCompleter.future;

  static Future<SimpleFlutterRunner> start(Directory projectDirectory) async {
    final String flutterToolPath = globals.fs.path.join(Cache.flutterRoot!, 'bin', globals.platform.isWindows ? 'flutter.bat' : 'flutter');

    final List<String> args = <String>[
      'run',
      '--machine',
      '-d',
      'flutter-tester',
    ];

    final Process process = await Process.start(
      flutterToolPath,
      args,
      workingDirectory: projectDirectory.path,
    );

    return SimpleFlutterRunner(process);
  }
}

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 156 157 158 159 160 161 162 163 164 165
/// A helper class containing the DAP server/client for DAP integration tests.
class DapTestSession {
  DapTestSession._(this.server, this.client);

  DapTestServer server;
  DapTestClient client;

  Future<void> tearDown() async {
    await client.stop();
    await server.stop();
  }

  static Future<DapTestSession> setUp({List<String>? additionalArgs}) async {
    final DapTestServer server = await _startServer(additionalArgs: additionalArgs);
    final DapTestClient client = await DapTestClient.connect(
      server,
      captureVmServiceTraffic: verboseLogging,
      logger: verboseLogging ? print : null,
    );
    return DapTestSession._(server, client);
  }

  /// Starts a DAP server that can be shared across tests.
  static Future<DapTestServer> _startServer({
    Logger? logger,
    List<String>? additionalArgs,
  }) async {
    return useInProcessDap
        ? await InProcessDapTestServer.create(
            logger: logger,
            additionalArgs: additionalArgs,
          )
        : await OutOfProcessDapTestServer.create(
            logger: logger,
            additionalArgs: additionalArgs,
          );
  }
}