[test_runner] support testing dartdevk with d8

Change-Id: If38d3013a4ac60b6ef552f004451556898838445
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/177001
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Bob Nystrom <rnystrom@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/smith/lib/configuration.dart b/pkg/smith/lib/configuration.dart
index 846d39d..370e772 100644
--- a/pkg/smith/lib/configuration.dart
+++ b/pkg/smith/lib/configuration.dart
@@ -663,10 +663,9 @@
 
       case Compiler.dartdevc:
       case Compiler.dartdevk:
-        // TODO(rnystrom): Expand to support other JS execution environments
-        // (other browsers, d8) when tested and working.
         return const [
           Runtime.none,
+          Runtime.d8,
           Runtime.chrome,
           Runtime.edge,
           Runtime.firefox,
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index d92797d..b6e07e5 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -557,29 +557,36 @@
     args.addAll(options);
     args.addAll(_configuration.sharedOptions);
 
+    bool d8Runtime = _configuration.runtime == Runtime.d8;
+
     args.addAll([
       "--ignore-unrecognized-flags",
       "--no-summarize",
+      if (d8Runtime) "--modules=legacy",
       "-o",
       outputFile,
       inputFile,
     ]);
 
-    // Link to the summaries for the available packages, so that they don't
-    // get recompiled into the test's own module.
-    var packageSummaryDir =
-        _configuration.nnbdMode == NnbdMode.strong ? 'pkg_sound' : 'pkg_kernel';
-    for (var package in testPackages) {
-      args.add("-s");
+    if (!d8Runtime) {
+      // TODO(sigmund): allow caching of shared packages in legacy mode too.
+      // Link to the summaries for the available packages, so that they don't
+      // get recompiled into the test's own module.
+      var packageSummaryDir = _configuration.nnbdMode == NnbdMode.strong
+          ? 'pkg_sound'
+          : 'pkg_kernel';
+      for (var package in testPackages) {
+        args.add("-s");
 
-      // Since the summaries for the packages are not near the tests, we give
-      // dartdevc explicit module paths for each one. When the test is run, we
-      // will tell require.js where to find each package's compiled JS.
-      var summary = Path(_configuration.buildDirectory)
-          .append("/gen/utils/dartdevc/$packageSummaryDir/$package.dill")
-          .absolute
-          .toNativePath();
-      args.add("$summary=$package");
+        // Since the summaries for the packages are not near the tests, we give
+        // dartdevc explicit module paths for each one. When the test is run, we
+        // will tell require.js where to find each package's compiled JS.
+        var summary = Path(_configuration.buildDirectory)
+            .append("/gen/utils/dartdevc/$packageSummaryDir/$package.dill")
+            .absolute
+            .toNativePath();
+        args.add("$summary=$package");
+      }
     }
 
     var inputDir = Path(inputFile).append("..").canonicalize().toNativePath();
@@ -598,12 +605,80 @@
     // computeCompilerArguments() to here seems hacky. Is there a cleaner way?
     var sharedOptions = arguments.sublist(0, arguments.length - 1);
     var inputFile = arguments.last;
-    var inputFilename = Uri.file(inputFile).pathSegments.last;
-    var outputFile = "$tempDir/${inputFilename.replaceAll('.dart', '.js')}";
+    var inputUri = Uri.file(inputFile);
+    var inputFilename = inputUri.pathSegments.last;
+    var moduleName =
+        inputFilename.substring(0, inputFilename.length - ".dart".length);
+    var outputFile = "$tempDir/$moduleName.js";
+    var runFile = outputFile;
+
+    if (_configuration.runtime == Runtime.d8) {
+      // TODO(sigmund): ddc should have a flag to emit an entrypoint file like
+      // the one below, otherwise it is succeptible to break, for example, if
+      // library naming conventions were to change in the future.
+      runFile = "$tempDir/$moduleName.d8.js";
+      var nonNullAsserts = arguments.contains('--null-assertions');
+      var nativeNonNullAsserts = arguments.contains('--native-null-assertions');
+      var weakNullSafetyErrors =
+          arguments.contains('--weak-null-safety-errors');
+      var soundNullSafety = _configuration.nnbdMode == NnbdMode.strong;
+      var weakNullSafetyWarnings = !(weakNullSafetyErrors || soundNullSafety);
+      var repositoryUri = Uri.directory(Repository.dir.toNativePath());
+      var dartLibraryPath = repositoryUri
+          .resolve('pkg/dev_compiler/lib/js/legacy/dart_library.js')
+          .path;
+      var sdkJsDir = Uri.directory(_configuration.buildDirectory)
+          .resolve('gen/utils/dartdevc/');
+      var sdkJsPath = soundNullSafety
+          ? 'sound/legacy/dart_sdk.js'
+          : 'kernel/legacy/dart_sdk.js';
+      var libraryName = inputUri.path
+          .substring(repositoryUri.path.length)
+          .replaceAll("/", "__")
+          .replaceAll("-", "_")
+          .replaceAll(".dart", "");
+
+      // Note: this assumes that d8 is invoked with the dart2js d8.js preamble.
+      // TODO(sigmund): to support other runtimes like js-shell, we may want to
+      // remove the `load` statements here and instead provide those files
+      // through the runtime command-line arguments.
+      File(runFile).writeAsStringSync('''
+        load("$dartLibraryPath");
+        load("$sdkJsDir/$sdkJsPath");
+        load("$outputFile");
+
+        let sdk = dart_library.import("dart_sdk");
+        sdk.dart.weakNullSafetyWarnings($weakNullSafetyWarnings);
+        sdk.dart.weakNullSafetyErrors($weakNullSafetyErrors);
+        sdk.dart.nonNullAsserts($nonNullAsserts);
+        sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts);
+
+        // Invoke main through the d8 preamble to ensure the code is running
+        // within the fake event loop.
+        self.dartMainRunner(function () {
+          dart_library.start("$moduleName", "$libraryName");
+        });
+      '''
+          .replaceAll("\n        ", "\n"));
+    }
 
     return CommandArtifact([
       _createCommand(inputFile, outputFile, sharedOptions, environmentOverrides)
-    ], outputFile, "application/javascript");
+    ], runFile, "application/javascript");
+  }
+
+  List<String> computeRuntimeArguments(
+      RuntimeConfiguration runtimeConfiguration,
+      TestFile testFile,
+      List<String> vmOptions,
+      List<String> originalArguments,
+      CommandArtifact artifact) {
+    var sdkDir = _useSdk
+        ? Uri.directory(_configuration.buildDirectory).resolve('dart-sdk/')
+        : Uri.directory(Repository.dir.toNativePath()).resolve('sdk/');
+    var preambleDir = sdkDir.resolve('lib/_internal/js_runtime/lib/preambles/');
+    return runtimeConfiguration.dart2jsPreambles(preambleDir)
+      ..add(artifact.filename);
   }
 }
 
diff --git a/pkg/test_runner/lib/src/test_suite.dart b/pkg/test_runner/lib/src/test_suite.dart
index 6f5d5f2..9c1baf2 100644
--- a/pkg/test_runner/lib/src/test_suite.dart
+++ b/pkg/test_runner/lib/src/test_suite.dart
@@ -234,8 +234,11 @@
     var minified = configuration.isMinified ? '-minified' : '';
     var csp = configuration.isCsp ? '-csp' : '';
     var sdk = configuration.useSdk ? '-sdk' : '';
+    var isLegacyModule = configuration.compiler == Compiler.dartdevk &&
+        configuration.runtime == Runtime.d8;
+    var module = isLegacyModule ? '-legacy' : '';
     var dirName = "${configuration.compiler.name}"
-        "$checked$minified$csp$sdk";
+        "$checked$minified$csp$sdk$module";
     return createGeneratedTestDirectoryHelper(
         "compilations", dirName, testPath);
   }