Skip to content

Commit

Permalink
Re-enable scenario tests on Android (flutter#33574)
Browse files Browse the repository at this point in the history
  • Loading branch information
Emmanuel Garcia authored Jun 3, 2022
1 parent 84c3012 commit ada245a
Show file tree
Hide file tree
Showing 58 changed files with 722 additions and 329 deletions.
12 changes: 10 additions & 2 deletions .ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,24 @@ targets:
timeout: 60

- name: Linux Android Emulator Tests
bringup: true # Recipe issue https://github.com/flutter/flutter/issues/86427
recipe: engine/scenarios
enabled_branches:
- main
- master
properties:
dependencies: >-
[
{"dependency": "android_virtual_device", "version": "31"}
{"dependency": "android_virtual_device", "version": "31"},
{"dependency": "goldctl"}
]
upload_packages: "true"
clobber: "true"
timeout: 60
runIf:
- DEPS
- .ci.yaml
- testing/**
- shell/platforms/android/**

- name: Linux Benchmarks
enabled_branches:
Expand Down
3 changes: 2 additions & 1 deletion shell/platform/android/android_context_gl_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ TEST(AndroidSurfaceGL, CreateSnapshopSurfaceWhenOnscreenSurfaceIsNull) {
EXPECT_NE(android_surface->GetOnscreenSurface(), nullptr);
}

TEST(AndroidContextGl, MSAAx4) {
// TODO(https://github.com/flutter/flutter/issues/104463): Flaky test.
TEST(AndroidContextGl, DISABLED_MSAAx4) {
GrMockOptions main_context_options;
sk_sp<GrDirectContext> main_context =
GrDirectContext::MakeMock(&main_context_options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ void AndroidExternalViewEmbedder::SubmitFrame(

for (size_t i = 0; i < current_frame_view_count; i++) {
int64_t view_id = composition_order_[i];
if (picture_recorders_.at(view_id)->getRecordingCanvas() == nullptr) {
continue;
}

sk_sp<SkPicture> picture =
picture_recorders_.at(view_id)->finishRecordingAsPicture();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ private static class Connection {
}

synchronized void writeFile(String name, byte[] fileContent) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(name.length() + fileContent.length + 4);
final ByteBuffer buffer = ByteBuffer.allocate(name.length() + fileContent.length + 8);
// See ScreenshotBlobTransformer#bind in screenshot_transformer.dart for consumer side.
buffer.putInt(name.length());
buffer.putInt(fileContent.length);
buffer.put(name.getBytes());
buffer.put(fileContent);
final byte[] bytes = buffer.array();
Expand Down Expand Up @@ -118,6 +120,9 @@ public static void capture(@NonNull TestableFlutterActivity activity, @NonNull S

final Bitmap bitmap =
InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot();
if (bitmap == null) {
throw new RuntimeException("failed to capture screenshot");
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
ScreenshotUtil.writeFile(captureName, out.toByteArray());
Expand Down
3 changes: 0 additions & 3 deletions testing/scenario_app/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize"
android:theme="@style/FullScreenScreenshot"
android:exported="true">
<intent-filter>
<action android:name="com.google.intent.action.TEST_LOOP" />
Expand All @@ -32,7 +31,6 @@
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize"
android:theme="@style/FullScreenScreenshot"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -45,7 +43,6 @@
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize"
android:theme="@style/FullScreenScreenshot"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.loader.FlutterLoader;
Expand All @@ -35,6 +39,8 @@ public abstract class TestActivity extends TestableFlutterActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hideSystemBars(getWindow());

final Intent launchIntent = getIntent();
if ("com.google.intent.action.TEST_LOOP".equals(launchIntent.getAction())) {
if (Build.VERSION.SDK_INT > 22) {
Expand Down Expand Up @@ -158,4 +164,12 @@ public void run() {
}
});
}

private static void hideSystemBars(Window window) {
final WindowInsetsControllerCompat insetController =
WindowCompat.getInsetsController(window, window.getDecorView());
insetController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
insetController.hide(WindowInsetsCompat.Type.systemBars());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,4 @@
<item name="colorAccent">@color/colorAccent</item>
</style>

<style name="FullScreenScreenshot">
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>

</resources>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
108 changes: 93 additions & 15 deletions testing/scenario_app/bin/android_integration_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
// found in the LICENSE file.

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

import 'package:args/args.dart';
import 'package:path/path.dart';
import 'package:process/process.dart';
import 'package:skia_gold_client/skia_gold_client.dart';

import 'utils/logs.dart';
import 'utils/process_manager_extension.dart';
import 'utils/screenshot_transformer.dart';

const int tcpPort = 3001;

Expand All @@ -34,9 +35,12 @@ void main(List<String> args) async {
panic(<String>['cannot find adb: $adb', 'make sure to run gclient sync']);
}

final String apkOut = join(outDir.path, 'scenario_app', 'app', 'outputs', 'apk');
final File testApk = File(join(apkOut, 'androidTest', 'debug', 'app-debug-androidTest.apk'));
final File appApk = File(join(apkOut, 'debug', 'app-debug.apk'));
final String scenarioAppPath = join(outDir.path, 'scenario_app');
final String logcatPath = join(scenarioAppPath, 'logcat.txt');
final String screenshotPath = join(scenarioAppPath, 'screenshots');
final String apkOutPath = join(scenarioAppPath, 'app', 'outputs', 'apk');
final File testApk = File(join(apkOutPath, 'androidTest', 'debug', 'app-debug-androidTest.apk'));
final File appApk = File(join(apkOutPath, 'debug', 'app-debug.apk'));

if (!testApk.existsSync()) {
panic(<String>['test apk does not exist: ${testApk.path}', 'make sure to build the selected engine variant']);
Expand All @@ -50,30 +54,97 @@ void main(List<String> args) async {
// This allows the test process to start a connection with the host, and write the bytes
// for the screenshots.
// On LUCI, the host uploads the screenshots to Skia Gold.
SkiaGoldClient? skiaGoldClient;
late ServerSocket server;
final List<Future<void>> pendingComparisons = <Future<void>>[];
await step('Starting server...', () async {
server = await ServerSocket.bind(InternetAddress.anyIPv4, tcpPort);
stdout.writeln('listening on host ${server.address.address}:${server.port}');
server.listen((Socket client) {
stdout.writeln('client connected ${client.remoteAddress.address}:${client.remotePort}');

client.listen((Uint8List data) {
final int fnameLen = data.buffer.asByteData().getInt32(0);
final String fileName = utf8.decode(data.buffer.asUint8List(4, fnameLen));
final Uint8List fileContent = data.buffer.asUint8List(4 + fnameLen);
client.transform(const ScreenshotBlobTransformer()).listen((Screenshot screenshot) {
final String fileName = screenshot.filename;
final Uint8List fileContent = screenshot.fileContent;
log('host received ${fileContent.lengthInBytes} bytes for screenshot `$fileName`');
});
assert(skiaGoldClient != null, 'expected Skia Gold client');
late File goldenFile;
try {
goldenFile = File(join(screenshotPath, fileName))..writeAsBytesSync(fileContent, flush: true);
} on FileSystemException catch (err) {
panic(<String>['failed to create screenshot $fileName: ${err.toString()}']);
}
log('wrote ${goldenFile.absolute.path}');
if (isSkiaGoldClientAvailable) {
final Future<void> comparison = skiaGoldClient!
.addImg(fileName, goldenFile, screenshotSize: fileContent.lengthInBytes)
.catchError((dynamic err) {
panic(<String>['skia gold comparison failed: ${err.toString()}']);
});
pendingComparisons.add(comparison);
}
},
onError: (dynamic err) {
panic(<String>['error while receiving bytes: ${err.toString()}']);
},
cancelOnError: true);
});
});

late Process logcatProcess;
final StringBuffer logcat = StringBuffer();
final IOSink logcat = File(logcatPath).openWrite();
try {
await step('Creating screenshot directory...', () async {
Directory(screenshotPath).createSync(recursive: true);
});

await step('Starting logcat...', () async {
logcatProcess = await pm.start(<String>[adb.path, 'logcat', '*:E', '-T', '1']);
final int exitCode = await pm.runAndForward(<String>[adb.path, 'logcat', '-c']);
if (exitCode != 0) {
panic(<String>['could not clear logs']);
}
logcatProcess = await pm.start(<String>[adb.path, 'logcat', '-T', '1']);
unawaited(pipeProcessStreams(logcatProcess, out: logcat));
});

await step('Configuring emulator...', () async {
final int exitCode = await pm.runAndForward(<String>[
adb.path,
'shell',
'settings',
'put',
'secure',
'immersive_mode_confirmations',
'confirmed',
]);
if (exitCode != 0) {
panic(<String>['could not configure emulator']);
}
});

await step('Get API level of connected device...', () async {
final ProcessResult apiLevelProcessResult = await pm.run(<String>[adb.path, 'shell', 'getprop', 'ro.build.version.sdk']);
if (apiLevelProcessResult.exitCode != 0) {
panic(<String>['could not get API level of the connected device']);
}
final String connectedDeviceAPILevel = (apiLevelProcessResult.stdout as String).trim();
log('using API level $connectedDeviceAPILevel');
skiaGoldClient = SkiaGoldClient(
outDir,
dimensions: <String, String>{
'AndroidAPILevel': connectedDeviceAPILevel,
},
);
});

await step('Skia Gold auth...', () async {
if (isSkiaGoldClientAvailable) {
await skiaGoldClient!.auth();
log('skia gold client is available');
} else {
log('skia gold client is unavailable');
}
});

await step('Reverse port...', () async {
final int exitCode = await pm.runAndForward(<String>[adb.path, 'reverse', 'tcp:3000', 'tcp:$tcpPort']);
if (exitCode != 0) {
Expand Down Expand Up @@ -136,11 +207,18 @@ void main(List<String> args) async {
});

await step('Killing logcat process...', () async {
logcatProcess.kill();
final bool delivered = logcatProcess.kill(ProcessSignal.sigkill);
assert(delivered);
});

await step('Dumping logcat (Errors only)...', () async {
stdout.write(logcat);
await step('Wait for Skia gold comparisons...', () async {
await Future.wait(pendingComparisons);
});

await step('Flush logcat...', () async {
await logcat.flush();
});

exit(0);
}
}
29 changes: 22 additions & 7 deletions testing/scenario_app/bin/utils/process_manager_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,46 @@ import 'dart:io';

import 'package:process/process.dart';

/// Pipes the [process] streams and writes them to [out].
Future<int> pipeProcessStreams(Process process, {StringSink? out}) async {
/// Pipes the [process] streams and writes them to [out] sink.
/// If [out] is null, then the current [Process.stdout] is used as the sink.
/// If [includePrefix] is true, then the prefix `[stdout]` or `[stderr]` is
/// added before writting to the [out] sink.
Future<int> pipeProcessStreams(
Process process, {
StringSink? out,
bool includePrefix = true,
}) async {
out ??= stdout;
final Completer<void> stdoutCompleter = Completer<void>();
final StreamSubscription<String> stdoutSub = process.stdout
.transform(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
out!.writeln('[stdout] $line');
if (includePrefix) {
out!.writeln('[stdout] $line');
} else {
out!.writeln(line);
}
}, onDone: stdoutCompleter.complete);

final Completer<void> stderrCompleter = Completer<void>();
final StreamSubscription<String> stderrSub = process.stderr
.transform(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
out!.writeln('[stderr] $line');
if (includePrefix) {
out!.writeln('[stderr] $line');
} else {
out!.writeln(line);
}
}, onDone: stderrCompleter.complete);

final int exitCode = await process.exitCode;
await stderrSub.cancel();
await stdoutSub.cancel();

await stdoutCompleter.future;
await stderrCompleter.future;

stderrSub.cancel();
stdoutSub.cancel();
return exitCode;
}

Expand Down
Loading

0 comments on commit ada245a

Please sign in to comment.