run method

Future<void> run(
  1. String testTarget, {
  2. required String testDriver,
  3. bool headless = false,
  4. List<String> dartDefineArgs = const <String>[],
  5. bool debugLogging = false,
})

Implementation

Future<void> run(
  String testTarget, {
  required String testDriver,
  bool headless = false,
  List<String> dartDefineArgs = const <String>[],
  bool debugLogging = false,
}) async {
  void debugLog(String log) {
    if (debugLogging) print(log);
  }

  Future<void> runTest({required int attemptNumber}) async {
    debugLog('starting the flutter drive process');
    final process = await Process.start(
      'flutter',
      [
        'drive',
        // Debug outputs from the test will not show up in profile mode. Since
        // we rely on debug outputs for detecting errors and exceptions from the
        // test, we cannot run this these tests in profile mode until this issue
        // is resolved.  See https://github.com/flutter/flutter/issues/69070.
        // '--profile',
        '--driver=$testDriver',
        '--target=$testTarget',
        '-d',
        headless ? 'web-server' : 'chrome',
        for (final arg in dartDefineArgs) '--dart-define=$arg',
      ],
    );

    bool stdOutWriteInProgress = false;
    bool stdErrWriteInProgress = false;
    final exceptionBuffer = StringBuffer();

    var testsPassed = false;
    listenToProcessOutput(
      process,
      printTag: 'FlutterDriveProcess',
      onStdout: (line) {
        if (line.endsWith(_allTestsPassed)) {
          testsPassed = true;
        }

        if (line.startsWith(_IntegrationTestResult.testResultPrefix)) {
          final testResultJson = line.substring(line.indexOf('{'));
          final testResultMap =
              jsonDecode(testResultJson) as Map<String, Object?>;
          final result = _IntegrationTestResult.parse(testResultMap);
          if (!result.result) {
            exceptionBuffer
              ..writeln('$result')
              ..writeln();
          }
        }

        if (line.contains(_beginExceptionMarker)) {
          stdOutWriteInProgress = true;
        }
        if (stdOutWriteInProgress) {
          exceptionBuffer.writeln(line);
          // Marks the end of the exception caught by flutter.
          if (line.contains(_endExceptionMarker) &&
              !line.contains(_beginExceptionMarker)) {
            stdOutWriteInProgress = false;
            exceptionBuffer.writeln();
          }
        }
      },
      onStderr: (line) {
        if (line.contains(_errorMarker) ||
            line.contains(_unhandledExceptionMarker)) {
          stdErrWriteInProgress = true;
        }
        if (stdErrWriteInProgress) {
          exceptionBuffer.writeln(line);
        }
      },
    );

    bool testTimedOut = false;
    final timeout = Future.delayed(const Duration(minutes: 8)).then((_) {
      testTimedOut = true;
    });

    await Future.any([
      process.exitCode,
      timeout,
    ]);

    debugLog('attempting to kill the flutter drive process');
    process.kill();
    debugLog('flutter drive process has exited');

    // Ignore exception handling and retries if the tests passed. This is to
    // avoid bugs with the test runner where the test can fail after the test
    // has passed. See https://github.com/flutter/flutter/issues/129041.
    if (!testsPassed) {
      if (testTimedOut) {
        if (attemptNumber >= _maxRetriesOnTimeout) {
          throw Exception(
            'Integration test timed out on try #$attemptNumber: $testTarget',
          );
        } else {
          debugLog(
            'Integration test timed out on try #$attemptNumber. Retrying '
            '$testTarget now.',
          );
          await runTest(attemptNumber: ++attemptNumber);
        }
      }

      if (exceptionBuffer.isNotEmpty) {
        throw Exception(exceptionBuffer.toString());
      }
    }
  }

  await runTest(attemptNumber: 0);
}