flutter_attach_test_android.dart 5.63 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 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:path/path.dart' as path;
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';

Future<void> testReload(Process process, { Future<void> Function() onListening }) async {
  section('Testing hot reload, restart and quit');
16 17 18 19 20
  final Completer<void> listening = Completer<void>();
  final Completer<void> ready = Completer<void>();
  final Completer<void> reloaded = Completer<void>();
  final Completer<void> restarted = Completer<void>();
  final Completer<void> finished = Completer<void>();
21 22 23 24 25 26 27 28
  final List<String> stdout = <String>[];
  final List<String> stderr = <String>[];

  if (onListening == null)
    listening.complete();

  int exitCode;
  process.stdout
29 30
      .transform<String>(utf8.decoder)
      .transform<String>(const LineSplitter())
31 32 33
      .listen((String line) {
    print('attach:stdout: $line');
    stdout.add(line);
34
    if (line.contains('Waiting') && onListening != null)
35
      listening.complete(onListening());
36
    if (line.contains('Quit (terminate the application on the device)'))
37 38 39
      ready.complete();
    if (line.contains('Reloaded '))
      reloaded.complete();
40
    if (line.contains('Restarted application in '))
41 42 43 44 45
      restarted.complete();
    if (line.contains('Application finished'))
      finished.complete();
  });
  process.stderr
46 47
      .transform<String>(utf8.decoder)
      .transform<String>(const LineSplitter())
48 49
      .listen((String line) {
    print('run:stderr: $line');
50
    stdout.add(line);
51 52
  });

53
  process.exitCode.then<void>((int processExitCode) { exitCode = processExitCode; });
54

55
  Future<dynamic> eventOrExit(Future<void> event) {
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    return Future.any<dynamic>(<Future<dynamic>>[ event, process.exitCode ]);
  }

  await eventOrExit(listening.future);
  await eventOrExit(ready.future);

  if (exitCode != null)
    throw 'Failed to attach to test app; command unexpected exited, with exit code $exitCode.';

  process.stdin.write('r');
  process.stdin.flush();
  await eventOrExit(reloaded.future);
  process.stdin.write('R');
  process.stdin.flush();
  await eventOrExit(restarted.future);
  process.stdin.write('q');
  process.stdin.flush();
  await eventOrExit(finished.future);

  await process.exitCode;

  if (stderr.isNotEmpty)
    throw 'flutter attach had output on standard error.';

  if (exitCode != 0)
    throw 'exit code was not 0';
}

void main() {
  const String kAppId = 'com.yourcompany.integration_ui';
  const String kActivityId = '$kAppId/com.yourcompany.integration_ui.MainActivity';

  task(() async {
89
    final AndroidDevice device = await devices.workingDevice as AndroidDevice;
90 91 92
    await device.unlock();
    final Directory appDir = dir(path.join(flutterDirectory.path, 'dev/integration_tests/ui'));
    await inDirectory(appDir, () async {
93
      section('Building');
94 95 96 97 98
      final String buildStdout = await eval(
          path.join(flutterDirectory.path, 'bin', 'flutter'),
          <String>['--suppress-analytics', 'build', 'apk', '--debug', 'lib/main.dart'],
      );
      final String lastLine = buildStdout.split('\n').last;
99
      final RegExp builtRegExp = RegExp(r'Built (.+)( \(|\.$)');
100 101 102 103
      final String apkPath = builtRegExp.firstMatch(lastLine)[1];

      section('Installing $apkPath');

104
      await device.adb(<String>['install', '-r', apkPath]);
105 106

      try {
107
        section('Launching `flutter attach`');
108 109 110 111 112 113 114 115 116 117
        Process attachProcess = await startProcess(
          path.join(flutterDirectory.path, 'bin', 'flutter'),
          <String>['--suppress-analytics', 'attach', '-d', device.deviceId],
          isBot: false, // we just want to test the output, not have any debugging info
        );

        await testReload(attachProcess, onListening: () async {
          await device.shellExec('am', <String>['start', '-n', kActivityId]);
        });

118
        // Give the device the time to really shut down the app.
119
        await Future<void>.delayed(const Duration(milliseconds: 200));
120 121 122
        // After the delay, force-stopping it shouldn't do anything, but doesn't hurt.
        await device.shellExec('am', <String>['force-stop', kAppId]);

123 124
        final String currentTime = (await device.shellEval('date', <String>['"+%F %R:%S.000"'])).trim();
        print('Start time on device: $currentTime');
125
        section('Relaunching application');
126 127
        await device.shellExec('am', <String>['start', '-n', kActivityId]);

128
        // If the next line fails, your device may not support regexp search.
129 130
        final String observatoryLine = await device.adb(<String>['logcat', '-e', 'Observatory listening on http:', '-m', '1', '-T', currentTime]);
        print('Found observatory line: $observatoryLine');
131
        final String observatoryUri = RegExp(r'Observatory listening on ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)').firstMatch(observatoryLine)[1];
132
        print('Extracted observatory port: $observatoryUri');
133

134
        section('Launching attach with given port');
135 136
        attachProcess = await startProcess(
          path.join(flutterDirectory.path, 'bin', 'flutter'),
137 138
          <String>['--suppress-analytics', 'attach', '--debug-uri',
          observatoryUri, '-d', device.deviceId],
139 140 141 142 143 144 145 146 147
          isBot: false, // we just want to test the output, not have any debugging info
        );
        await testReload(attachProcess);

      } finally {
        section('Uninstalling');
        await device.adb(<String>['uninstall', kAppId]);
      }
    });
148
    return TaskResult.success(null);
149 150
  });
}