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;
|
||||
|
||||
// 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);
|
||||
|
@ -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);
|
||||
|
@ -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 = '''
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user