[CFE] Add test for running old dills

This CL:
* adds a test that runs the VM with old dills (compilations of dart2js
  with old checkouts) with the --compile_all argument (1).
* adds a number of old dills (for binary version 25, 27, 28, 29)
  (by mistake binary version 26 sort of didn't really happen)
* adds a PRESUBMIT check in kernel that verifies that we have an
  "old dill" for the current binary version, so one don't bump the
  version without creating a new dill.

(1): It uses --compile_all to force the VM to "read and understand"
     everything in the dill file. The hope being that we try out
     all/most language constructs and thus verifies that they can be
     read by the VM.

Old "old dill" files should be removed once the VM stops supporting that
version. That is a manual process, but a test should complain that the VM
cannot read the old "old dill"(s) once/if that happens.

This should (help) detect errors such as the one recently where a merge
error caused a single "if >= 28" (that should have been "if >= 29" and
thus stopped the VM from reading dills from version 28) to slip through
and require a lot of debugging a few days later.

Change-Id: Id79e16c7ad896c0ccc4e181465b05b67822ac31a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/113698
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/test/old_dill_test.dart b/pkg/front_end/test/old_dill_test.dart
new file mode 100644
index 0000000..df757c40
--- /dev/null
+++ b/pkg/front_end/test/old_dill_test.dart
@@ -0,0 +1,128 @@
+// 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.
+
+import 'dart:async' show Future;
+
+import 'dart:io';
+
+import 'package:kernel/binary/tag.dart' show Tag;
+
+import 'package:testing/testing.dart'
+    show ChainContext, Result, Step, TestDescription, Chain, runMe;
+
+Future<Null> main([List<String> arguments = const []]) async {
+  if (arguments.length == 1 && arguments[0] == "--generate") {
+    print("Should now generate a dill of dart2js.");
+    await generateDill();
+    return null;
+  } else if (arguments.length == 1 && arguments[0] == "--checkDill") {
+    await checkDill();
+    return null;
+  }
+  await runMe(arguments, createContext, "../testing.json");
+  await checkDill();
+}
+
+String get dartVm => Platform.resolvedExecutable;
+
+Uri generateOutputUri(int binaryVersion, int compileNumber) {
+  return Uri.base.resolve("pkg/front_end/testcases/old_dills/"
+      "dart2js"
+      ".version.$binaryVersion"
+      ".compile.$compileNumber"
+      ".dill");
+}
+
+verifyNotUsingCheckedInDart() {
+  String vm = dartVm.replaceAll(r"\", "/");
+  if (vm.contains("tools/sdks/dart-sdk/bin/dart")) {
+    throw "Running with checked-in VM which is not supported";
+  }
+}
+
+Future<Null> checkDill() async {
+  Uri uri = generateOutputUri(Tag.BinaryFormatVersion, 1);
+  if (!new File.fromUri(uri).existsSync()) {
+    print("File $uri doesn't exist. Generate running script");
+    print("${Platform.script.toFilePath()} --generate");
+    exit(1);
+  }
+}
+
+Future<Null> generateDill() async {
+  Uri fastaCompile = Uri.base.resolve("pkg/front_end/tool/_fasta/compile.dart");
+  if (!new File.fromUri(fastaCompile).existsSync()) {
+    throw "compile.dart from fasta tools couldn't be found";
+  }
+
+  Uri dart2js = Uri.base.resolve("pkg/compiler/bin/dart2js.dart");
+  if (!new File.fromUri(dart2js).existsSync()) {
+    throw "dart2js couldn't be found";
+  }
+
+  int compileNumber = 0;
+  Uri output;
+  do {
+    compileNumber++;
+    output = generateOutputUri(Tag.BinaryFormatVersion, compileNumber);
+  } while (new File.fromUri(output).existsSync());
+
+  ProcessResult result = await Process.run(
+      dartVm,
+      [
+        fastaCompile.toFilePath(),
+        "sdkroot:/pkg/compiler/bin/dart2js.dart",
+        "-o",
+        output.toFilePath(),
+        "--target=vm",
+        "--single-root-base=${Uri.base.toFilePath()}",
+        "--single-root-scheme=sdkroot",
+      ],
+      workingDirectory: Uri.base.toFilePath());
+  if (result.exitCode != 0) {
+    print("stdout: ${result.stdout}");
+    print("stderr: ${result.stderr}");
+    print("Exit code: ${result.exitCode}");
+    throw "Got exit code ${result.exitCode}";
+  } else {
+    print("File generated.");
+  }
+}
+
+Future<Context> createContext(
+    Chain suite, Map<String, String> environment) async {
+  return new Context();
+}
+
+class Context extends ChainContext {
+  final List<Step> steps = const <Step>[
+    const RunDill(),
+  ];
+}
+
+class RunDill extends Step<TestDescription, TestDescription, Context> {
+  const RunDill();
+
+  String get name => "RunDill";
+
+  Future<Result<TestDescription>> run(
+      TestDescription description, Context context) async {
+    verifyNotUsingCheckedInDart();
+    ProcessResult result = await Process.run(
+        dartVm,
+        [
+          "--compile_all",
+          description.uri.toFilePath(),
+          "-h",
+        ],
+        workingDirectory: Uri.base.toFilePath());
+    print("stdout: ${result.stdout}");
+    print("stderr: ${result.stderr}");
+    print("Exit code: ${result.exitCode}");
+    if (result.exitCode != 0) {
+      return fail(description, "Got exit code ${result.exitCode}");
+    }
+    return pass(description);
+  }
+}
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index 794a2a1..df8d7bd 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -108,6 +108,7 @@
 dictionary
 differs
 dillfile
+dills
 dirname
 disagree
 disconnect
@@ -316,6 +317,8 @@
 scans
 screen
 scripts
+sdkroot
+sdks
 secondary
 seconds
 segment
diff --git a/pkg/front_end/testcases/old_dills/dart2js.version.25.compile.1.dill b/pkg/front_end/testcases/old_dills/dart2js.version.25.compile.1.dill
new file mode 100644
index 0000000..deb7a1a
--- /dev/null
+++ b/pkg/front_end/testcases/old_dills/dart2js.version.25.compile.1.dill
Binary files differ
diff --git a/pkg/front_end/testcases/old_dills/dart2js.version.27.compile.1.dill b/pkg/front_end/testcases/old_dills/dart2js.version.27.compile.1.dill
new file mode 100644
index 0000000..40f0b90
--- /dev/null
+++ b/pkg/front_end/testcases/old_dills/dart2js.version.27.compile.1.dill
Binary files differ
diff --git a/pkg/front_end/testcases/old_dills/dart2js.version.28.compile.1.dill b/pkg/front_end/testcases/old_dills/dart2js.version.28.compile.1.dill
new file mode 100644
index 0000000..606bbf1
--- /dev/null
+++ b/pkg/front_end/testcases/old_dills/dart2js.version.28.compile.1.dill
Binary files differ
diff --git a/pkg/front_end/testcases/old_dills/dart2js.version.29.compile.1.dill b/pkg/front_end/testcases/old_dills/dart2js.version.29.compile.1.dill
new file mode 100644
index 0000000..060ab45
--- /dev/null
+++ b/pkg/front_end/testcases/old_dills/dart2js.version.29.compile.1.dill
Binary files differ
diff --git a/pkg/front_end/testcases/old_dills/old_dills.status b/pkg/front_end/testcases/old_dills/old_dills.status
new file mode 100644
index 0000000..e22cfe5
--- /dev/null
+++ b/pkg/front_end/testcases/old_dills/old_dills.status
@@ -0,0 +1,3 @@
+# 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.md file.
diff --git a/pkg/front_end/testing.json b/pkg/front_end/testing.json
index 3f10468..9ee45ec 100644
--- a/pkg/front_end/testing.json
+++ b/pkg/front_end/testing.json
@@ -221,6 +221,19 @@
     },
 
     {
+      "name": "old_dill_test",
+      "kind": "Chain",
+      "source": "test/old_dill_test.dart",
+      "path": "testcases/old_dills/",
+      "status": "testcases/old_dills/old_dills.status",
+      "pattern": [
+        ".*\\.dill$"
+      ],
+      "exclude": [
+      ]
+    },
+
+    {
       "name": "lint_test",
       "kind": "Chain",
       "source": "test/lint_test.dart",
diff --git a/pkg/kernel/PRESUBMIT.py b/pkg/kernel/PRESUBMIT.py
new file mode 100644
index 0000000..9d898e4
--- /dev/null
+++ b/pkg/kernel/PRESUBMIT.py
@@ -0,0 +1,61 @@
+# 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.
+"""Front-end specific presubmit script.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into gcl.
+"""
+
+import imp
+import os.path
+import subprocess
+
+
+def runSmokeTest(input_api, output_api):
+    hasChangedFiles = False
+    for git_file in input_api.AffectedTextFiles():
+        filename = git_file.AbsoluteLocalPath()
+        if filename.endswith(".dart"):
+            hasChangedFiles = True
+            break
+
+    if hasChangedFiles:
+        local_root = input_api.change.RepositoryRoot()
+        utils = imp.load_source('utils',
+                        os.path.join(local_root, 'tools', 'utils.py'))
+        dart = os.path.join(utils.CheckedInSdkPath(), 'bin', 'dart')
+        smoke_test = os.path.join(local_root, 'pkg', 'kernel', 'tool',
+                                  'smoke_test_quick.dart')
+
+        windows = utils.GuessOS() == 'win32'
+        if windows:
+            dart += '.exe'
+
+        if not os.path.isfile(dart):
+            print('WARNING: dart not found: %s' % dart)
+            return []
+
+        if not os.path.isfile(smoke_test):
+            print('WARNING: kernel smoke test not found: %s' % smoke_test)
+            return []
+
+        args = [dart, smoke_test]
+        process = subprocess.Popen(
+            args, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+        outs, _ = process.communicate()
+
+        if process.returncode != 0:
+            return [output_api.PresubmitError(
+                    'Kernel smoke test failure(s):',
+                    long_text=outs)]
+
+    return []
+
+
+def CheckChangeOnCommit(input_api, output_api):
+    return runSmokeTest(input_api, output_api)
+
+
+def CheckChangeOnUpload(input_api, output_api):
+    return runSmokeTest(input_api, output_api)
diff --git a/pkg/kernel/tool/smoke_test_quick.dart b/pkg/kernel/tool/smoke_test_quick.dart
new file mode 100644
index 0000000..2065aea
--- /dev/null
+++ b/pkg/kernel/tool/smoke_test_quick.dart
@@ -0,0 +1,62 @@
+// 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.
+
+import 'dart:io';
+
+final String repoDir = _computeRepoDir();
+
+String get dartVm => Platform.executable;
+
+main(List<String> args) async {
+  Stopwatch stopwatch = new Stopwatch()..start();
+  List<Future> futures = new List<Future>();
+  futures.add(run("pkg/front_end/test/old_dill_test.dart", ["--checkDill"]));
+  await Future.wait(futures);
+  print("\n-----------------------\n");
+  print("Done with exitcode $exitCode in ${stopwatch.elapsedMilliseconds} ms");
+}
+
+Future<void> run(String script, List<String> scriptArguments,
+    {bool filter: true}) async {
+  List<String> arguments = [];
+  arguments.add("$script");
+  arguments.addAll(scriptArguments);
+
+  Stopwatch stopwatch = new Stopwatch()..start();
+  ProcessResult result =
+      await Process.run(dartVm, arguments, workingDirectory: repoDir);
+  String runWhat = "${dartVm} ${arguments.join(' ')}";
+  if (result.exitCode != 0) {
+    exitCode = result.exitCode;
+    print("-----");
+    print("Running: $runWhat: "
+        "Failed with exit code ${result.exitCode} "
+        "in ${stopwatch.elapsedMilliseconds} ms.");
+    String stdout = result.stdout.toString();
+    if (filter) {
+      List<String> lines = stdout.split("\n");
+      int lastIgnored = -1;
+      for (int i = 0; i < lines.length; i++) {
+        if (lines[i].startsWith("[ ")) lastIgnored = i;
+      }
+      lines.removeRange(0, lastIgnored + 1);
+      stdout = lines.join("\n");
+    }
+    stdout = stdout.trim();
+    if (stdout.isNotEmpty) {
+      print(stdout);
+      print("-----");
+    }
+  } else {
+    print("Running: $runWhat: Done in ${stopwatch.elapsedMilliseconds} ms.");
+  }
+}
+
+String _computeRepoDir() {
+  ProcessResult result = Process.runSync(
+      'git', ['rev-parse', '--show-toplevel'],
+      runInShell: true,
+      workingDirectory: new File.fromUri(Platform.script).parent.path);
+  return (result.stdout as String).trim();
+}