@@ -79,6 +79,7 @@ void main(List<String> args) async {
7979 contentsGolden: options.outputContentsGolden,
8080 ndkStack: options.ndkStack,
8181 forceSurfaceProducerSurfaceTexture: options.forceSurfaceProducerSurfaceTexture,
82+ prefixLogsPerRun: options.prefixLogsPerRun,
8283 );
8384 onSigint.cancel ();
8485 exit (0 );
@@ -122,15 +123,22 @@ Future<void> _run({
122123 required String ? contentsGolden,
123124 required String ndkStack,
124125 required bool forceSurfaceProducerSurfaceTexture,
126+ required bool prefixLogsPerRun,
125127}) async {
126128 const ProcessManager pm = LocalProcessManager ();
127129 final String scenarioAppPath = join (outDir.path, 'scenario_app' );
130+
131+ // Due to the CI environment, the logs directory persists between runs and
132+ // even different builds. Because we're checking the output directory after
133+ // each run, we need a clean logs directory to avoid false positives.
134+ //
135+ // Only after the runner is done, we can move the logs to the final location.
136+ //
137+ // See [_copyFiles] below and https://github.com/flutter/flutter/issues/144402.
138+ final Directory finalLogsDir = logsDir..createSync (recursive: true );
139+ logsDir = Directory .systemTemp.createTempSync ('scenario_app_test_logs.' );
128140 final String logcatPath = join (logsDir.path, 'logcat.txt' );
129141
130- // TODO(matanlurey): Use screenshots/ sub-directory (https://github.com/flutter/flutter/issues/143604).
131- if (! logsDir.existsSync ()) {
132- logsDir.createSync (recursive: true );
133- }
134142 final String screenshotPath = logsDir.path;
135143 final String apkOutPath = join (scenarioAppPath, 'app' , 'outputs' , 'apk' );
136144 final File testApk = File (join (apkOutPath, 'androidTest' , 'debug' , 'app-debug-androidTest.apk' ));
@@ -388,6 +396,32 @@ Future<void> _run({
388396 await logcat.flush ();
389397 await logcat.close ();
390398 log ('wrote logcat to $logcatPath ' );
399+
400+ // Copy the logs to the final location.
401+ // Optionally prefix the logs with a run number and backend name.
402+ // See https://github.com/flutter/flutter/issues/144402.
403+ final StringBuffer prefix = StringBuffer ();
404+ if (prefixLogsPerRun) {
405+ final int rerunNumber = _getAndIncrementRerunNumber (finalLogsDir.path);
406+ prefix.write ('run_$rerunNumber .' );
407+ if (enableImpeller) {
408+ prefix.write ('impeller' );
409+ } else {
410+ prefix.write ('skia' );
411+ }
412+ if (enableImpeller) {
413+ prefix.write ('_${impellerBackend !.name }' );
414+ }
415+ if (forceSurfaceProducerSurfaceTexture) {
416+ prefix.write ('_force-st' );
417+ }
418+ prefix.write ('.' );
419+ }
420+ _copyFiles (
421+ source: logsDir,
422+ destination: finalLogsDir,
423+ prefix: prefix.toString (),
424+ );
391425 });
392426
393427 if (enableImpeller) {
@@ -448,7 +482,9 @@ Future<void> _run({
448482 await Future .wait (pendingComparisons);
449483 });
450484
451- if (contentsGolden != null ) {
485+ final bool allTestsRun = smokeTestFullPath == null ;
486+ final bool checkGoldens = contentsGolden != null ;
487+ if (allTestsRun && checkGoldens) {
452488 // Check the output here.
453489 await step ('Check output files...' , () async {
454490 // TODO(matanlurey): Resolve this in a better way. On CI this file always exists.
@@ -476,3 +512,32 @@ void _withTemporaryCwd(String path, void Function() callback) {
476512 Directory .current = originalCwd;
477513 }
478514}
515+
516+ /// Reads the file named `reruns.txt` in the logs directory and returns the number of reruns.
517+ ///
518+ /// If the file does not exist, it is created with the number 1 and that number is returned.
519+ int _getAndIncrementRerunNumber (String logsDir) {
520+ final File rerunFile = File (join (logsDir, 'reruns.txt' ));
521+ if (! rerunFile.existsSync ()) {
522+ rerunFile.writeAsStringSync ('1' );
523+ return 1 ;
524+ }
525+ final int rerunNumber = int .parse (rerunFile.readAsStringSync ()) + 1 ;
526+ rerunFile.writeAsStringSync (rerunNumber.toString ());
527+ return rerunNumber;
528+ }
529+
530+ /// Copies the contents of [source] to [destination] , optionally adding a [prefix] to the destination path.
531+ ///
532+ /// This function is used to copy the screenshots from the device to the logs directory.
533+ void _copyFiles ({
534+ required Directory source,
535+ required Directory destination,
536+ String prefix = '' ,
537+ }) {
538+ for (final FileSystemEntity entity in source.listSync ()) {
539+ if (entity is File ) {
540+ entity.copySync (join (destination.path, prefix + basename (entity.path)));
541+ }
542+ }
543+ }
0 commit comments