Compile flutter tests via the sdk
This CL includes:
* A dart script that - given a flutter checkout - finds and compiles
tests to ensure we actually can.
* A shell script that checks out flutter, compiles the platform
(to cope with any changes to the kernel format) and runs the above
script.
Change-Id: I02806a375943361c3c111d9aa12a76d745391ca3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98140
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Alexander Thomas <athom@google.com>
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
diff --git a/pkg/vm/test/frontend_server_flutter.dart b/pkg/vm/test/frontend_server_flutter.dart
new file mode 100644
index 0000000..be1ee67
--- /dev/null
+++ b/pkg/vm/test/frontend_server_flutter.dart
@@ -0,0 +1,251 @@
+import 'dart:async' show StreamController;
+import 'dart:convert' show utf8, LineSplitter;
+import 'dart:io' show Directory, File, FileSystemEntity, IOSink, stdout;
+
+import 'package:kernel/ast.dart' show Component;
+import 'package:kernel/kernel.dart' show loadComponentFromBytes;
+import 'package:kernel/verifier.dart' show verifyComponent;
+
+import '../lib/frontend_server.dart';
+
+main(List<String> args) async {
+ String flutterDir;
+ String flutterPlatformDir;
+ for (String arg in args) {
+ if (arg.startsWith("--flutterDir=")) {
+ flutterDir = arg.substring(13);
+ } else if (arg.startsWith("--flutterPlatformDir=")) {
+ flutterPlatformDir = arg.substring(21);
+ }
+ }
+ if (flutterDir == null || !(new Directory(flutterDir).existsSync())) {
+ throw "Didn't get a valid flutter directory to work with.";
+ }
+ Directory flutterDirectory = new Directory(flutterDir);
+ List<FileSystemEntity> allFlutterFiles =
+ flutterDirectory.listSync(recursive: true, followLinks: false);
+ Directory flutterPlatformDirectory;
+ if (flutterPlatformDir == null) {
+ List<File> platformFiles = new List<File>.from(allFlutterFiles.where((f) =>
+ f.uri
+ .toString()
+ .endsWith("/flutter_patched_sdk/platform_strong.dill")));
+ if (platformFiles.length < 1) {
+ throw "Expected to find a flutter platform file but didn't.";
+ }
+ flutterPlatformDirectory = platformFiles.first.parent;
+ } else {
+ flutterPlatformDirectory = Directory(flutterPlatformDir);
+ }
+ if (!flutterPlatformDirectory.existsSync()) {
+ throw "$flutterPlatformDirectory doesn't exist.";
+ }
+ if (!new File.fromUri(
+ flutterPlatformDirectory.uri.resolve("platform_strong.dill"))
+ .existsSync()) {
+ throw "$flutterPlatformDirectory doesn't contain a "
+ "platform_strong.dill file.";
+ }
+ print("Using $flutterPlatformDirectory as platform directory.");
+ List<File> dotPackagesFiles = new List<File>.from(allFlutterFiles.where((f) =>
+ (f.uri.toString().contains("/examples/") ||
+ f.uri.toString().contains("/packages/")) &&
+ f.uri.toString().endsWith("/.packages")));
+
+ for (File dotPackage in dotPackagesFiles) {
+ Directory tempDir;
+ Directory systemTempDir = Directory.systemTemp;
+ tempDir = systemTempDir.createTempSync('foo bar');
+ try {
+ Directory testDir =
+ new Directory.fromUri(dotPackage.parent.uri.resolve("test/"));
+ if (!testDir.existsSync()) continue;
+ print("Go for $testDir");
+ await attemptStuff(
+ tempDir, flutterPlatformDirectory, dotPackage, testDir);
+ } finally {
+ tempDir.delete(recursive: true);
+ }
+ }
+}
+
+void attemptStuff(Directory tempDir, Directory flutterPlatformDirectory,
+ File dotPackage, Directory testDir) async {
+ File dillFile = new File('${tempDir.path}/dill.dill');
+ if (dillFile.existsSync()) {
+ throw "$dillFile already exists.";
+ }
+ List<int> platformData = new File.fromUri(
+ flutterPlatformDirectory.uri.resolve("platform_strong.dill"))
+ .readAsBytesSync();
+ final List<String> args = <String>[
+ '--sdk-root',
+ flutterPlatformDirectory.path,
+ '--incremental',
+ '--strong',
+ '--target=flutter',
+ '--packages',
+ dotPackage.path,
+ '--output-dill=${dillFile.path}',
+ // '--unsafe-package-serialization',
+ ];
+
+ List<File> testFiles = new List<File>.from(testDir
+ .listSync(recursive: true)
+ .where((f) => f.path.endsWith("_test.dart")));
+ if (testFiles.isEmpty) return;
+
+ Stopwatch stopwatch = new Stopwatch()..start();
+
+ final StreamController<List<int>> inputStreamController =
+ new StreamController<List<int>>();
+ final StreamController<List<int>> stdoutStreamController =
+ new StreamController<List<int>>();
+ final IOSink ioSink = new IOSink(stdoutStreamController.sink);
+ StreamController<Result> receivedResults = new StreamController<Result>();
+
+ final outputParser = new OutputParser(receivedResults);
+ stdoutStreamController.stream
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .listen(outputParser.listener);
+
+ Iterator<File> testFileIterator = testFiles.iterator;
+ testFileIterator.moveNext();
+
+ final Future<int> result =
+ starter(args, input: inputStreamController.stream, output: ioSink);
+ String shortTestPath =
+ testFileIterator.current.path.substring(testDir.path.length);
+ stdout.write(" => $shortTestPath");
+ Stopwatch stopwatch2 = new Stopwatch()..start();
+ inputStreamController
+ .add('compile ${testFileIterator.current.path}\n'.codeUnits);
+ int compilations = 0;
+ receivedResults.stream.listen((Result compiledResult) {
+ stdout.write(" --- done in ${stopwatch2.elapsedMilliseconds} ms\n");
+ stopwatch2.reset();
+ compiledResult.expectNoErrors();
+
+ List<int> resultBytes = dillFile.readAsBytesSync();
+ Component component = loadComponentFromBytes(platformData);
+ component = loadComponentFromBytes(resultBytes, component);
+ verifyComponent(component);
+ print(" => verified in ${stopwatch2.elapsedMilliseconds} ms.");
+ stopwatch2.reset();
+
+ inputStreamController.add('accept\n'.codeUnits);
+ inputStreamController.add('reset\n'.codeUnits);
+ compilations++;
+
+ if (!testFileIterator.moveNext()) {
+ inputStreamController.add('quit\n'.codeUnits);
+ return;
+ }
+ String shortTestPath =
+ testFileIterator.current.path.substring(testDir.path.length);
+ stdout.write(" => $shortTestPath");
+ inputStreamController.add('recompile ${testFileIterator.current.path} abc\n'
+ '${testFileIterator.current.uri}\n'
+ 'abc\n'
+ .codeUnits);
+ });
+
+ int resultDone = await result;
+ if (resultDone != 0) {
+ throw "Got $resultDone, expected 0";
+ }
+
+ inputStreamController.close();
+
+ print("Did $compilations compilations and verifications in "
+ "${stopwatch.elapsedMilliseconds} ms.");
+}
+
+// The below is copied from incremental_compiler_test.dart,
+// but with expect stuff replaced with ifs and throws
+// (expect can only be used in tests via the test framework).
+
+class OutputParser {
+ OutputParser(this._receivedResults);
+ bool expectSources = true;
+
+ StreamController<Result> _receivedResults;
+ List<String> _receivedSources;
+
+ String _boundaryKey;
+ bool _readingSources;
+
+ void listener(String s) {
+ if (_boundaryKey == null) {
+ const String RESULT_OUTPUT_SPACE = 'result ';
+ if (s.startsWith(RESULT_OUTPUT_SPACE)) {
+ _boundaryKey = s.substring(RESULT_OUTPUT_SPACE.length);
+ }
+ _readingSources = false;
+ _receivedSources?.clear();
+ return;
+ }
+
+ if (s.startsWith(_boundaryKey)) {
+ // First boundaryKey separates compiler output from list of sources
+ // (if we expect list of sources, which is indicated by receivedSources
+ // being not null)
+ if (expectSources && !_readingSources) {
+ _readingSources = true;
+ return;
+ }
+ // Second boundaryKey indicates end of frontend server response
+ expectSources = true;
+ _receivedResults.add(new Result(
+ s.length > _boundaryKey.length
+ ? s.substring(_boundaryKey.length + 1)
+ : null,
+ _receivedSources));
+ _boundaryKey = null;
+ } else {
+ if (_readingSources) {
+ if (_receivedSources == null) {
+ _receivedSources = <String>[];
+ }
+ _receivedSources.add(s);
+ }
+ }
+ }
+}
+
+class Result {
+ String status;
+ List<String> sources;
+
+ Result(this.status, this.sources);
+
+ void expectNoErrors({String filename}) {
+ CompilationResult result = new CompilationResult.parse(status);
+ if (result.errorsCount != 0) {
+ throw "Got ${result.errorsCount} errors. Expected 0.";
+ }
+ if (filename != null) {
+ if (result.filename != filename) {
+ throw "Got ${result.filename} errors. Expected $filename.";
+ }
+ }
+ }
+}
+
+class CompilationResult {
+ String filename;
+ int errorsCount;
+
+ CompilationResult.parse(String filenameAndErrorCount) {
+ if (filenameAndErrorCount == null) {
+ return;
+ }
+ int delim = filenameAndErrorCount.lastIndexOf(' ');
+ if (delim <= 0) {
+ throw "Expected $delim > 0...";
+ }
+ filename = filenameAndErrorCount.substring(0, delim);
+ errorsCount = int.parse(filenameAndErrorCount.substring(delim + 1).trim());
+ }
+}
diff --git a/tools/bots/flutter/compile_flutter.sh b/tools/bots/flutter/compile_flutter.sh
new file mode 100755
index 0000000..1c81fba
--- /dev/null
+++ b/tools/bots/flutter/compile_flutter.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Compile flutter tests with a locally built SDK.
+
+set -e
+
+checkout=$(pwd)
+dart=$checkout/out/ReleaseX64/dart-sdk/bin/dart
+sdk=$checkout/out/ReleaseX64/dart-sdk
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT HUP INT QUIT TERM PIPE
+pushd "$tmpdir"
+
+git clone -vv https://chromium.googlesource.com/external/github.com/flutter/flutter
+
+pushd flutter
+bin/flutter config --no-analytics
+pinned_dart_sdk=$(cat bin/cache/dart-sdk/revision)
+patch=$checkout/tools/patches/flutter-engine/${pinned_dart_sdk}.flutter.patch
+if [ -e "$patch" ]; then
+ git apply $patch
+fi
+bin/flutter update-packages
+popd
+
+# Directly in temp directory again.
+mkdir engine
+pushd engine
+git clone -vv --depth 1 https://chromium.googlesource.com/external/github.com/flutter/engine flutter
+mkdir third_party
+pushd third_party
+ln -s $checkout dart
+popd
+popd
+
+mkdir flutter_patched_sdk
+
+$checkout/tools/sdks/dart-sdk/bin/dart --packages=$checkout/.packages $checkout/pkg/front_end/tool/_fasta/compile_platform.dart dart:core --single-root-scheme=org-dartlang-sdk --single-root-base=$checkout/ org-dartlang-sdk:///sdk/lib/libraries.json vm_outline_strong.dill vm_platform_strong.dill vm_outline_strong.dill
+$checkout/tools/sdks/dart-sdk/bin/dart --packages=$checkout/.packages $checkout/pkg/front_end/tool/_fasta/compile_platform.dart --target=flutter dart:core --single-root-scheme=org-dartlang-sdk --single-root-base=engine org-dartlang-sdk:///flutter/lib/snapshot/libraries.json vm_outline_strong.dill flutter_patched_sdk/platform_strong.dill flutter_patched_sdk/outline_strong.dill
+
+popd
+
+$dart --enable-asserts pkg/vm/test/frontend_server_flutter.dart --flutterDir=$tmpdir/flutter --flutterPlatformDir=$tmpdir/flutter_patched_sdk
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index 78e10bc..c50e05b 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -1839,6 +1839,30 @@
]
},
{
+ "builders": [
+ "flutter-frontend"
+ ],
+ "meta": {
+ "description": "This configuration is used for running frontend tests on flutter code."
+ },
+ "steps": [
+ {
+ "name": "build dart",
+ "script": "tools/build.py",
+ "arguments": [
+ "--mode=release",
+ "--arch=x64",
+ "create_sdk"
+ ]
+ },
+ {
+
+ "name": "compile flutter tests",
+ "script": "tools/bots/flutter/compile_flutter.sh"
+ }
+ ]
+ },
+ {
"builders": ["fuzz-linux"],
"meta": {
"description": "This configuration is used for fuzz testing."