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:
Ben Konyi 2024-11-18 14:52:52 -05:00 committed by GitHub
parent 26d5ec3edd
commit c7d158d9f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 74 additions and 1 deletions

View File

@ -198,6 +198,11 @@ class LocalFileSystem extends local_fs.LocalFileSystem {
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 {
_tryToDeleteTemp();
for (final MapEntry<ProcessSignal, Object> signalToken in _signalTokens.entries) {
@ -210,6 +215,7 @@ class LocalFileSystem extends local_fs.LocalFileSystem {
final List<ProcessSignal> _fatalSignals;
void _tryToDeleteTemp() {
_disposed = true;
try {
if (_systemTemp?.existsSync() ?? false) {
_systemTemp?.deleteSync(recursive: true);

View File

@ -79,14 +79,24 @@ class IOSCoreDeviceControl {
try {
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('${output.path} was expected to exist, but it did not.');
_logger.printError('The process exited with code ${result.exitCode} and');
_logger.printError('Stdout:\n\n${result.stdout.trim()}\n');
_logger.printError('Stderr:\n\n${result.stderr.trim()}');
throw StateError('Expected the file ${output.path} to exist but it did not');
} else if (isToolPossiblyShutdown) {
return <Object?>[];
}
final String stringOutput = output.readAsStringSync();
_logger.printTrace(stringOutput);

View File

@ -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/io.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/ios/core_devices.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/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() {
late MemoryFileSystem fileSystem;
@ -1400,6 +1410,53 @@ invalid JSON
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 {
const String deviceControlOutput = '''
{