Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
[macOS] Refactor the flutter run macOS console output test (#114645)
Browse files Browse the repository at this point in the history
  • Loading branch information
loic-sharma committed Nov 8, 2022
1 parent 7020f59 commit 9797d5f
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 113 deletions.
116 changes: 3 additions & 113 deletions dev/devicelab/bin/tasks/run_release_test_macos.dart
Expand Up @@ -2,122 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
import 'package:flutter_devicelab/tasks/run_tests.dart';

/// Basic launch test for desktop operating systems.
void main() {
task(() async {
deviceOperatingSystem = DeviceOperatingSystem.macos;
final Device device = await devices.workingDevice;
// TODO(cbracken): https://github.com/flutter/flutter/issues/87508#issuecomment-1043753201
// Switch to dev/integration_tests/ui once we have CocoaPods working on M1 Macs.
final Directory appDir = dir(path.join(flutterDirectory.path, 'examples/hello_world'));
await inDirectory(appDir, () async {
final Completer<void> ready = Completer<void>();
final List<String> stdout = <String>[];
final List<String> stderr = <String>[];

print('run: starting...');
final List<String> options = <String>[
'--release',
'-d',
device.deviceId,
];
final Process run = await startFlutter(
'run',
options: options,
isBot: false,
);
int? runExitCode;
run.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
print('run:stdout: $line');
if (
!line.startsWith('Building flutter tool...') &&
!line.startsWith('Running "flutter pub get" in ui...') &&
!line.startsWith('Resolving dependencies...') &&
// Catch engine piped output from unrelated concurrent Flutter apps
!line.contains(RegExp(r'[A-Z]\/flutter \([0-9]+\):')) &&
// Empty lines could be due to the progress spinner breaking up.
line.length > 1
) {
stdout.add(line);
}
if (line.contains('Quit (terminate the application on the device).')) {
ready.complete();
}
});
run.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
print('run:stderr: $line');
stderr.add(line);
});
unawaited(run.exitCode.then<void>((int exitCode) { runExitCode = exitCode; }));
await Future.any<dynamic>(<Future<dynamic>>[ ready.future, run.exitCode ]);
if (runExitCode != null) {
throw 'Failed to run test app; runner unexpected exited, with exit code $runExitCode.';
}
run.stdin.write('q');

await run.exitCode;

_findNextMatcherInList(
stdout,
(String line) => line.startsWith('Launching lib/main.dart on ') && line.endsWith(' in release mode...'),
'Launching lib/main.dart on',
);

_findNextMatcherInList(
stdout,
(String line) => line.contains('Quit (terminate the application on the device).'),
'q Quit (terminate the application on the device)',
);

_findNextMatcherInList(
stdout,
(String line) => line == 'Application finished.',
'Application finished.',
);
});
return TaskResult.success(null);
});
}

void _findNextMatcherInList(
List<String> list,
bool Function(String testLine) matcher,
String errorMessageExpectedLine
) {
final List<String> copyOfListForErrorMessage = List<String>.from(list);

while (list.isNotEmpty) {
final String nextLine = list.first;
list.removeAt(0);

if (matcher(nextLine)) {
return;
}
}

throw '''
Did not find expected line
$errorMessageExpectedLine
in flutter run --release stdout
$copyOfListForErrorMessage
''';
deviceOperatingSystem = DeviceOperatingSystem.macos;
task(createMacOSRunReleaseTest());
}
158 changes: 158 additions & 0 deletions dev/devicelab/lib/tasks/run_tests.dart
@@ -0,0 +1,158 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import '../framework/devices.dart';
import '../framework/framework.dart';
import '../framework/task_result.dart';
import '../framework/utils.dart';

TaskFunction createMacOSRunReleaseTest() {
return DesktopRunOutputTest(
// TODO(cbracken): https://github.com/flutter/flutter/issues/87508#issuecomment-1043753201
// Switch to dev/integration_tests/ui once we have CocoaPods working on M1 Macs.
'${flutterDirectory.path}/examples/hello_world',
'lib/main.dart',
release: true,
);
}

class DesktopRunOutputTest extends RunOutputTask {
DesktopRunOutputTest(
super.testDirectory,
super.testTarget, {
required super.release,
}
);

@override
TaskResult verify(List<String> stdout, List<String> stderr) {
_findNextMatcherInList(
stdout,
(String line) => line.startsWith('Launching lib/main.dart on ') &&
line.endsWith(' in ${release ? 'release' : 'debug'} mode...'),
'Launching lib/main.dart on',
);

_findNextMatcherInList(
stdout,
(String line) => line.contains('Quit (terminate the application on the device).'),
'q Quit (terminate the application on the device)',
);

_findNextMatcherInList(
stdout,
(String line) => line == 'Application finished.',
'Application finished.',
);

return TaskResult.success(null);
}
}

/// Test that the output of `flutter run` is expected.
abstract class RunOutputTask {
RunOutputTask(
this.testDirectory,
this.testTarget, {
required this.release,
}
);

/// The directory where the app under test is defined.
final String testDirectory;
/// The main entry-point file of the application, as run on the device.
final String testTarget;
/// Whether to run the app in release mode.
final bool release;

Future<TaskResult> call() {
return inDirectory<TaskResult>(testDirectory, () async {
final Device device = await devices.workingDevice;
await device.unlock();
final String deviceId = device.deviceId;

final Completer<void> ready = Completer<void>();
final List<String> stdout = <String>[];
final List<String> stderr = <String>[];

final List<String> options = <String>[
testTarget,
'-d',
deviceId,
if (release) '--release',
];

final Process run = await startFlutter(
'run',
options: options,
isBot: false,
);

int? runExitCode;
run.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
print('run:stdout: $line');
stdout.add(line);
if (line.contains('Quit (terminate the application on the device).')) {
ready.complete();
}
});
run.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
print('run:stderr: $line');
stderr.add(line);
});
unawaited(run.exitCode.then<void>((int exitCode) { runExitCode = exitCode; }));
await Future.any<dynamic>(<Future<dynamic>>[ ready.future, run.exitCode ]);
if (runExitCode != null) {
throw 'Failed to run test app; runner unexpected exited, with exit code $runExitCode.';
}
run.stdin.write('q');

await run.exitCode;

return verify(stdout, stderr);
});
}

/// Verify the output of `flutter run`.
TaskResult verify(List<String> stdout, List<String> stderr) => throw UnimplementedError('verify is not implemented');

/// Helper that verifies a line in [list] matches [matcher].
/// The [list] is updated to contain the lines remaining after the match.
void _findNextMatcherInList(
List<String> list,
bool Function(String testLine) matcher,
String errorMessageExpectedLine
) {
final List<String> copyOfListForErrorMessage = List<String>.from(list);

while (list.isNotEmpty) {
final String nextLine = list.first;
list.removeAt(0);

if (matcher(nextLine)) {
return;
}
}

throw '''
Did not find expected line
$errorMessageExpectedLine
in flutter run ${release ? '--release' : ''} stdout
$copyOfListForErrorMessage
''';
}
}

0 comments on commit 9797d5f

Please sign in to comment.