// 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 'package:flutter_tools/src/android/android_device.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/fake_process_manager.dart'; const int kLollipopVersionCode = 21; const String kLastLogcatTimestamp = '11-27 15:39:04.506'; /// By default the android log reader accepts lines that match no patterns /// if the previous line was a match. Include an intentionally non-matching /// line as the first input to disable this behavior. const String kDummyLine = 'Contents are not important\n'; void main() { testWithoutContext('AdbLogReader ignores spam from SurfaceSyncer', () async { const int appPid = 1; final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand( command: const <String>[ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', ], completer: Completer<void>.sync(), stdout: '$kDummyLine' '05-11 12:54:46.665 W/flutter($appPid): Hello there!\n' '05-11 12:54:46.665 E/SurfaceSyncer($appPid): Failed to find sync for id=9\n' '05-11 12:54:46.665 E/SurfaceSyncer($appPid): Failed to find sync for id=10\n' ), ]); final AdbLogReader logReader = await AdbLogReader.createLogReader( createFakeDevice(null), processManager, )..appPid = appPid; final Completer<void> onDone = Completer<void>.sync(); final List<String> emittedLines = <String>[]; logReader.logLines.listen((String line) { emittedLines.add(line); }, onDone: onDone.complete); await null; logReader.dispose(); await onDone.future; expect(emittedLines, const <String>['W/flutter($appPid): Hello there!']); }); testWithoutContext('AdbLogReader calls adb logcat with expected flags apiVersion 21', () async { final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: <String>[ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-T', "'$kLastLogcatTimestamp'", ], ), ]); await AdbLogReader.createLogReader( createFakeDevice(kLollipopVersionCode), processManager, ); expect(processManager, hasNoRemainingExpectations); }); testWithoutContext('AdbLogReader calls adb logcat with expected flags apiVersion < 21', () async { final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: <String>[ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', ], ), ]); await AdbLogReader.createLogReader( createFakeDevice(kLollipopVersionCode - 1), processManager, ); expect(processManager, hasNoRemainingExpectations); }); testWithoutContext('AdbLogReader calls adb logcat with expected flags null apiVersion', () async { final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: <String>[ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', ], ), ]); await AdbLogReader.createLogReader( createFakeDevice(null), processManager, ); expect(processManager, hasNoRemainingExpectations); }); testWithoutContext('AdbLogReader calls adb logcat with expected flags when requesting past logs', () async { final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: <String>[ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-s', 'flutter', ], ), ]); await AdbLogReader.createLogReader( createFakeDevice(null), processManager, includePastLogs: true, ); expect(processManager, hasNoRemainingExpectations); }); testWithoutContext('AdbLogReader handles process early exit', () async { final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand( command: const <String>[ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', ], completer: Completer<void>.sync(), stdout: 'Hello There\n', ), ]); final AdbLogReader logReader = await AdbLogReader.createLogReader( createFakeDevice(null), processManager, ); final Completer<void> onDone = Completer<void>.sync(); logReader.logLines.listen((String _) { }, onDone: onDone.complete); logReader.dispose(); await onDone.future; }); testWithoutContext('AdbLogReader does not filter output from AndroidRuntime crashes', () async { final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand( command: const <String>[ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', ], completer: Completer<void>.sync(), // Example stack trace from an incorrectly named application:name in the AndroidManifest.xml stdout: '$kDummyLine' '05-11 12:54:46.665 E/AndroidRuntime(11787): FATAL EXCEPTION: main\n' '05-11 12:54:46.665 E/AndroidRuntime(11787): Process: com.example.foobar, PID: 11787\n' '05-11 12:54:46.665 java.lang.RuntimeException: Unable to instantiate application ' 'io.flutter.app.FlutterApplication2: java.lang.ClassNotFoundException:\n', ), ]); final AdbLogReader logReader = await AdbLogReader.createLogReader( createFakeDevice(null), processManager, ); await expectLater(logReader.logLines, emitsInOrder(<String>[ 'E/AndroidRuntime(11787): FATAL EXCEPTION: main', 'E/AndroidRuntime(11787): Process: com.example.foobar, PID: 11787', 'java.lang.RuntimeException: Unable to instantiate application io.flutter.app.FlutterApplication2: java.lang.ClassNotFoundException:', ])); logReader.dispose(); }); } AndroidDevice createFakeDevice(int? sdkLevel) { return FakeAndroidDevice( sdkLevel.toString(), kLastLogcatTimestamp, ); } class FakeAndroidDevice extends Fake implements AndroidDevice { FakeAndroidDevice(this._apiVersion, this._lastLogcatTimestamp); final String _lastLogcatTimestamp; final String _apiVersion; @override String get name => 'test-device'; @override Future<String> get apiVersion => Future<String>.value(_apiVersion); @override Future<String> lastLogcatTimestamp() async => _lastLogcatTimestamp; @override List<String> adbCommandForDevice(List<String> command) { return <String>[ 'adb', '-s', '1234', ...command, ]; } }