Fix flaky failure related to core_device_list.json not being found (#158946)
It's possible that the tool can be in the process of shutting down, which could result in the temp directory being deleted after the shutdown hooks run before we check if `output` exists. If this happens, we shouldn't crash but just carry on as if no devices were found as the tool will exit on its own. Fixes https://github.com/flutter/flutter/issues/141892 --------- Co-authored-by: Andrew Kolos <andrewrkolos@gmail.com>
This commit is contained in:
parent
26d5ec3edd
commit
c7d158d9f9
@ -198,6 +198,11 @@ class LocalFileSystem extends local_fs.LocalFileSystem {
|
|||||||
|
|
||||||
final ShutdownHooks shutdownHooks;
|
final ShutdownHooks shutdownHooks;
|
||||||
|
|
||||||
|
// Indicates that `dispose()` has been invoked or some shutdown hook has executed,
|
||||||
|
// resulting in the underlying temporary directory being cleaned up.
|
||||||
|
bool get disposed => _disposed;
|
||||||
|
bool _disposed = false;
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
_tryToDeleteTemp();
|
_tryToDeleteTemp();
|
||||||
for (final MapEntry<ProcessSignal, Object> signalToken in _signalTokens.entries) {
|
for (final MapEntry<ProcessSignal, Object> signalToken in _signalTokens.entries) {
|
||||||
@ -210,6 +215,7 @@ class LocalFileSystem extends local_fs.LocalFileSystem {
|
|||||||
final List<ProcessSignal> _fatalSignals;
|
final List<ProcessSignal> _fatalSignals;
|
||||||
|
|
||||||
void _tryToDeleteTemp() {
|
void _tryToDeleteTemp() {
|
||||||
|
_disposed = true;
|
||||||
try {
|
try {
|
||||||
if (_systemTemp?.existsSync() ?? false) {
|
if (_systemTemp?.existsSync() ?? false) {
|
||||||
_systemTemp?.deleteSync(recursive: true);
|
_systemTemp?.deleteSync(recursive: true);
|
||||||
|
@ -79,14 +79,24 @@ class IOSCoreDeviceControl {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final RunResult result = await _processUtils.run(command, throwOnError: true);
|
final RunResult result = await _processUtils.run(command, throwOnError: true);
|
||||||
|
final bool isToolPossiblyShutdown = _fileSystem is LocalFileSystem && _fileSystem.disposed;
|
||||||
|
|
||||||
if (!output.existsSync()) {
|
// It's possible that the tool is in the process of shutting down, which
|
||||||
|
// could result in the temp directory being deleted after the shutdown hooks run
|
||||||
|
// before we check if `output` exists. If this happens, we shouldn't crash
|
||||||
|
// but just carry on as if no devices were found as the tool will exit on
|
||||||
|
// its own.
|
||||||
|
//
|
||||||
|
// See https://github.com/flutter/flutter/issues/141892 for details.
|
||||||
|
if (!isToolPossiblyShutdown && !output.existsSync()) {
|
||||||
_logger.printError('After running the command ${command.join(' ')} the file');
|
_logger.printError('After running the command ${command.join(' ')} the file');
|
||||||
_logger.printError('${output.path} was expected to exist, but it did not.');
|
_logger.printError('${output.path} was expected to exist, but it did not.');
|
||||||
_logger.printError('The process exited with code ${result.exitCode} and');
|
_logger.printError('The process exited with code ${result.exitCode} and');
|
||||||
_logger.printError('Stdout:\n\n${result.stdout.trim()}\n');
|
_logger.printError('Stdout:\n\n${result.stdout.trim()}\n');
|
||||||
_logger.printError('Stderr:\n\n${result.stderr.trim()}');
|
_logger.printError('Stderr:\n\n${result.stderr.trim()}');
|
||||||
throw StateError('Expected the file ${output.path} to exist but it did not');
|
throw StateError('Expected the file ${output.path} to exist but it did not');
|
||||||
|
} else if (isToolPossiblyShutdown) {
|
||||||
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
final String stringOutput = output.readAsStringSync();
|
final String stringOutput = output.readAsStringSync();
|
||||||
_logger.printTrace(stringOutput);
|
_logger.printTrace(stringOutput);
|
||||||
|
@ -7,6 +7,7 @@ import 'package:file_testing/file_testing.dart';
|
|||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/io.dart';
|
import 'package:flutter_tools/src/base/io.dart';
|
||||||
import 'package:flutter_tools/src/base/logger.dart';
|
import 'package:flutter_tools/src/base/logger.dart';
|
||||||
|
import 'package:flutter_tools/src/base/signals.dart';
|
||||||
import 'package:flutter_tools/src/base/version.dart';
|
import 'package:flutter_tools/src/base/version.dart';
|
||||||
import 'package:flutter_tools/src/ios/core_devices.dart';
|
import 'package:flutter_tools/src/ios/core_devices.dart';
|
||||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||||
@ -15,6 +16,15 @@ import 'package:flutter_tools/src/macos/xcode.dart';
|
|||||||
import '../../src/common.dart';
|
import '../../src/common.dart';
|
||||||
import '../../src/fake_process_manager.dart';
|
import '../../src/fake_process_manager.dart';
|
||||||
|
|
||||||
|
class LocalFileSystemFake extends LocalFileSystem {
|
||||||
|
LocalFileSystemFake.test({required super.signals}) : super.test();
|
||||||
|
|
||||||
|
MemoryFileSystem memoryFileSystem = MemoryFileSystem.test();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Directory get systemTempDirectory => memoryFileSystem.systemTempDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late MemoryFileSystem fileSystem;
|
late MemoryFileSystem fileSystem;
|
||||||
|
|
||||||
@ -1400,6 +1410,53 @@ invalid JSON
|
|||||||
expect(fakeProcessManager, hasNoRemainingExpectations);
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWithoutContext('Handles file system disposal', () async {
|
||||||
|
final LocalFileSystem localFs = LocalFileSystemFake.test(signals: Signals.test());
|
||||||
|
deviceControl = IOSCoreDeviceControl(
|
||||||
|
logger: logger,
|
||||||
|
processManager: fakeProcessManager,
|
||||||
|
xcode: xcode,
|
||||||
|
fileSystem: localFs,
|
||||||
|
);
|
||||||
|
final Directory tempDir = localFs.systemTempDirectory
|
||||||
|
.childDirectory('core_devices.rand0');
|
||||||
|
final File tempFile = tempDir.childFile('core_device_list.json');
|
||||||
|
final List<String> args = <String>[
|
||||||
|
'xcrun',
|
||||||
|
'devicectl',
|
||||||
|
'list',
|
||||||
|
'devices',
|
||||||
|
'--timeout',
|
||||||
|
'5',
|
||||||
|
'--json-output',
|
||||||
|
tempFile.path,
|
||||||
|
];
|
||||||
|
fakeProcessManager.addCommand(FakeCommand(
|
||||||
|
command: args,
|
||||||
|
onRun: (_) {
|
||||||
|
// Simulate that the tool started shutting down and disposed the
|
||||||
|
// file system, causing the temp directory to be deleted before
|
||||||
|
// this program invocation returns a result.
|
||||||
|
localFs.dispose();
|
||||||
|
expect(localFs.disposed, true);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
final List<IOSCoreDevice> coreDevices = await deviceControl.getCoreDevices();
|
||||||
|
expect(coreDevices, isEmpty);
|
||||||
|
expect(
|
||||||
|
logger.errorText,
|
||||||
|
isNot(
|
||||||
|
contains('After running the command xcrun devicectl list devices '
|
||||||
|
'--timeout 5 --json-output ${tempFile.path} the file\n'
|
||||||
|
'${tempFile.path} was expected to exist, but it did not',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
testWithoutContext('No devices', () async {
|
testWithoutContext('No devices', () async {
|
||||||
const String deviceControlOutput = '''
|
const String deviceControlOutput = '''
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user