blob: 2f6d264d36f4c6ad4e76427a689affb603bc4bbb [file] [log] [blame] [edit]
// 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:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:test/fake.dart';
import 'package:vm_service/vm_service.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';
class _FakeVm extends Fake implements VM {
_FakeVm(this._appPid);
final int _appPid;
@override
int? get pid => _appPid;
}
class _FakeFlutterVmService extends Fake implements FlutterVmService {
_FakeFlutterVmService(this._appPid);
final int _appPid;
@override
Future<VM?> getVmGuarded() async => _FakeVm(_appPid);
}
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,
BufferLogger.test(),
);
await logReader.provideVmService(_FakeFlutterVmService(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,
BufferLogger.test(),
);
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,
BufferLogger.test(),
);
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,
BufferLogger.test(),
);
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,
BufferLogger.test(),
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,
BufferLogger.test(),
);
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,
BufferLogger.test()
);
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,
];
}
}