Unverified Commit 944a4c97 authored by Lau Ching Jun's avatar Lau Ching Jun Committed by GitHub

Write crash report in temp directory if writing to CWD failed. (#113316)

parent 91d88336
...@@ -13,6 +13,7 @@ import 'package:intl/intl_standalone.dart' as intl_standalone; ...@@ -13,6 +13,7 @@ import 'package:intl/intl_standalone.dart' as intl_standalone;
import 'src/base/async_guard.dart'; import 'src/base/async_guard.dart';
import 'src/base/common.dart'; import 'src/base/common.dart';
import 'src/base/context.dart'; import 'src/base/context.dart';
import 'src/base/error_handling_io.dart';
import 'src/base/file_system.dart'; import 'src/base/file_system.dart';
import 'src/base/io.dart'; import 'src/base/io.dart';
import 'src/base/logger.dart'; import 'src/base/logger.dart';
...@@ -189,12 +190,6 @@ String _crashException(dynamic error) => '${error.runtimeType}: $error'; ...@@ -189,12 +190,6 @@ String _crashException(dynamic error) => '${error.runtimeType}: $error';
/// Saves the crash report to a local file. /// Saves the crash report to a local file.
Future<File> _createLocalCrashReport(CrashDetails details) async { Future<File> _createLocalCrashReport(CrashDetails details) async {
File crashFile = globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'log',
);
final StringBuffer buffer = StringBuffer(); final StringBuffer buffer = StringBuffer();
buffer.writeln('Flutter crash report.'); buffer.writeln('Flutter crash report.');
...@@ -210,22 +205,32 @@ Future<File> _createLocalCrashReport(CrashDetails details) async { ...@@ -210,22 +205,32 @@ Future<File> _createLocalCrashReport(CrashDetails details) async {
buffer.writeln('## flutter doctor\n'); buffer.writeln('## flutter doctor\n');
buffer.writeln('```\n${await details.doctorText.text}```'); buffer.writeln('```\n${await details.doctorText.text}```');
try { late File crashFile;
crashFile.writeAsStringSync(buffer.toString()); ErrorHandlingFileSystem.noExitOnFailure(() {
} on FileSystemException catch (_) {
// Fallback to the system temporary directory.
crashFile = globals.fsUtils.getUniqueFile(
globals.fs.systemTempDirectory,
'flutter',
'log',
);
try { try {
crashFile = globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'log',
);
crashFile.writeAsStringSync(buffer.toString()); crashFile.writeAsStringSync(buffer.toString());
} on FileSystemException catch (e) { } on FileSystemException catch (_) {
globals.printError('Could not write crash report to disk: $e'); // Fallback to the system temporary directory.
globals.printError(buffer.toString()); try {
crashFile = globals.fsUtils.getUniqueFile(
globals.fs.systemTempDirectory,
'flutter',
'log',
);
crashFile.writeAsStringSync(buffer.toString());
} on FileSystemException catch (e) {
globals.printError('Could not write crash report to disk: $e');
globals.printError(buffer.toString());
rethrow;
}
} }
} });
return crashFile; return crashFile;
} }
......
...@@ -816,7 +816,7 @@ void _throwFileSystemException(String? errorMessage) { ...@@ -816,7 +816,7 @@ void _throwFileSystemException(String? errorMessage) {
return; return;
} }
if (ErrorHandlingFileSystem._noExitOnFailure) { if (ErrorHandlingFileSystem._noExitOnFailure) {
throw Exception(errorMessage); throw FileSystemException(errorMessage);
} }
throwToolExit(errorMessage); throwToolExit(errorMessage);
} }
...@@ -27,6 +27,7 @@ const String kCustomBugInstructions = 'These are instructions to report with a c ...@@ -27,6 +27,7 @@ const String kCustomBugInstructions = 'These are instructions to report with a c
void main() { void main() {
int? firstExitCode; int? firstExitCode;
late MemoryFileSystem fileSystem;
group('runner', () { group('runner', () {
setUp(() { setUp(() {
...@@ -46,6 +47,7 @@ void main() { ...@@ -46,6 +47,7 @@ void main() {
}); });
Cache.disableLocking(); Cache.disableLocking();
fileSystem = MemoryFileSystem.test();
}); });
tearDown(() { tearDown(() {
...@@ -93,7 +95,7 @@ void main() { ...@@ -93,7 +95,7 @@ void main() {
'FLUTTER_ANALYTICS_LOG_FILE': 'test', 'FLUTTER_ANALYTICS_LOG_FILE': 'test',
'FLUTTER_ROOT': '/', 'FLUTTER_ROOT': '/',
}), }),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
Usage: () => CrashingUsage(), Usage: () => CrashingUsage(),
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
...@@ -137,7 +139,7 @@ void main() { ...@@ -137,7 +139,7 @@ void main() {
'FLUTTER_ANALYTICS_LOG_FILE': 'test', 'FLUTTER_ANALYTICS_LOG_FILE': 'test',
'FLUTTER_ROOT': '/', 'FLUTTER_ROOT': '/',
}), }),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
CrashReporter: () => WaitingCrashReporter(commandCompleter.future), CrashReporter: () => WaitingCrashReporter(commandCompleter.future),
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
...@@ -207,13 +209,109 @@ void main() { ...@@ -207,13 +209,109 @@ void main() {
'FLUTTER_ROOT': '/', 'FLUTTER_ROOT': '/',
} }
), ),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
UserMessages: () => CustomBugInstructions(), UserMessages: () => CustomBugInstructions(),
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
CrashReporter: () => WaitingCrashReporter(Future<void>.value()), CrashReporter: () => WaitingCrashReporter(Future<void>.value()),
HttpClientFactory: () => () => FakeHttpClient.any(), HttpClientFactory: () => () => FakeHttpClient.any(),
}); });
group('in directory without permission', () {
setUp(() {
bool inTestSetup = true;
fileSystem = MemoryFileSystem(opHandle: (String context, FileSystemOp operation) {
if (inTestSetup) {
// Allow all operations during test setup.
return;
}
const Set<FileSystemOp> disallowedOperations = <FileSystemOp>{
FileSystemOp.create,
FileSystemOp.delete,
FileSystemOp.copy,
FileSystemOp.write,
};
// Make current_directory not writable.
if (context.startsWith('/current_directory') && disallowedOperations.contains(operation)) {
throw FileSystemException('No permission, context = $context, operation = $operation');
}
});
final Directory currentDirectory = fileSystem.directory('/current_directory');
currentDirectory.createSync();
fileSystem.currentDirectory = currentDirectory;
inTestSetup = false;
});
testUsingContext('create local report in temporary directory', () async {
// Since crash reporting calls the doctor, which checks for the devtools
// version file in the cache, write a version file to the memory fs.
Cache.flutterRoot = '/path/to/flutter';
final Directory devtoolsDir = globals.fs.directory(
'${Cache.flutterRoot}/bin/cache/dart-sdk/bin/resources/devtools',
)..createSync(recursive: true);
devtoolsDir.childFile('version.json').writeAsStringSync(
'{"version": "1.2.3"}',
);
final Completer<void> completer = Completer<void>();
// runner.run() asynchronously calls the exit function set above, so we
// catch it in a zone.
unawaited(runZoned<Future<void>?>(
() {
unawaited(runner.run(
<String>['crash'],
() => <FlutterCommand>[
CrashingFlutterCommand(),
],
// This flutterVersion disables crash reporting.
flutterVersion: '[user-branch]/',
reportCrashes: true,
shutdownHooks: ShutdownHooks(),
));
return null;
},
onError: (Object error, StackTrace stack) { // ignore: deprecated_member_use
expect(firstExitCode, isNotNull);
expect(firstExitCode, isNot(0));
expect(error.toString(), 'Exception: test exit');
completer.complete();
},
));
await completer.future;
final String errorText = testLogger.errorText;
expect(
errorText,
containsIgnoringWhitespace('Oops; flutter has exited unexpectedly: "Exception: an exception % --".\n'),
);
final File log = globals.fs.systemTempDirectory.childFile('flutter_01.log');
final String logContents = log.readAsStringSync();
expect(logContents, contains(kCustomBugInstructions));
expect(logContents, contains('flutter crash'));
expect(logContents, contains('Exception: an exception % --'));
expect(logContents, contains('CrashingFlutterCommand.runCommand'));
expect(logContents, contains('[!] Flutter'));
final CrashDetails sentDetails = (globals.crashReporter! as WaitingCrashReporter)._details;
expect(sentDetails.command, 'flutter crash');
expect(sentDetails.error.toString(), 'Exception: an exception % --');
expect(sentDetails.stackTrace.toString(), contains('CrashingFlutterCommand.runCommand'));
expect(await sentDetails.doctorText.text, contains('[!] Flutter'));
}, overrides: <Type, Generator>{
Platform: () => FakePlatform(
environment: <String, String>{
'FLUTTER_ANALYTICS_LOG_FILE': 'test',
'FLUTTER_ROOT': '/',
}
),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
UserMessages: () => CustomBugInstructions(),
Artifacts: () => Artifacts.test(),
CrashReporter: () => WaitingCrashReporter(Future<void>.value()),
HttpClientFactory: () => () => FakeHttpClient.any(),
});
});
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment