Add an sdk module to the modular_test pipeline

+ and use it in the dart2js modular tests

Change-Id: I66cf29dcea7fb705135049e7a6ea4eb17052f3d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103575
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/modular_test/lib/src/io_pipeline.dart b/pkg/modular_test/lib/src/io_pipeline.dart
index 910eb3d..229f637 100644
--- a/pkg/modular_test/lib/src/io_pipeline.dart
+++ b/pkg/modular_test/lib/src/io_pipeline.dart
@@ -22,7 +22,8 @@
   /// Assets created on previous steps of the pipeline should be available under
   /// `root.resolveUri(toUri(module, dataId))` and the output of this step
   /// should be stored under `root.resolveUri(toUri(module, resultKind))`.
-  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri);
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags);
 }
 
 class IOPipeline extends Pipeline<IOModularStep> {
@@ -94,7 +95,7 @@
 
   @override
   Future<void> runStep(IOModularStep step, Module module,
-      Map<Module, Set<DataId>> visibleData) async {
+      Map<Module, Set<DataId>> visibleData, List<String> flags) async {
     if (cacheSharedModules && module.isShared) {
       // If all expected outputs are already available, skip the step.
       bool allCachedResultsFound = true;
@@ -135,7 +136,7 @@
     }
 
     await step.execute(module, stepFolder.uri,
-        (Module m, DataId id) => Uri.parse(_toFileName(m, id)));
+        (Module m, DataId id) => Uri.parse(_toFileName(m, id)), flags);
 
     for (var dataId in step.resultData) {
       var outputFile =
diff --git a/pkg/modular_test/lib/src/loader.dart b/pkg/modular_test/lib/src/loader.dart
index a0504b6..7112755 100644
--- a/pkg/modular_test/lib/src/loader.dart
+++ b/pkg/modular_test/lib/src/loader.dart
@@ -39,7 +39,8 @@
   Uri root = await findRoot();
   Map<String, Uri> defaultPackages =
       package_config.parse(_defaultPackagesInput, root);
-  Map<String, Module> modules = {};
+  Module sdkModule = await _createSdkModule(root);
+  Map<String, Module> modules = {'sdk': sdkModule};
   String specString;
   Module mainModule;
   Map<String, Uri> packages = {};
@@ -52,6 +53,11 @@
       var fileName = entryUri.path.substring(testUri.path.length);
       if (fileName.endsWith('.dart')) {
         var moduleName = fileName.substring(0, fileName.indexOf('.dart'));
+        if (moduleName == 'sdk') {
+          return _invalidTest("The file '$fileName' defines a module called "
+              "'$moduleName' which conflicts with the sdk module "
+              "that is provided by default.");
+        }
         if (defaultPackages.containsKey(moduleName)) {
           return _invalidTest("The file '$fileName' defines a module called "
               "'$moduleName' which conflicts with a package by the same name "
@@ -78,6 +84,11 @@
       assert(entry is Directory);
       var path = entryUri.path;
       var moduleName = path.substring(testUri.path.length, path.length - 1);
+      if (moduleName == 'sdk') {
+        return _invalidTest("The folder '$moduleName' defines a module "
+            "which conflicts with the sdk module "
+            "that is provided by default.");
+      }
       if (defaultPackages.containsKey(moduleName)) {
         return _invalidTest("The folder '$moduleName' defines a module "
             "which conflicts with a package by the same name "
@@ -104,6 +115,7 @@
   _attachDependencies(spec.dependencies, modules);
   _attachDependencies(
       parseTestSpecification(_defaultPackagesSpec).dependencies, modules);
+  _addSdkDependencies(modules, sdkModule);
   _detectCyclesAndRemoveUnreachable(modules, mainModule);
   var sortedModules = modules.values.toList()
     ..sort((a, b) => a.name.compareTo(b.name));
@@ -148,6 +160,15 @@
   });
 }
 
+/// Make every module depend on the sdk module.
+void _addSdkDependencies(Map<String, Module> modules, Module sdkModule) {
+  for (var module in modules.values) {
+    if (module != sdkModule) {
+      module.dependencies.add(sdkModule);
+    }
+  }
+}
+
 void _addDefaultPackageEntries(
     Map<String, Uri> packages, Map<String, Uri> defaultPackages) {
   for (var name in defaultPackages.keys) {
@@ -180,6 +201,30 @@
   }
 }
 
+Future<Module> _createSdkModule(Uri root) async {
+  List<Uri> sources = [Uri.parse('sdk/lib/libraries.json')];
+
+  // Include all dart2js, ddc, vm library sources and patch files.
+  // Note: we don't extract the list of files from the libraries.json because
+  // it doesn't list files that are transitively imported.
+  var sdkLibrariesAndPatchesRoots = [
+    'sdk/lib/',
+    'runtime/lib/',
+    'runtime/bin/',
+    'pkg/dev_compiler/tool/input_sdk/',
+  ];
+  for (var path in sdkLibrariesAndPatchesRoots) {
+    var dir = Directory.fromUri(root.resolve(path));
+    await for (var file in dir.list(recursive: true)) {
+      if (file is File && file.path.endsWith(".dart")) {
+        sources.add(Uri.parse(file.path.substring(root.path.length)));
+      }
+    }
+  }
+  sources..sort((a, b) => a.path.compareTo(b.path));
+  return Module('sdk', [], root, sources, isSdk: true, isShared: true);
+}
+
 /// Trim the set of modules, and detect cycles while we are at it.
 _detectCyclesAndRemoveUnreachable(Map<String, Module> modules, Module main) {
   Set<Module> visiting = {};
diff --git a/pkg/modular_test/lib/src/memory_pipeline.dart b/pkg/modular_test/lib/src/memory_pipeline.dart
index a107af4..d97586c 100644
--- a/pkg/modular_test/lib/src/memory_pipeline.dart
+++ b/pkg/modular_test/lib/src/memory_pipeline.dart
@@ -13,8 +13,11 @@
 typedef SourceProvider = String Function(Uri);
 
 abstract class MemoryModularStep extends ModularStep {
-  Future<Map<DataId, Object>> execute(Module module,
-      SourceProvider sourceProvider, ModuleDataProvider dataProvider);
+  Future<Map<DataId, Object>> execute(
+      Module module,
+      SourceProvider sourceProvider,
+      ModuleDataProvider dataProvider,
+      List<String> flags);
 }
 
 class MemoryPipeline extends Pipeline<MemoryModularStep> {
@@ -66,7 +69,7 @@
 
   @override
   Future<void> runStep(MemoryModularStep step, Module module,
-      Map<Module, Set<DataId>> visibleData) async {
+      Map<Module, Set<DataId>> visibleData, List<String> flags) async {
     if (cacheSharedModules && module.isShared) {
       bool allCachedResultsFound = true;
       for (var dataId in step.resultData) {
@@ -98,7 +101,8 @@
     Map<DataId, Object> result = await step.execute(
         module,
         (Uri uri) => inputSources[uri],
-        (Module m, DataId id) => inputData[m][id]);
+        (Module m, DataId id) => inputData[m][id],
+        flags);
     for (var dataId in step.resultData) {
       (_results[module] ??= {})[dataId] = result[dataId];
     }
diff --git a/pkg/modular_test/lib/src/pipeline.dart b/pkg/modular_test/lib/src/pipeline.dart
index 029e5b2..c55f2f9 100644
--- a/pkg/modular_test/lib/src/pipeline.dart
+++ b/pkg/modular_test/lib/src/pipeline.dart
@@ -121,7 +121,7 @@
     // TODO(sigmund): validate that [ModularTest] has no cycles.
     Map<Module, Set<DataId>> computedData = {};
     for (var step in steps) {
-      await _recursiveRun(step, test.mainModule, computedData, {}, {});
+      await _recursiveRun(step, test.mainModule, computedData, {}, test.flags);
     }
   }
 
@@ -129,22 +129,22 @@
       S step,
       Module module,
       Map<Module, Set<DataId>> computedData,
-      Set<Module> seen,
-      Set<Module> parentDependencies) async {
-    if (!seen.add(module)) return;
-    parentDependencies.add(module);
-    Set<Module> transitiveDependencies = {};
+      Map<Module, Set<Module>> transitiveDependencies,
+      List<String> flags) async {
+    if (transitiveDependencies.containsKey(module)) return;
+    var deps = transitiveDependencies[module] = {};
     for (var dependency in module.dependencies) {
       await _recursiveRun(
-          step, dependency, computedData, seen, transitiveDependencies);
+          step, dependency, computedData, transitiveDependencies, flags);
+      deps.add(dependency);
+      deps.addAll(transitiveDependencies[dependency]);
     }
-    parentDependencies.addAll(transitiveDependencies);
 
     if (step.onlyOnMain && !module.isMain) return;
     // Include only requested data from transitive dependencies.
     Map<Module, Set<DataId>> visibleData = {};
 
-    transitiveDependencies.forEach((dep) {
+    deps.forEach((dep) {
       visibleData[dep] = {};
       for (var dataId in step.dependencyDataNeeded) {
         if (computedData[dep].contains(dataId)) {
@@ -158,12 +158,12 @@
         visibleData[module].add(dataId);
       }
     }
-    await runStep(step, module, visibleData);
+    await runStep(step, module, visibleData, flags);
     (computedData[module] ??= {}).addAll(step.resultData);
   }
 
-  Future<void> runStep(
-      S step, Module module, Map<Module, Set<DataId>> visibleData);
+  Future<void> runStep(S step, Module module,
+      Map<Module, Set<DataId>> visibleData, List<String> flags);
 }
 
 class InvalidPipelineError extends Error {
diff --git a/pkg/modular_test/lib/src/suite.dart b/pkg/modular_test/lib/src/suite.dart
index 88aca4c..281c381 100644
--- a/pkg/modular_test/lib/src/suite.dart
+++ b/pkg/modular_test/lib/src/suite.dart
@@ -27,7 +27,7 @@
       throw ArgumentError("modules cannot be null or empty");
     }
     for (var module in modules) {
-      module._validateDependencies();
+      module._validate();
     }
   }
 
@@ -58,6 +58,9 @@
   /// package name matches the module name.
   bool isPackage;
 
+  /// Whether this module represents part of the sdk.
+  bool isSdk;
+
   /// When [isPackage], the base where all package URIs are resolved against.
   /// Stored as a relative [Uri] from [rootUri].
   final Uri packageBase;
@@ -74,16 +77,25 @@
       this.isPackage: false,
       this.isMain: false,
       this.packageBase,
-      this.isShared: false}) {
+      this.isShared: false,
+      this.isSdk: false}) {
     if (!_validModuleName.hasMatch(name)) {
       throw ArgumentError("invalid module name: $name");
     }
   }
 
-  void _validateDependencies() {
-    if (!isPackage && !isShared) return;
+  void _validate() {
+    if (!isPackage && !isShared && !isSdk) return;
+
+    // Note: we validate this now and not in the constructor because loader.dart
+    // may update `isPackage` after the module is created.
+    if (isSdk && isPackage) {
+      throw InvalidModularTestError("invalid module: $name is an sdk "
+          "module but was also marked as a package module.");
+    }
+
     for (var dependency in dependencies) {
-      if (isPackage && !dependency.isPackage) {
+      if (isPackage && !dependency.isPackage && !dependency.isSdk) {
         throw InvalidModularTestError("invalid dependency: $name is a package "
             "but it depends on ${dependency.name}, which is not.");
       }
@@ -92,6 +104,17 @@
             "invalid dependency: $name is a shared module "
             "but it depends on ${dependency.name}, which is not.");
       }
+      if (isSdk) {
+        // TODO(sigmund): we should allow to split sdk modules in smaller
+        // pieces. This requires a bit of work:
+        // - allow to compile subsets of the sdk (see #30957 regarding
+        //   extraRequiredLibraries in CFE)
+        // - add logic to specify sdk dependencies.
+        throw InvalidModularTestError(
+            "invalid dependency: $name is an sdk module that depends on  "
+            "${dependency.name}, but sdk modules are not expected to "
+            "have dependencies.");
+      }
     }
   }
 
@@ -105,7 +128,11 @@
     buffer.write(': ');
     buffer.write(isPackage ? 'package' : '(not package)');
     buffer.write(', deps: {${dependencies.map((d) => d.name).join(", ")}}');
-    buffer.write(', sources: {${sources.map((u) => "$u").join(', ')}}');
+    if (isSdk) {
+      buffer.write(', sources: {...omitted ${sources.length} sources...}');
+    } else {
+      buffer.write(', sources: {${sources.map((u) => "$u").join(', ')}}');
+    }
     return '$buffer';
   }
 }
diff --git a/pkg/modular_test/lib/src/test_specification_parser.dart b/pkg/modular_test/lib/src/test_specification_parser.dart
index 92f81ec..d0eed3d 100644
--- a/pkg/modular_test/lib/src/test_specification_parser.dart
+++ b/pkg/modular_test/lib/src/test_specification_parser.dart
@@ -9,7 +9,7 @@
 ///      b: a
 ///      main: [b, expect]
 ///    flags:
-///      - "--enable-experiment=constant-update-2018"
+///      - constant-update-2018
 ///
 /// Where the dependencies section describe how modules depend on one another,
 /// and the flags section show what flags are needed to run that specific test.
@@ -24,6 +24,10 @@
 /// The logic in this library mostly treats these names as strings, separately
 /// `loader.dart` is responsible for validating and attaching this dependency
 /// information to a set of module definitions.
+///
+/// The framework is agnostic of what the flags are, but at this time we only
+/// use the name of experimental language features. These are then used to
+/// decide what options to pass to the tools that compile and run the tests.
 import 'package:yaml/yaml.dart';
 
 /// Parses [contents] containing a module dependencies specification written in
diff --git a/pkg/modular_test/test/io_pipeline_test.dart b/pkg/modular_test/test/io_pipeline_test.dart
index 0ae59ae..db980f8 100644
--- a/pkg/modular_test/test/io_pipeline_test.dart
+++ b/pkg/modular_test/test/io_pipeline_test.dart
@@ -111,8 +111,8 @@
   SourceOnlyStep(this.action, this.resultId, this.needsSources);
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     Map<Uri, String> sources = {};
 
     for (var uri in module.sources) {
@@ -142,8 +142,8 @@
       : moduleDataNeeded = requestInput ? [inputId] : [];
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     var inputData = await _readHelper(module, root, inputId, toUri);
     var result =
         inputData == null ? "data for $module was null" : action(inputData);
@@ -171,8 +171,8 @@
       this.action1, this.action2, this.inputId, this.result1Id, this.result2Id);
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     var inputData = await _readHelper(module, root, inputId, toUri);
     var result1 =
         inputData == null ? "data for $module was null" : action1(inputData);
@@ -204,8 +204,8 @@
       : dependencyDataNeeded = requestDependencies ? [depId] : [];
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     List<String> depsData = [];
     for (var dependency in module.dependencies) {
       var depData = await _readHelper(dependency, root, depId, toUri);
@@ -236,8 +236,8 @@
       : dependencyDataNeeded = requestDependencies ? [depId] : [];
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     List<String> depsData = [];
     for (var dependency in computeTransitiveDependencies(module)) {
       var depData = await _readHelper(dependency, root, depId, toUri);
diff --git a/pkg/modular_test/test/loader/dag/expectation.txt b/pkg/modular_test/test/loader/dag/expectation.txt
index 188ee70..38220ca 100644
--- a/pkg/modular_test/test/loader/dag/expectation.txt
+++ b/pkg/modular_test/test/loader/dag/expectation.txt
@@ -3,25 +3,29 @@
 a
   is package? no
   is shared?  no
-  dependencies: b, c
+  is sdk?  no
+  dependencies: b, c, sdk
   a.dart
 
 b
   is package? no
   is shared?  no
-  dependencies: d
+  is sdk?  no
+  dependencies: d, sdk
   b.dart
 
 c
   is package? no
   is shared?  no
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   c.dart
 
 d
   is package? no
   is shared?  no
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   d/d.dart
   d/e.dart
 
@@ -29,5 +33,13 @@
   **main module**
   is package? no
   is shared?  no
-  dependencies: a, b
+  is sdk?  no
+  dependencies: a, b, sdk
   main.dart
+
+sdk
+  is package? no
+  is shared?  yes
+  is sdk?  yes
+  (no dependencies)
+  (sdk sources omitted)
diff --git a/pkg/modular_test/test/loader/dag_with_packages/expectation.txt b/pkg/modular_test/test/loader/dag_with_packages/expectation.txt
index ddb9f1e..961d6b8 100644
--- a/pkg/modular_test/test/loader/dag_with_packages/expectation.txt
+++ b/pkg/modular_test/test/loader/dag_with_packages/expectation.txt
@@ -3,25 +3,29 @@
 a
   is package? yes
   is shared?  no
-  dependencies: b, c
+  is sdk?  no
+  dependencies: b, c, sdk
   a.dart
 
 b
   is package? yes
   is shared?  no
-  dependencies: d
+  is sdk?  no
+  dependencies: d, sdk
   b.dart
 
 c
   is package? yes
   is shared?  no
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   c.dart
 
 d
   is package? yes
   is shared?  no
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   d/d.dart
   d/e.dart
 
@@ -29,5 +33,13 @@
   **main module**
   is package? no
   is shared?  no
-  dependencies: a, b
+  is sdk?  no
+  dependencies: a, b, sdk
   main.dart
+
+sdk
+  is package? no
+  is shared?  yes
+  is sdk?  yes
+  (no dependencies)
+  (sdk sources omitted)
diff --git a/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt b/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt
index 29f45c1..48e0849 100644
--- a/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt
+++ b/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt
@@ -3,7 +3,8 @@
 expect
   is package? yes
   is shared?  yes
-  dependencies: meta
+  is sdk?  no
+  dependencies: meta, sdk
   lib/expect.dart
   lib/matchers_lite.dart
   lib/minitest.dart
@@ -12,12 +13,21 @@
   **main module**
   is package? no
   is shared?  no
-  dependencies: expect
+  is sdk?  no
+  dependencies: expect, sdk
   main.dart
 
 meta
   is package? yes
   is shared?  yes
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   lib/dart2js.dart
   lib/meta.dart
+
+sdk
+  is package? no
+  is shared?  yes
+  is sdk?  yes
+  (no dependencies)
+  (sdk sources omitted)
diff --git a/pkg/modular_test/test/loader/loader_test.dart b/pkg/modular_test/test/loader/loader_test.dart
index 6faf716..39b3081 100644
--- a/pkg/modular_test/test/loader/loader_test.dart
+++ b/pkg/modular_test/test/loader/loader_test.dart
@@ -74,6 +74,7 @@
     }
     buffer.write('\n  is package? ${module.isPackage ? 'yes' : 'no'}');
     buffer.write('\n  is shared?  ${module.isShared ? 'yes' : 'no'}');
+    buffer.write('\n  is sdk?  ${module.isSdk ? 'yes' : 'no'}');
     if (module.dependencies.isEmpty) {
       buffer.write('\n  (no dependencies)');
     } else {
@@ -83,6 +84,8 @@
 
     if (module.sources.isEmpty) {
       buffer.write('\n  (no sources)');
+    } else if (module.isSdk) {
+      buffer.write('\n  (sdk sources omitted)');
     } else {
       module.sources.forEach((uri) {
         buffer.write('\n  $uri');
diff --git a/pkg/modular_test/test/loader/no_dependencies/expectation.txt b/pkg/modular_test/test/loader/no_dependencies/expectation.txt
index 21b8165..8af18d5 100644
--- a/pkg/modular_test/test/loader/no_dependencies/expectation.txt
+++ b/pkg/modular_test/test/loader/no_dependencies/expectation.txt
@@ -4,5 +4,13 @@
   **main module**
   is package? no
   is shared?  no
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   main.dart
+
+sdk
+  is package? no
+  is shared?  yes
+  is sdk?  yes
+  (no dependencies)
+  (sdk sources omitted)
diff --git a/pkg/modular_test/test/loader/tree/expectation.txt b/pkg/modular_test/test/loader/tree/expectation.txt
index 285b4d7..d2c3d88 100644
--- a/pkg/modular_test/test/loader/tree/expectation.txt
+++ b/pkg/modular_test/test/loader/tree/expectation.txt
@@ -3,25 +3,29 @@
 a
   is package? no
   is shared?  no
-  dependencies: b, c
+  is sdk?  no
+  dependencies: b, c, sdk
   a.dart
 
 b
   is package? no
   is shared?  no
-  dependencies: d
+  is sdk?  no
+  dependencies: d, sdk
   b.dart
 
 c
   is package? no
   is shared?  no
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   c.dart
 
 d
   is package? no
   is shared?  no
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   d/d.dart
   d/e.dart
 
@@ -29,5 +33,13 @@
   **main module**
   is package? no
   is shared?  no
-  dependencies: a
+  is sdk?  no
+  dependencies: a, sdk
   main.dart
+
+sdk
+  is package? no
+  is shared?  yes
+  is sdk?  yes
+  (no dependencies)
+  (sdk sources omitted)
diff --git a/pkg/modular_test/test/loader/valid_packages/expectation.txt b/pkg/modular_test/test/loader/valid_packages/expectation.txt
index be86423..4ef3034 100644
--- a/pkg/modular_test/test/loader/valid_packages/expectation.txt
+++ b/pkg/modular_test/test/loader/valid_packages/expectation.txt
@@ -3,7 +3,8 @@
 expect
   is package? yes
   is shared?  yes
-  dependencies: meta
+  is sdk?  no
+  dependencies: meta, sdk
   lib/expect.dart
   lib/matchers_lite.dart
   lib/minitest.dart
@@ -11,7 +12,8 @@
 js
   is package? yes
   is shared?  yes
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   lib/js.dart
   lib/js_util.dart
   lib/src/varargs.dart
@@ -20,12 +22,21 @@
   **main module**
   is package? no
   is shared?  no
-  dependencies: js, expect
+  is sdk?  no
+  dependencies: js, expect, sdk
   main.dart
 
 meta
   is package? yes
   is shared?  yes
-  (no dependencies)
+  is sdk?  no
+  dependencies: sdk
   lib/dart2js.dart
   lib/meta.dart
+
+sdk
+  is package? no
+  is shared?  yes
+  is sdk?  yes
+  (no dependencies)
+  (sdk sources omitted)
diff --git a/pkg/modular_test/test/memory_pipeline_test.dart b/pkg/modular_test/test/memory_pipeline_test.dart
index 124bda7..1d4da36 100644
--- a/pkg/modular_test/test/memory_pipeline_test.dart
+++ b/pkg/modular_test/test/memory_pipeline_test.dart
@@ -89,8 +89,11 @@
 
   SourceOnlyStep(this.action, this.resultId, this.needsSources);
 
-  Future<Map<DataId, Object>> execute(Module module,
-      SourceProvider sourceProvider, ModuleDataProvider dataProvider) {
+  Future<Map<DataId, Object>> execute(
+      Module module,
+      SourceProvider sourceProvider,
+      ModuleDataProvider dataProvider,
+      List<String> flags) {
     Map<Uri, String> sources = {};
     for (var uri in module.sources) {
       sources[uri] = sourceProvider(module.rootUri.resolveUri(uri));
@@ -115,8 +118,11 @@
   ModuleDataStep(this.action, this.inputId, this.resultId, bool requestInput)
       : moduleDataNeeded = requestInput ? [inputId] : [];
 
-  Future<Map<DataId, Object>> execute(Module module,
-      SourceProvider sourceProvider, ModuleDataProvider dataProvider) {
+  Future<Map<DataId, Object>> execute(
+      Module module,
+      SourceProvider sourceProvider,
+      ModuleDataProvider dataProvider,
+      List<String> flags) {
     var inputData = dataProvider(module, inputId) as String;
     if (inputData == null)
       return Future.value({resultId: "data for $module was null"});
@@ -142,8 +148,11 @@
   TwoOutputStep(
       this.action1, this.action2, this.inputId, this.result1Id, this.result2Id);
 
-  Future<Map<DataId, Object>> execute(Module module,
-      SourceProvider sourceProvider, ModuleDataProvider dataProvider) {
+  Future<Map<DataId, Object>> execute(
+      Module module,
+      SourceProvider sourceProvider,
+      ModuleDataProvider dataProvider,
+      List<String> flags) {
     var inputData = dataProvider(module, inputId) as String;
     if (inputData == null)
       return Future.value({
@@ -173,8 +182,11 @@
       bool requestDependencies)
       : dependencyDataNeeded = requestDependencies ? [depId] : [];
 
-  Future<Map<DataId, Object>> execute(Module module,
-      SourceProvider sourceProvider, ModuleDataProvider dataProvider) {
+  Future<Map<DataId, Object>> execute(
+      Module module,
+      SourceProvider sourceProvider,
+      ModuleDataProvider dataProvider,
+      List<String> flags) {
     List<String> depsData = module.dependencies
         .map((d) => dataProvider(d, depId) as String)
         .toList();
@@ -201,8 +213,11 @@
       bool requestDependencies)
       : dependencyDataNeeded = requestDependencies ? [depId] : [];
 
-  Future<Map<DataId, Object>> execute(Module module,
-      SourceProvider sourceProvider, ModuleDataProvider dataProvider) {
+  Future<Map<DataId, Object>> execute(
+      Module module,
+      SourceProvider sourceProvider,
+      ModuleDataProvider dataProvider,
+      List<String> flags) {
     List<String> depsData = computeTransitiveDependencies(module)
         .map((d) => dataProvider(d, depId) as String)
         .toList();
diff --git a/pkg/modular_test/test/validate_pipeline_test.dart b/pkg/modular_test/test/validate_pipeline_test.dart
index 7666333..a0b5759 100644
--- a/pkg/modular_test/test/validate_pipeline_test.dart
+++ b/pkg/modular_test/test/validate_pipeline_test.dart
@@ -82,6 +82,6 @@
 
   @override
   Future<void> runStep(ModularStep step, Module module,
-          Map<Module, Set<DataId>> visibleData) =>
+          Map<Module, Set<DataId>> visibleData, List<String> flags) =>
       null;
 }
diff --git a/pkg/modular_test/test/validate_suite_test.dart b/pkg/modular_test/test/validate_suite_test.dart
index e0fad1e..f335567 100644
--- a/pkg/modular_test/test/validate_suite_test.dart
+++ b/pkg/modular_test/test/validate_suite_test.dart
@@ -58,4 +58,35 @@
         throwsA(TypeMatcher<InvalidModularTestError>()));
     expect(ModularTest([m1b, m2b], m2b, []), isNotNull);
   });
+
+  test('sdk module must not have dependencies', () {
+    var m1a = Module("a", const [], Uri.parse("app:/"),
+        [Uri.parse("a1.dart"), Uri.parse("a2.dart")],
+        isSdk: false);
+    var m1b = Module("a", const [], Uri.parse("app:/"),
+        [Uri.parse("a1.dart"), Uri.parse("a2.dart")],
+        isSdk: true);
+
+    var m2a = Module("b", [m1a], Uri.parse("app:/"),
+        [Uri.parse("b/b1.dart"), Uri.parse("b/b2.dart")],
+        isSdk: true);
+    var m2b = Module("b", [m1b], Uri.parse("app:/"),
+        [Uri.parse("b/b1.dart"), Uri.parse("b/b2.dart")],
+        isSdk: true);
+    expect(() => ModularTest([m1a, m2a], m2a, []),
+        throwsA(TypeMatcher<InvalidModularTestError>()));
+    expect(() => ModularTest([m1b, m2b], m2b, []),
+        throwsA(TypeMatcher<InvalidModularTestError>()));
+  });
+
+  test('sdk module cannot be package module', () {
+    var m = Module("a", const [], Uri.parse("app:/"),
+        [Uri.parse("a1.dart"), Uri.parse("a2.dart")],
+        isSdk: true);
+    expect(ModularTest([m], m, []), isNotNull);
+
+    m.isPackage = true;
+    expect(() => ModularTest([m], m, []),
+        throwsA(TypeMatcher<InvalidModularTestError>()));
+  });
 }
diff --git a/tests/compiler/dart2js/modular/data/int_js_number/modules.yaml b/tests/compiler/dart2js/modular/data/int_js_number/modules.yaml
index a02b69a..e8cdeea 100644
--- a/tests/compiler/dart2js/modular/data/int_js_number/modules.yaml
+++ b/tests/compiler/dart2js/modular/data/int_js_number/modules.yaml
@@ -6,3 +6,5 @@
 # after serialization across modules.
 dependencies:
   main: def
+flags:
+  - constant-update-2018
diff --git a/tests/compiler/dart2js/modular/modular_test.dart b/tests/compiler/dart2js/modular/modular_test.dart
index 2e37025..73d0ec1 100644
--- a/tests/compiler/dart2js/modular/modular_test.dart
+++ b/tests/compiler/dart2js/modular/modular_test.dart
@@ -76,9 +76,10 @@
   bool get onlyOnMain => false;
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     if (_options.verbose) print("step: source-to-dill on $module");
+
     // We use non file-URI schemes for representeing source locations in a
     // root-agnostic way. This allows us to refer to file across modules and
     // across steps without exposing the underlying temporary folders that are
@@ -87,7 +88,7 @@
     //
     // Files in packages are defined in terms of `package:` URIs, while
     // non-package URIs are defined using the `dart-dev-app` scheme.
-    String rootScheme = 'dev-dart-app';
+    String rootScheme = module.isSdk ? 'dart-dev-sdk' : 'dev-dart-app';
     String sourceToImportUri(Uri relativeUri) {
       if (module.isPackage) {
         var basePath = module.packageBase.path;
@@ -127,10 +128,30 @@
         .writeAsString('$packagesContents');
 
     var sdkRoot = Platform.script.resolve("../../../../");
-    var platform =
-        computePlatformBinariesLocation().resolve("dart2js_platform.dill");
+    List<String> sources;
+    List<String> extraArgs;
+    if (module.isSdk) {
+      // When no flags are passed, we can skip compilation and reuse the
+      // platform.dill created by build.py.
+      if (flags.isEmpty) {
+        var platform =
+            computePlatformBinariesLocation().resolve("dart2js_platform.dill");
+        var destination = root.resolveUri(toUri(module, dillId));
+        if (_options.verbose) {
+          print('command:\ncp $platform $destination');
+        }
+        await File.fromUri(platform).copy(destination.toFilePath());
+        return;
+      }
+      sources = ['dart:core'];
+      extraArgs = ['--libraries-file', '$rootScheme:///sdk/lib/libraries.json'];
+      assert(transitiveDependencies.isEmpty);
+    } else {
+      sources = module.sources.map(sourceToImportUri).toList();
+      extraArgs = ['--packages-file', '$rootScheme:/.packages'];
+    }
 
-    List<String> workerArgs = [
+    List<String> args = [
       sdkRoot.resolve("utils/bazel/kernel_worker.dart").toFilePath(),
       '--no-summary-only',
       '--target',
@@ -139,19 +160,17 @@
       '$root',
       '--multi-root-scheme',
       rootScheme,
-      '--dart-sdk-summary',
-      '${platform}',
+      ...extraArgs,
       '--output',
       '${toUri(module, dillId)}',
-      '--packages-file',
-      '$rootScheme:/.packages',
       ...(transitiveDependencies
           .expand((m) => ['--input-linked', '${toUri(m, dillId)}'])),
-      ...(module.sources.expand((uri) => ['--source', sourceToImportUri(uri)])),
+      ...(sources.expand((String uri) => ['--source', uri])),
+      ...(flags.expand((String flag) => ['--enable-experiment', flag])),
     ];
 
-    var result = await _runProcess(
-        Platform.resolvedExecutable, workerArgs, root.toFilePath());
+    var result =
+        await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
     _checkExitCode(result, this, module);
   }
 
@@ -180,8 +199,8 @@
   bool get onlyOnMain => true;
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     if (_options.verbose) print("step: dart2js global analysis on $module");
     Set<Module> transitiveDependencies = computeTransitiveDependencies(module);
     Iterable<String> dillDependencies =
@@ -191,6 +210,7 @@
       '--packages=${sdkRoot.toFilePath()}/.packages',
       'package:compiler/src/dart2js.dart',
       '${toUri(module, dillId)}',
+      for (String flag in flags) '--enable-experiment=$flag',
       '--dill-dependencies=${dillDependencies.join(',')}',
       '--write-data=${toUri(module, globalDataId)}',
       '--out=${toUri(module, updatedDillId)}',
@@ -227,14 +247,15 @@
   bool get onlyOnMain => true;
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     if (_options.verbose) print("step: dart2js backend on $module");
     var sdkRoot = Platform.script.resolve("../../../../");
     List<String> args = [
       '--packages=${sdkRoot.toFilePath()}/.packages',
       'package:compiler/src/dart2js.dart',
       '${toUri(module, updatedDillId)}',
+      for (String flag in flags) '--enable-experiment=$flag',
       '--read-data=${toUri(module, globalDataId)}',
       '--out=${toUri(module, jsId)}',
     ];
@@ -268,8 +289,8 @@
   bool get onlyOnMain => true;
 
   @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
     if (_options.verbose) print("step: d8 on $module");
     var sdkRoot = Platform.script.resolve("../../../../");
     List<String> d8Args = [