From a1a801a48dae48cf0958c3dc6ab8df57813a51d2 Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Wed, 31 Jan 2024 15:36:15 -0800 Subject: [PATCH] [flutter_tools] add debugging to ios/core_devices.dart (#142187) Add debugging for #141892 to detect when the temp file mysteriously disappears after running devicectl. --- .../lib/src/ios/core_devices.dart | 10 ++- .../general.shard/ios/core_devices_test.dart | 47 +++++++++- .../overall_experience_test.dart | 88 ++++++++++++------- .../transition_test_utils.dart | 4 +- 4 files changed, 115 insertions(+), 34 deletions(-) diff --git a/packages/flutter_tools/lib/src/ios/core_devices.dart b/packages/flutter_tools/lib/src/ios/core_devices.dart index 1e49950c37d..71a65265025 100644 --- a/packages/flutter_tools/lib/src/ios/core_devices.dart +++ b/packages/flutter_tools/lib/src/ios/core_devices.dart @@ -78,8 +78,16 @@ class IOSCoreDeviceControl { ]; try { - await _processUtils.run(command, throwOnError: true); + final RunResult result = await _processUtils.run(command, throwOnError: true); + if (!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'); + } final String stringOutput = output.readAsStringSync(); _logger.printTrace(stringOutput); diff --git a/packages/flutter_tools/test/general.shard/ios/core_devices_test.dart b/packages/flutter_tools/test/general.shard/ios/core_devices_test.dart index 1313b42134e..860947c0ca6 100644 --- a/packages/flutter_tools/test/general.shard/ios/core_devices_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/core_devices_test.dart @@ -87,7 +87,7 @@ void main() { setUp(() { logger = BufferLogger.test(); fakeProcessManager = FakeProcessManager.empty(); - // TODO(fujino): make this FakeProcessManager.empty() + // TODO(fujino): re-use fakeProcessManager xcode = Xcode.test(processManager: FakeProcessManager.any()); deviceControl = IOSCoreDeviceControl( logger: logger, @@ -1355,6 +1355,51 @@ invalid JSON expect(fakeProcessManager, hasNoRemainingExpectations); }); + testWithoutContext('Handles json file mysteriously disappearing', () async { + final Directory tempDir = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0'); + final File tempFile = tempDir.childFile('core_device_list.json'); + final List args = [ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ]; + fakeProcessManager.addCommand(FakeCommand( + command: args, + onRun: (_) { + // Simulate that this command deleted tempFile, did not create a + // new one, and exited successfully + expect(tempFile, exists); + tempFile.deleteSync(); + expect(tempFile, isNot(exists)); + }, + )); + + await expectLater( + () => deviceControl.getCoreDevices(), + throwsA( + isA().having( + (StateError e) => e.message, + 'message', + contains('Expected the file ${tempFile.path} to exist but it did not'), + ), + ), + ); + expect( + logger.errorText, + 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 = ''' { diff --git a/packages/flutter_tools/test/integration.shard/overall_experience_test.dart b/packages/flutter_tools/test/integration.shard/overall_experience_test.dart index 8cc4fff7e37..eddf735f973 100644 --- a/packages/flutter_tools/test/integration.shard/overall_experience_test.dart +++ b/packages/flutter_tools/test/integration.shard/overall_experience_test.dart @@ -66,15 +66,25 @@ void main() { tryToDelete(fileSystem.directory(tempDirectory)); } }, skip: Platform.isWindows); // [intended] Windows doesn't support sending signals so we don't care if it can store the PID. - testWithoutContext('flutter run handle SIGUSR1/2', () async { + + testWithoutContext('flutter run handle SIGUSR1/2 run', () async { final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync(); final String pidFile = fileSystem.path.join(tempDirectory, 'flutter.pid'); final String testDirectory = fileSystem.path.join(flutterRoot, 'dev', 'integration_tests', 'ui'); final String testScript = fileSystem.path.join('lib', 'commands.dart'); late int pid; + final List command = [ + 'run', + '-dflutter-tester', + '--report-ready', + '--pid-file', + pidFile, + '--no-devtools', + testScript, + ]; try { final ProcessTestResult result = await runFlutter( - ['run', '-dflutter-tester', '--report-ready', '--pid-file', pidFile, '--no-devtools', testScript], + command, testDirectory, [ Multiple(['Flutter run key commands.', 'called paint'], handler: (String line) { @@ -108,16 +118,22 @@ void main() { 'called main', 'called paint', ]); - expect(result.stdout.where((String line) => !line.startsWith('called ')), [ - // logs start after we receive the response to sending SIGUSR1 - 'Performing hot reload...'.padRight(progressMessageWidth), - startsWith('Reloaded 0 libraries in '), - 'Performing hot restart...'.padRight(progressMessageWidth), - startsWith('Restarted application in '), - '', // this newline is the one for after we hit "q" - 'Application finished.', - 'ready', - ]); + expect( + result.stdout.where((String line) => !line.startsWith('called ')), + [ + // logs start after we receive the response to sending SIGUSR1 + 'Performing hot reload...'.padRight(progressMessageWidth), + startsWith('Reloaded 0 libraries in '), + 'Performing hot restart...'.padRight(progressMessageWidth), + startsWith('Restarted application in '), + '', // this newline is the one for after we hit "q" + 'Application finished.', + 'ready', + ], + reason: 'stdout from command ${command.join(' ')} was unexpected, ' + 'full Stdout:\n\n${result.stdout.join('\n')}\n\n' + 'Stderr:\n\n${result.stderr.join('\n')}', + ); expect(result.exitCode, 0); } finally { tryToDelete(fileSystem.directory(tempDirectory)); @@ -128,9 +144,16 @@ void main() { final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync(); final String testDirectory = fileSystem.path.join(flutterRoot, 'dev', 'integration_tests', 'ui'); final String testScript = fileSystem.path.join('lib', 'commands.dart'); + final List command = [ + 'run', + '-dflutter-tester', + '--report-ready', + '--no-devtools', + testScript, + ]; try { final ProcessTestResult result = await runFlutter( - ['run', '-dflutter-tester', '--report-ready', '--no-devtools', testScript], + command, testDirectory, [ Multiple(['Flutter run key commands.', 'called main', 'called paint'], handler: (String line) { @@ -171,23 +194,28 @@ void main() { // debugPaintSizeEnabled = false: 'called paint', ]); - expect(result.stdout.where((String line) => !line.startsWith('called ')), [ - // logs start after we receive the response to hitting "r" - 'Performing hot reload...'.padRight(progressMessageWidth), - startsWith('Reloaded 0 libraries in '), - 'ready', - '', // this newline is the one for after we hit "R" - 'Performing hot restart...'.padRight(progressMessageWidth), - startsWith('Restarted application in '), - 'ready', - '', // newline for after we hit "p" the first time - 'ready', - '', // newline for after we hit "p" the second time - 'ready', - '', // this newline is the one for after we hit "q" - 'Application finished.', - 'ready', - ]); + expect( + result.stdout.where((String line) => !line.startsWith('called ')), [ + // logs start after we receive the response to hitting "r" + 'Performing hot reload...'.padRight(progressMessageWidth), + startsWith('Reloaded 0 libraries in '), + 'ready', + '', // this newline is the one for after we hit "R" + 'Performing hot restart...'.padRight(progressMessageWidth), + startsWith('Restarted application in '), + 'ready', + '', // newline for after we hit "p" the first time + 'ready', + '', // newline for after we hit "p" the second time + 'ready', + '', // this newline is the one for after we hit "q" + 'Application finished.', + 'ready', + ], + reason: 'stdout from command ${command.join(' ')} was unexpected, ' + 'full Stdout:\n\n${result.stdout.join('\n')}\n\n' + 'Stderr:\n\n${result.stderr.join('\n')}', + ); expect(result.exitCode, 0); } finally { tryToDelete(fileSystem.directory(tempDirectory)); diff --git a/packages/flutter_tools/test/integration.shard/transition_test_utils.dart b/packages/flutter_tools/test/integration.shard/transition_test_utils.dart index fa3a7f3b9e5..3b187f28667 100644 --- a/packages/flutter_tools/test/integration.shard/transition_test_utils.dart +++ b/packages/flutter_tools/test/integration.shard/transition_test_utils.dart @@ -175,8 +175,8 @@ Future runFlutter( bool debug = false, bool logging = true, Duration expectedMaxDuration = const Duration( - minutes: - 10), // must be less than test timeout of 15 minutes! See ../../dart_test.yaml. + minutes: 10, + ), // must be less than test timeout of 15 minutes! See ../../dart_test.yaml. }) async { const LocalPlatform platform = LocalPlatform(); final Stopwatch clock = Stopwatch()..start();