Add support for loading a modular test folder and specifying deps in yaml

To test the loading logic, I created sample modular test folders which also
illustrate what modular tests will look like in the future.

Change-Id: Iaa8e076e5be66107391d258f5c72456c89841123
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/101780
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/modular_test/lib/src/dependency_parser.dart b/pkg/modular_test/lib/src/dependency_parser.dart
new file mode 100644
index 0000000..51f2460
--- /dev/null
+++ b/pkg/modular_test/lib/src/dependency_parser.dart
@@ -0,0 +1,74 @@
+// 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.
+
+/// This library defines how to read module dependencies from a Yaml
+/// specification. We expect to find specifications written in this format:
+///
+///    dependencies:
+///      b: a
+///      main: [b, expect]
+///
+/// Where:
+///   - Each name corresponds to a module.
+///   - Module names correlate to either a file, a folder, or a package.
+///   - A map entry contains all the dependencies of a module, if any.
+///   - If a module has a single dependency, it can be written as a single
+///     value.
+///
+/// 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.
+import 'package:yaml/yaml.dart';
+
+/// Parses [contents] containing a module dependencies specification written in
+/// yaml, and returns a normalized dependency map.
+///
+/// Note: some values in the map may not have a corresponding key. That may be
+/// the case for modules that have no dependencies and modules that are not
+/// specified in [contents] (e.g. modules that are supported by default).
+Map<String, List<String>> parseDependencyMap(String contents) {
+  var spec = loadYaml(contents);
+  if (spec is! YamlMap) {
+    return _invalidSpecification("spec is not a map");
+  }
+  var dependencies = spec['dependencies'];
+  if (dependencies == null) {
+    return _invalidSpecification("no dependencies section");
+  }
+  if (dependencies is! YamlMap) {
+    return _invalidSpecification("dependencies is not a map");
+  }
+
+  Map<String, List<String>> normalizedMap = {};
+  dependencies.forEach((key, value) {
+    if (key is! String) {
+      _invalidSpecification("key: '$key' is not a string");
+    }
+    normalizedMap[key] = [];
+    if (value is String) {
+      normalizedMap[key].add(value);
+    } else if (value is List) {
+      value.forEach((entry) {
+        if (entry is! String) {
+          _invalidSpecification("entry: '$entry' is not a string");
+        }
+        normalizedMap[key].add(entry);
+      });
+    } else {
+      _invalidSpecification(
+          "entry: '$value' is not a string or a list of strings");
+    }
+  });
+  return normalizedMap;
+}
+
+_invalidSpecification(String message) {
+  throw new InvalidSpecificationError(message);
+}
+
+class InvalidSpecificationError extends Error {
+  final String message;
+  InvalidSpecificationError(this.message);
+  String toString() => "Invalid specification: $message";
+}
diff --git a/pkg/modular_test/lib/src/find_sdk_root.dart b/pkg/modular_test/lib/src/find_sdk_root.dart
new file mode 100644
index 0000000..2b36550
--- /dev/null
+++ b/pkg/modular_test/lib/src/find_sdk_root.dart
@@ -0,0 +1,28 @@
+// 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';
+
+/// Helper method to locate the root of the SDK repository.
+///
+/// The `modular_test` package is only intended to be used within the SDK at
+/// this time. We need the ability to find the sdk root in order to locate the
+/// default set of packages that are available to all modular tests.
+Future<Uri> findRoot() async {
+  Uri current = Platform.script;
+  while (true) {
+    var segments = current.pathSegments;
+    var index = segments.lastIndexOf('sdk');
+    if (index == -1) {
+      print("error: cannot find the root of the Dart SDK");
+      exitCode = 1;
+      return null;
+    }
+    current = current.resolve("../" * (segments.length - index - 1));
+    if (await File.fromUri(current.resolve("sdk/DEPS")).exists()) {
+      break;
+    }
+  }
+  return current.resolve("sdk/");
+}
diff --git a/pkg/modular_test/lib/src/loader.dart b/pkg/modular_test/lib/src/loader.dart
new file mode 100644
index 0000000..3403380
--- /dev/null
+++ b/pkg/modular_test/lib/src/loader.dart
@@ -0,0 +1,237 @@
+// 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.
+
+/// This library defines how `modular_test` converts the contents of a folder
+/// into a modular test. At this time, the logic in this library assumes this is
+/// only used within the Dart SDK repo.
+///
+/// A modular test folder contains:
+///   * individual .dart files, each file is considered a module. A
+///   `main.dart` file is required as the entry point of the test.
+///   * subfolders: each considered a module with multiple files
+///   * (optional) a .packages file:
+///       * if this is not specified, the test will use [defaultPackagesInput]
+///       instead.
+///       * if specified, it will be extended with the definitions in
+///       [defaultPackagesInput]. The list of packages provided is expected to
+///       be disjoint with those in [defaultPackagesInput].
+///   * a modules.yaml file: a specification of dependencies between modules.
+///     The format is described in `dependencies_parser.dart`.
+import 'dart:io';
+import 'dart:convert';
+import 'suite.dart';
+import 'dependency_parser.dart';
+import 'find_sdk_root.dart';
+
+import 'package:package_config/packages_file.dart' as package_config;
+
+/// Returns the [ModularTest] associated with a folder under [uri].
+///
+/// After scanning the contents of the folder, this method creates a
+/// [ModularTest] that contains only modules that are reachable from the main
+/// module.  This method runs several validations including that modules don't
+/// have conflicting names, that the default packages are always visible, and
+/// that modules do not contain cycles.
+Future<ModularTest> loadTest(Uri uri) async {
+  var folder = Directory.fromUri(uri);
+  var testUri = folder.uri; // normalized in case the trailing '/' was missing.
+  Uri root = await findRoot();
+  Map<String, Uri> defaultPackages =
+      package_config.parse(_defaultPackagesInput, root);
+  Map<String, Module> modules = {};
+  String spec;
+  Module mainModule;
+  Map<String, Uri> packages = {};
+  await for (var entry in folder.list(recursive: false)) {
+    var entryUri = entry.uri;
+    if (entry is File) {
+      var fileName = entryUri.path.substring(testUri.path.length);
+      if (fileName.endsWith('.dart')) {
+        var moduleName = fileName.substring(0, fileName.indexOf('.dart'));
+        if (defaultPackages.containsKey(moduleName)) {
+          return _invalidTest("The file '$fileName' defines a module called "
+              "'$moduleName' which conflicts with a package by the same name "
+              "that is provided by default.");
+        }
+        if (modules.containsKey(moduleName)) {
+          return _moduleConflict(fileName, modules[moduleName], testUri);
+        }
+        var relativeUri = Uri.parse(fileName);
+        var isMain = moduleName == 'main';
+        var module = Module(moduleName, [], testUri, [relativeUri],
+            mainSource: isMain ? relativeUri : null);
+        if (isMain) mainModule = module;
+        modules[moduleName] = module;
+      } else if (fileName == '.packages') {
+        List<int> packagesBytes = await entry.readAsBytes();
+        packages = package_config.parse(packagesBytes, entryUri);
+      } else if (fileName == 'modules.yaml') {
+        spec = await entry.readAsString();
+      }
+    } else {
+      assert(entry is Directory);
+      var path = entryUri.path;
+      var moduleName = path.substring(testUri.path.length, path.length - 1);
+      if (defaultPackages.containsKey(moduleName)) {
+        return _invalidTest("The folder '$moduleName' defines a module "
+            "which conflicts with a package by the same name "
+            "that is provided by default.");
+      }
+      if (modules.containsKey(moduleName)) {
+        return _moduleConflict(moduleName, modules[moduleName], testUri);
+      }
+      var sources = await _listModuleSources(entryUri);
+      modules[moduleName] = Module(moduleName, [], entryUri, sources);
+    }
+  }
+  if (spec == null) {
+    return _invalidTest("modules.yaml file is missing");
+  }
+  if (mainModule == null) {
+    return _invalidTest("main module is missing");
+  }
+
+  _addDefaultPackageEntries(packages, defaultPackages);
+  await _addModulePerPackage(packages, modules);
+  _attachDependencies(parseDependencyMap(spec), modules);
+  _attachDependencies(parseDependencyMap(_defaultPackagesSpec), modules);
+  _detectCyclesAndRemoveUnreachable(modules, mainModule);
+  var sortedModules = modules.values.toList()
+    ..sort((a, b) => a.name.compareTo(b.name));
+  return new ModularTest(sortedModules, mainModule);
+}
+
+/// Returns all source files recursively found in a folder as relative URIs.
+Future<List<Uri>> _listModuleSources(Uri root) async {
+  List<Uri> sources = [];
+  Directory folder = Directory.fromUri(root);
+  await for (var file in folder.list(recursive: true)) {
+    sources.add(Uri.parse(file.uri.path.substring(root.path.length)));
+  }
+  return sources..sort((a, b) => a.path.compareTo(b.path));
+}
+
+/// Add links between modules based on the provided dependency map.
+void _attachDependencies(
+    Map<String, List<String>> dependencies, Map<String, Module> modules) {
+  dependencies.forEach((name, moduleDependencies) {
+    var module = modules[name];
+    if (module == null) {
+      _invalidTest(
+          "declared dependencies for a non existing module named '$name'");
+    }
+    if (module.dependencies.isNotEmpty) {
+      _invalidTest("Module dependencies have already been declared on $name.");
+    }
+    moduleDependencies.forEach((dependencyName) {
+      var moduleDependency = modules[dependencyName];
+      if (moduleDependency == null) {
+        _invalidTest("'$name' declares a dependency on a non existing module "
+            "named '$dependencyName'");
+      }
+      module.dependencies.add(moduleDependency);
+    });
+  });
+}
+
+void _addDefaultPackageEntries(
+    Map<String, Uri> packages, Map<String, Uri> defaultPackages) {
+  for (var name in defaultPackages.keys) {
+    var existing = packages[name];
+    if (existing != null && existing != defaultPackages[name]) {
+      _invalidTest(
+          ".packages file defines an conflicting entry for package '$name'.");
+    }
+    packages[name] = defaultPackages[name];
+  }
+}
+
+/// Create a module for each package dependency.
+Future<void> _addModulePerPackage(
+    Map<String, Uri> packages, Map<String, Module> modules) async {
+  for (var packageName in packages.keys) {
+    var module = modules[packageName];
+    if (module != null) {
+      module.isPackage = true;
+    } else {
+      var packageLibUri = packages[packageName];
+      var sources = await _listModuleSources(packageLibUri);
+      // TODO(sigmund): validate that we don't use a different alias for a
+      // module that is part of the test (package name and module name should
+      // match).
+      modules[packageName] =
+          Module(packageName, [], packageLibUri, sources, isPackage: true);
+    }
+  }
+}
+
+/// Trim the set of modules, and detect cycles while we are at it.
+_detectCyclesAndRemoveUnreachable(Map<String, Module> modules, Module main) {
+  Set<Module> visiting = {};
+  Set<Module> visited = {};
+
+  helper(Module current) {
+    if (!visiting.add(current)) {
+      _invalidTest("module '${current.name}' has a dependency cycle.");
+    }
+    if (visited.add(current)) {
+      current.dependencies.forEach(helper);
+    }
+    visiting.remove(current);
+  }
+
+  helper(main);
+  Set<String> toKeep = visited.map((m) => m.name).toSet();
+  List<String> toRemove =
+      modules.keys.where((name) => !toKeep.contains(name)).toList();
+  toRemove.forEach(modules.remove);
+}
+
+/// Default entries for a .packages file with paths relative to the SDK root.
+List<int> _defaultPackagesInput = utf8.encode('''
+expect:pkg/expect/lib
+async_helper:pkg/async_helper/lib
+meta:pkg/meta/lib
+collection:third_party/pkg/collection/lib
+''');
+
+/// Specifies the dependencies of all packages in [_defaultPackagesInput]. This
+/// string needs to be updated if dependencies between those packages changes
+/// (which is rare).
+// TODO(sigmund): consider either computing this from the pubspec files or the
+// import graph, or adding tests that validate this is always up to date.
+String _defaultPackagesSpec = '''
+dependencies:
+  expect: meta
+  meta: []
+  async_helper: []
+  collection: []
+''';
+
+/// Report an conflict error.
+_moduleConflict(String name, Module existing, Uri root) {
+  var isFile = name.endsWith('.dart');
+  var entryType = isFile ? 'file' : 'folder';
+
+  var existingIsFile = existing.rootUri == root;
+  var existingEntryType = existingIsFile ? 'file' : 'folder';
+
+  var existingName = existingIsFile
+      ? existing.sources.single.pathSegments.last
+      : existing.name;
+
+  return _invalidTest("The $entryType '$name' defines a module "
+      "which conflicts with the module defined by the $existingEntryType "
+      "'$existingName'.");
+}
+
+_invalidTest(String message) {
+  throw new InvalidTestError(message);
+}
+
+class InvalidTestError extends Error {
+  final String message;
+  InvalidTestError(this.message);
+  String toString() => "Invalid test: $message";
+}
diff --git a/pkg/modular_test/lib/src/suite.dart b/pkg/modular_test/lib/src/suite.dart
index bba158e..45ebc6a 100644
--- a/pkg/modular_test/lib/src/suite.dart
+++ b/pkg/modular_test/lib/src/suite.dart
@@ -37,10 +37,14 @@
   /// [Uri] from [rootUri].
   final Uri mainSource;
 
+  /// Whether this module is also available as a package import, where the
+  /// package name matches the module name.
+  bool isPackage;
+
   Module(this.name, this.dependencies, this.rootUri, this.sources,
-      this.mainSource) {
+      {this.mainSource, this.isPackage: false}) {
     if (!_validModuleName.hasMatch(name)) {
-      throw "invalid module name: $name";
+      throw ArgumentError("invalid module name: $name");
     }
   }
 
diff --git a/pkg/modular_test/pubspec.yaml b/pkg/modular_test/pubspec.yaml
index 94f26d8..39f65c8 100644
--- a/pkg/modular_test/pubspec.yaml
+++ b/pkg/modular_test/pubspec.yaml
@@ -8,7 +8,9 @@
   sdk: ">=2.2.1 <3.0.0"
 
 dependencies:
+  package_config: ^1.0.5
   yaml: ^2.1.15
 
 dev_dependencies:
+  args: any
   test: any
diff --git a/pkg/modular_test/test/dependency_parser_test.dart b/pkg/modular_test/test/dependency_parser_test.dart
new file mode 100644
index 0000000..f536282
--- /dev/null
+++ b/pkg/modular_test/test/dependency_parser_test.dart
@@ -0,0 +1,64 @@
+// 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 'package:test/test.dart';
+import 'package:modular_test/src/dependency_parser.dart';
+
+main() {
+  test('require dependencies section', () {
+    expect(() => parseDependencyMap(""),
+        throwsA(TypeMatcher<InvalidSpecificationError>()));
+  });
+
+  test('dependencies is a map', () {
+    expect(() => parseDependencyMap("dependencies: []"),
+        throwsA(TypeMatcher<InvalidSpecificationError>()));
+  });
+
+  test('dependencies can be a string or list of strings', () {
+    parseDependencyMap('''
+          dependencies:
+            a: b
+          ''');
+
+    parseDependencyMap('''
+          dependencies:
+            a: [b, c]
+          ''');
+
+    expect(() => parseDependencyMap('''
+          dependencies:
+            a: 1
+          '''), throwsA(TypeMatcher<InvalidSpecificationError>()));
+
+    expect(() => parseDependencyMap('''
+          dependencies:
+            a: true
+          '''), throwsA(TypeMatcher<InvalidSpecificationError>()));
+
+    expect(() => parseDependencyMap('''
+          dependencies:
+            a: [false]
+          '''), throwsA(TypeMatcher<InvalidSpecificationError>()));
+
+    expect(() => parseDependencyMap('''
+          dependencies:
+            a:
+               c: d
+          '''), throwsA(TypeMatcher<InvalidSpecificationError>()));
+  });
+
+  test('result map is normalized', () {
+    expect(
+        parseDependencyMap('''
+          dependencies:
+            a: [b, c]
+            b: d
+            '''),
+        equals({
+          'a': ['b', 'c'],
+          'b': ['d'],
+        }));
+  });
+}
diff --git a/pkg/modular_test/test/find_sdk_root1_test.dart b/pkg/modular_test/test/find_sdk_root1_test.dart
new file mode 100644
index 0000000..2a5ac51
--- /dev/null
+++ b/pkg/modular_test/test/find_sdk_root1_test.dart
@@ -0,0 +1,15 @@
+// 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';
+
+import 'package:expect/expect.dart';
+import 'package:async_helper/async_helper.dart';
+import 'package:modular_test/src/find_sdk_root.dart';
+
+main() {
+  asyncTest(() async {
+    Expect.equals(Platform.script.resolve("../../../"), await findRoot());
+  });
+}
diff --git a/pkg/modular_test/test/loader/conflict_file_folder_error/expectation.txt b/pkg/modular_test/test/loader/conflict_file_folder_error/expectation.txt
new file mode 100644
index 0000000..4a83176
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_file_folder_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: The file 'm1.dart' defines a module which conflicts with the module defined by the folder 'm1'.
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/conflict_file_folder_error/m1.dart b/pkg/modular_test/test/loader/conflict_file_folder_error/m1.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_file_folder_error/m1.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/conflict_file_folder_error/m1/b.dart b/pkg/modular_test/test/loader/conflict_file_folder_error/m1/b.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_file_folder_error/m1/b.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/conflict_file_folder_error/modules.yaml b/pkg/modular_test/test/loader/conflict_file_folder_error/modules.yaml
new file mode 100644
index 0000000..c093387
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_file_folder_error/modules.yaml
@@ -0,0 +1 @@
+dependencies: {}
diff --git a/pkg/modular_test/test/loader/conflict_file_package_error/expect.dart b/pkg/modular_test/test/loader/conflict_file_package_error/expect.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_file_package_error/expect.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/conflict_file_package_error/expectation.txt b/pkg/modular_test/test/loader/conflict_file_package_error/expectation.txt
new file mode 100644
index 0000000..cabe77c
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_file_package_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: The file 'expect.dart' defines a module called 'expect' which conflicts with a package by the same name that is provided by default.
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/conflict_file_package_error/modules.yaml b/pkg/modular_test/test/loader/conflict_file_package_error/modules.yaml
new file mode 100644
index 0000000..c093387
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_file_package_error/modules.yaml
@@ -0,0 +1 @@
+dependencies: {}
diff --git a/pkg/modular_test/test/loader/conflict_folder_package_error/expect/a.dart b/pkg/modular_test/test/loader/conflict_folder_package_error/expect/a.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_folder_package_error/expect/a.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/conflict_folder_package_error/expectation.txt b/pkg/modular_test/test/loader/conflict_folder_package_error/expectation.txt
new file mode 100644
index 0000000..5118fc0
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_folder_package_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: The folder 'expect' defines a module which conflicts with a package by the same name that is provided by default.
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/conflict_folder_package_error/modules.yaml b/pkg/modular_test/test/loader/conflict_folder_package_error/modules.yaml
new file mode 100644
index 0000000..c093387
--- /dev/null
+++ b/pkg/modular_test/test/loader/conflict_folder_package_error/modules.yaml
@@ -0,0 +1 @@
+dependencies: {}
diff --git a/pkg/modular_test/test/loader/cycle_error/a.dart b/pkg/modular_test/test/loader/cycle_error/a.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/cycle_error/a.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/cycle_error/b.dart b/pkg/modular_test/test/loader/cycle_error/b.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/cycle_error/b.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/cycle_error/c.dart b/pkg/modular_test/test/loader/cycle_error/c.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/cycle_error/c.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/cycle_error/expectation.txt b/pkg/modular_test/test/loader/cycle_error/expectation.txt
new file mode 100644
index 0000000..3825548
--- /dev/null
+++ b/pkg/modular_test/test/loader/cycle_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: module 'b' has a dependency cycle.
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/cycle_error/main.dart b/pkg/modular_test/test/loader/cycle_error/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/cycle_error/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/cycle_error/modules.yaml b/pkg/modular_test/test/loader/cycle_error/modules.yaml
new file mode 100644
index 0000000..b07465b
--- /dev/null
+++ b/pkg/modular_test/test/loader/cycle_error/modules.yaml
@@ -0,0 +1,5 @@
+dependencies:
+  a: b
+  b: c
+  c: a
+  main: b
diff --git a/pkg/modular_test/test/loader/dag/a.dart b/pkg/modular_test/test/loader/dag/a.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag/a.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag/b.dart b/pkg/modular_test/test/loader/dag/b.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag/b.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag/c.dart b/pkg/modular_test/test/loader/dag/c.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag/c.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag/d/d.dart b/pkg/modular_test/test/loader/dag/d/d.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag/d/d.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag/d/e.dart b/pkg/modular_test/test/loader/dag/d/e.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag/d/e.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag/expectation.txt b/pkg/modular_test/test/loader/dag/expectation.txt
new file mode 100644
index 0000000..ef49643
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag/expectation.txt
@@ -0,0 +1,28 @@
+# This expectation file is generated by loader_test.dart
+
+a
+  is package? no
+  dependencies: b, c
+  a.dart
+
+b
+  is package? no
+  dependencies: d
+  b.dart
+
+c
+  is package? no
+  (no dependencies)
+  c.dart
+
+d
+  is package? no
+  (no dependencies)
+  d.dart
+  e.dart
+
+main
+  **main module**
+  is package? no
+  dependencies: a, b
+  main.dart
diff --git a/pkg/modular_test/test/loader/dag/main.dart b/pkg/modular_test/test/loader/dag/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag/modules.yaml b/pkg/modular_test/test/loader/dag/modules.yaml
new file mode 100644
index 0000000..0a775d2
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag/modules.yaml
@@ -0,0 +1,4 @@
+dependencies:
+  a: [b, c]
+  b: d
+  main: [a, b]
diff --git a/pkg/modular_test/test/loader/dag_with_packages/.packages b/pkg/modular_test/test/loader/dag_with_packages/.packages
new file mode 100644
index 0000000..5f1a721
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/.packages
@@ -0,0 +1,2 @@
+a:a/
+c:c/
diff --git a/pkg/modular_test/test/loader/dag_with_packages/a.dart b/pkg/modular_test/test/loader/dag_with_packages/a.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/a.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag_with_packages/b.dart b/pkg/modular_test/test/loader/dag_with_packages/b.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/b.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag_with_packages/c.dart b/pkg/modular_test/test/loader/dag_with_packages/c.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/c.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag_with_packages/d/d.dart b/pkg/modular_test/test/loader/dag_with_packages/d/d.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/d/d.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag_with_packages/d/e.dart b/pkg/modular_test/test/loader/dag_with_packages/d/e.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/d/e.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag_with_packages/expectation.txt b/pkg/modular_test/test/loader/dag_with_packages/expectation.txt
new file mode 100644
index 0000000..ff6f03a
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/expectation.txt
@@ -0,0 +1,28 @@
+# This expectation file is generated by loader_test.dart
+
+a
+  is package? yes
+  dependencies: b, c
+  a.dart
+
+b
+  is package? no
+  dependencies: d
+  b.dart
+
+c
+  is package? yes
+  (no dependencies)
+  c.dart
+
+d
+  is package? no
+  (no dependencies)
+  d.dart
+  e.dart
+
+main
+  **main module**
+  is package? no
+  dependencies: a, b
+  main.dart
diff --git a/pkg/modular_test/test/loader/dag_with_packages/main.dart b/pkg/modular_test/test/loader/dag_with_packages/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/dag_with_packages/modules.yaml b/pkg/modular_test/test/loader/dag_with_packages/modules.yaml
new file mode 100644
index 0000000..0a775d2
--- /dev/null
+++ b/pkg/modular_test/test/loader/dag_with_packages/modules.yaml
@@ -0,0 +1,4 @@
+dependencies:
+  a: [b, c]
+  b: d
+  main: [a, b]
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
new file mode 100644
index 0000000..43c7dac
--- /dev/null
+++ b/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt
@@ -0,0 +1,21 @@
+# This expectation file is generated by loader_test.dart
+
+expect
+  is package? yes
+  dependencies: meta
+  async_minitest.dart
+  expect.dart
+  matchers_lite.dart
+  minitest.dart
+
+main
+  **main module**
+  is package? no
+  dependencies: expect
+  main.dart
+
+meta
+  is package? yes
+  (no dependencies)
+  dart2js.dart
+  meta.dart
diff --git a/pkg/modular_test/test/loader/default_package_dependency_error/main.dart b/pkg/modular_test/test/loader/default_package_dependency_error/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/default_package_dependency_error/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/default_package_dependency_error/modules.yaml b/pkg/modular_test/test/loader/default_package_dependency_error/modules.yaml
new file mode 100644
index 0000000..a5864d3
--- /dev/null
+++ b/pkg/modular_test/test/loader/default_package_dependency_error/modules.yaml
@@ -0,0 +1,3 @@
+dependencies:
+  main:
+    - expect
diff --git a/pkg/modular_test/test/loader/invalid_module_name_error/bad.name.dart b/pkg/modular_test/test/loader/invalid_module_name_error/bad.name.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/invalid_module_name_error/bad.name.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/invalid_module_name_error/expectation.txt b/pkg/modular_test/test/loader/invalid_module_name_error/expectation.txt
new file mode 100644
index 0000000..631afff
--- /dev/null
+++ b/pkg/modular_test/test/loader/invalid_module_name_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid argument(s): invalid module name: bad.name
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/invalid_module_name_error/modules.yaml b/pkg/modular_test/test/loader/invalid_module_name_error/modules.yaml
new file mode 100644
index 0000000..c093387
--- /dev/null
+++ b/pkg/modular_test/test/loader/invalid_module_name_error/modules.yaml
@@ -0,0 +1 @@
+dependencies: {}
diff --git a/pkg/modular_test/test/loader/invalid_packages_error/.packages b/pkg/modular_test/test/loader/invalid_packages_error/.packages
new file mode 100644
index 0000000..d8892ce
--- /dev/null
+++ b/pkg/modular_test/test/loader/invalid_packages_error/.packages
@@ -0,0 +1 @@
+expect:.
diff --git a/pkg/modular_test/test/loader/invalid_packages_error/expectation.txt b/pkg/modular_test/test/loader/invalid_packages_error/expectation.txt
new file mode 100644
index 0000000..6931c77
--- /dev/null
+++ b/pkg/modular_test/test/loader/invalid_packages_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: .packages file defines an conflicting entry for package 'expect'.
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/invalid_packages_error/main.dart b/pkg/modular_test/test/loader/invalid_packages_error/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/invalid_packages_error/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/invalid_packages_error/modules.yaml b/pkg/modular_test/test/loader/invalid_packages_error/modules.yaml
new file mode 100644
index 0000000..a5864d3
--- /dev/null
+++ b/pkg/modular_test/test/loader/invalid_packages_error/modules.yaml
@@ -0,0 +1,3 @@
+dependencies:
+  main:
+    - expect
diff --git a/pkg/modular_test/test/loader/loader_test.dart b/pkg/modular_test/test/loader/loader_test.dart
new file mode 100644
index 0000000..e40db7b
--- /dev/null
+++ b/pkg/modular_test/test/loader/loader_test.dart
@@ -0,0 +1,129 @@
+// 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.
+
+/// Tests that the logic to load, parse, and validate modular tests.
+import 'dart:io';
+import 'package:async_helper/async_helper.dart';
+import 'package:expect/expect.dart';
+import 'package:modular_test/src/loader.dart';
+import 'package:modular_test/src/suite.dart';
+
+import 'package:args/args.dart';
+
+main(List<String> args) {
+  var options = _Options.parse(args);
+  asyncTest(() async {
+    var baseUri = Platform.script.resolve('./');
+    var baseDir = Directory.fromUri(baseUri);
+    await for (var entry in baseDir.list(recursive: false)) {
+      if (entry is Directory) {
+        await _runTest(entry.uri, baseUri, options);
+      }
+    }
+  });
+}
+
+Future<void> _runTest(Uri uri, Uri baseDir, _Options options) async {
+  var dirName = uri.path.substring(baseDir.path.length);
+  if (options.filter != null && !dirName.contains(options.filter)) {
+    if (options.showSkipped) print("skipped: $dirName");
+    return;
+  }
+
+  print("testing: $dirName");
+  String result;
+  String header =
+      "# This expectation file is generated by loader_test.dart\n\n";
+  try {
+    ModularTest test = await loadTest(uri);
+    result = '$header${_dumpAsText(test)}';
+  } on Error catch (e) {
+    result = '$header$e';
+  }
+
+  var file = File.fromUri(uri.resolve('expectation.txt'));
+  if (!options.updateExpectations) {
+    Expect.isTrue(await file.exists(), "expectation.txt file is missing");
+    var expectation = await file.readAsString();
+    if (expectation != result) {
+      print("expectation.txt doesn't match the result of the test. "
+          "To update it, run:\n"
+          "   ${Platform.executable} ${Platform.script} "
+          "--update --show-update --filter $dirName");
+    }
+    Expect.equals(expectation, result);
+    print("  expectation matches result.");
+  } else if (await file.exists() && (await file.readAsString() == result)) {
+    print("  expectation matches result and was up to date.");
+  } else {
+    await file.writeAsString(result);
+    print("  updated ${file.uri}");
+    if (options.showResultOnUpdate) {
+      print('  new expectation is:\x1b[32m\n$result\x1b[0m');
+    }
+  }
+}
+
+String _dumpAsText(ModularTest test) {
+  var buffer = new StringBuffer();
+  bool isFirst = true;
+  for (var module in test.modules) {
+    if (isFirst) {
+      isFirst = false;
+    } else {
+      buffer.write('\n');
+    }
+    buffer.write(module.name);
+    if (test.mainModule == module) {
+      buffer.write('\n  **main module**');
+    }
+    buffer.write('\n  is package? ${module.isPackage ? 'yes' : 'no'}');
+    if (module.dependencies.isEmpty) {
+      buffer.write('\n  (no dependencies)');
+    } else {
+      buffer.write('\n  dependencies: '
+          '${module.dependencies.map((d) => d.name).join(", ")}');
+    }
+
+    if (module.sources.isEmpty) {
+      buffer.write('\n  (no sources)');
+    } else {
+      module.sources.forEach((uri) {
+        buffer.write('\n  $uri');
+      });
+    }
+
+    buffer.write('\n');
+  }
+  return '$buffer';
+}
+
+class _Options {
+  bool updateExpectations = false;
+  bool showResultOnUpdate = false;
+  bool showSkipped = false;
+  String filter = null;
+
+  static _Options parse(List<String> args) {
+    var parser = new ArgParser()
+      ..addFlag('update',
+          abbr: 'u',
+          defaultsTo: false,
+          help: "update expectation files if the result don't match")
+      ..addFlag('show-update',
+          defaultsTo: false,
+          help: "print the result when updating expectation files")
+      ..addFlag('show-skipped',
+          defaultsTo: false,
+          help: "print the name of the tests skipped by the filtering option")
+      ..addOption('filter',
+          help: "only run tests containing this filter as a substring");
+    ArgResults argResults = parser.parse(args);
+    return _Options()
+      ..updateExpectations = argResults['update']
+      ..showResultOnUpdate = argResults['show-update']
+      ..showSkipped = argResults['show-skipped']
+      ..filter = argResults['filter'];
+  }
+}
diff --git a/pkg/modular_test/test/loader/missing_dependency_module_error/expectation.txt b/pkg/modular_test/test/loader/missing_dependency_module_error/expectation.txt
new file mode 100644
index 0000000..7fc8c8b
--- /dev/null
+++ b/pkg/modular_test/test/loader/missing_dependency_module_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: 'main' declares a dependency on a non existing module named 'foo'
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/missing_dependency_module_error/main.dart b/pkg/modular_test/test/loader/missing_dependency_module_error/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/missing_dependency_module_error/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/missing_dependency_module_error/modules.yaml b/pkg/modular_test/test/loader/missing_dependency_module_error/modules.yaml
new file mode 100644
index 0000000..ffd0987
--- /dev/null
+++ b/pkg/modular_test/test/loader/missing_dependency_module_error/modules.yaml
@@ -0,0 +1,3 @@
+dependencies:
+  main:
+    - foo
diff --git a/pkg/modular_test/test/loader/missing_module_error/expectation.txt b/pkg/modular_test/test/loader/missing_module_error/expectation.txt
new file mode 100644
index 0000000..6551c46
--- /dev/null
+++ b/pkg/modular_test/test/loader/missing_module_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: declared dependencies for a non existing module named 'foo'
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/missing_module_error/main.dart b/pkg/modular_test/test/loader/missing_module_error/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/missing_module_error/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/missing_module_error/modules.yaml b/pkg/modular_test/test/loader/missing_module_error/modules.yaml
new file mode 100644
index 0000000..87e50e1
--- /dev/null
+++ b/pkg/modular_test/test/loader/missing_module_error/modules.yaml
@@ -0,0 +1,3 @@
+dependencies:
+  foo:
+    - expect
diff --git a/pkg/modular_test/test/loader/missing_yaml_error/a.dart b/pkg/modular_test/test/loader/missing_yaml_error/a.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/missing_yaml_error/a.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/missing_yaml_error/expectation.txt b/pkg/modular_test/test/loader/missing_yaml_error/expectation.txt
new file mode 100644
index 0000000..9dd6322
--- /dev/null
+++ b/pkg/modular_test/test/loader/missing_yaml_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: modules.yaml file is missing
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/no_dependencies/expectation.txt b/pkg/modular_test/test/loader/no_dependencies/expectation.txt
new file mode 100644
index 0000000..f19449b
--- /dev/null
+++ b/pkg/modular_test/test/loader/no_dependencies/expectation.txt
@@ -0,0 +1,7 @@
+# This expectation file is generated by loader_test.dart
+
+main
+  **main module**
+  is package? no
+  (no dependencies)
+  main.dart
diff --git a/pkg/modular_test/test/loader/no_dependencies/main.dart b/pkg/modular_test/test/loader/no_dependencies/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/no_dependencies/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/no_dependencies/modules.yaml b/pkg/modular_test/test/loader/no_dependencies/modules.yaml
new file mode 100644
index 0000000..c093387
--- /dev/null
+++ b/pkg/modular_test/test/loader/no_dependencies/modules.yaml
@@ -0,0 +1 @@
+dependencies: {}
diff --git a/pkg/modular_test/test/loader/no_main_error/a.dart b/pkg/modular_test/test/loader/no_main_error/a.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/no_main_error/a.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/no_main_error/expectation.txt b/pkg/modular_test/test/loader/no_main_error/expectation.txt
new file mode 100644
index 0000000..78e9711
--- /dev/null
+++ b/pkg/modular_test/test/loader/no_main_error/expectation.txt
@@ -0,0 +1,3 @@
+# This expectation file is generated by loader_test.dart
+
+Invalid test: main module is missing
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/no_main_error/modules.yaml b/pkg/modular_test/test/loader/no_main_error/modules.yaml
new file mode 100644
index 0000000..c093387
--- /dev/null
+++ b/pkg/modular_test/test/loader/no_main_error/modules.yaml
@@ -0,0 +1 @@
+dependencies: {}
diff --git a/pkg/modular_test/test/loader/readme.md b/pkg/modular_test/test/loader/readme.md
new file mode 100644
index 0000000..a3da9fa
--- /dev/null
+++ b/pkg/modular_test/test/loader/readme.md
@@ -0,0 +1,18 @@
+The `loader_test` folder contains a folder per test.
+
+Each test is focusing on part of the features of how tests folders are
+recognized and turned into a modular test specification by our libraries.  Note
+that all `.dart` source files in these tests are empty because we ignore their
+contents.
+
+Each folder contains a file named `expectation.txt` which shows a plain text
+summary of what we expect to extract or report from the test:
+
+ * for invalid inputs it shows the error mesage produced by the modular\_test
+   loader logic
+
+ * for valid inputs it shows the test layout, highlighting what each module
+   contents and dependencies are.
+
+In the future, modular tests will be written in the style of the non-error test
+cases.
diff --git a/pkg/modular_test/test/loader/tree/a.dart b/pkg/modular_test/test/loader/tree/a.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/tree/a.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/tree/b.dart b/pkg/modular_test/test/loader/tree/b.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/tree/b.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/tree/c.dart b/pkg/modular_test/test/loader/tree/c.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/tree/c.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/tree/d/d.dart b/pkg/modular_test/test/loader/tree/d/d.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/tree/d/d.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/tree/d/e.dart b/pkg/modular_test/test/loader/tree/d/e.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/tree/d/e.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/tree/expectation.txt b/pkg/modular_test/test/loader/tree/expectation.txt
new file mode 100644
index 0000000..ffcf4b0
--- /dev/null
+++ b/pkg/modular_test/test/loader/tree/expectation.txt
@@ -0,0 +1,28 @@
+# This expectation file is generated by loader_test.dart
+
+a
+  is package? no
+  dependencies: b, c
+  a.dart
+
+b
+  is package? no
+  dependencies: d
+  b.dart
+
+c
+  is package? no
+  (no dependencies)
+  c.dart
+
+d
+  is package? no
+  (no dependencies)
+  d.dart
+  e.dart
+
+main
+  **main module**
+  is package? no
+  dependencies: a
+  main.dart
diff --git a/pkg/modular_test/test/loader/tree/main.dart b/pkg/modular_test/test/loader/tree/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/tree/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/tree/modules.yaml b/pkg/modular_test/test/loader/tree/modules.yaml
new file mode 100644
index 0000000..b1254c0
--- /dev/null
+++ b/pkg/modular_test/test/loader/tree/modules.yaml
@@ -0,0 +1,4 @@
+dependencies:
+  a: [b, c]
+  b: d
+  main: a
diff --git a/pkg/modular_test/test/loader/valid_packages/.packages b/pkg/modular_test/test/loader/valid_packages/.packages
new file mode 100644
index 0000000..d8e718b
--- /dev/null
+++ b/pkg/modular_test/test/loader/valid_packages/.packages
@@ -0,0 +1 @@
+js:../../../../js/lib
diff --git a/pkg/modular_test/test/loader/valid_packages/expectation.txt b/pkg/modular_test/test/loader/valid_packages/expectation.txt
new file mode 100644
index 0000000..a81d4ec
--- /dev/null
+++ b/pkg/modular_test/test/loader/valid_packages/expectation.txt
@@ -0,0 +1,29 @@
+# This expectation file is generated by loader_test.dart
+
+expect
+  is package? yes
+  dependencies: meta
+  async_minitest.dart
+  expect.dart
+  matchers_lite.dart
+  minitest.dart
+
+js
+  is package? yes
+  (no dependencies)
+  js.dart
+  js_util.dart
+  src/
+  src/varargs.dart
+
+main
+  **main module**
+  is package? no
+  dependencies: js, expect
+  main.dart
+
+meta
+  is package? yes
+  (no dependencies)
+  dart2js.dart
+  meta.dart
diff --git a/pkg/modular_test/test/loader/valid_packages/main.dart b/pkg/modular_test/test/loader/valid_packages/main.dart
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pkg/modular_test/test/loader/valid_packages/main.dart
@@ -0,0 +1 @@
+
diff --git a/pkg/modular_test/test/loader/valid_packages/modules.yaml b/pkg/modular_test/test/loader/valid_packages/modules.yaml
new file mode 100644
index 0000000..2ad9483
--- /dev/null
+++ b/pkg/modular_test/test/loader/valid_packages/modules.yaml
@@ -0,0 +1,4 @@
+dependencies:
+  main:
+    - js
+    - expect
diff --git a/pkg/modular_test/test/pipeline_common.dart b/pkg/modular_test/test/pipeline_common.dart
index a01da5c..d99e687 100644
--- a/pkg/modular_test/test/pipeline_common.dart
+++ b/pkg/modular_test/test/pipeline_common.dart
@@ -75,9 +75,9 @@
   };
 
   var m1 = Module("a", const [], testStrategy.testRootUri,
-      [Uri.parse("a1.dart"), Uri.parse("a2.dart")], null);
+      [Uri.parse("a1.dart"), Uri.parse("a2.dart")]);
   var m2 = Module("b", [m1], testStrategy.testRootUri.resolve('b/'),
-      [Uri.parse("b1.dart"), Uri.parse("b2.dart")], null);
+      [Uri.parse("b1.dart"), Uri.parse("b2.dart")]);
 
   var singleModuleInput = ModularTest([m1], m1);
   var multipleModulesInput = ModularTest([m1, m2], m2);
diff --git a/pkg/modular_test/test/src/find_sdk_root2_test.dart b/pkg/modular_test/test/src/find_sdk_root2_test.dart
new file mode 100644
index 0000000..0bfbeb6
--- /dev/null
+++ b/pkg/modular_test/test/src/find_sdk_root2_test.dart
@@ -0,0 +1,15 @@
+// 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';
+
+import 'package:expect/expect.dart';
+import 'package:async_helper/async_helper.dart';
+import 'package:modular_test/src/find_sdk_root.dart';
+
+main() {
+  asyncTest(() async {
+    Expect.equals(Platform.script.resolve("../../../../"), await findRoot());
+  });
+}
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 5ad6e12..881f093 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -130,7 +130,6 @@
 [ $runtime != vm ]
 dev_compiler/test/options/*: SkipByDesign
 front_end/test/hot_reload_e2e_test: Skip
-modular_test/test/io_pipeline_test: SkipByDesign
 vm/test/*: SkipByDesign # Only meant to run on vm
 
 [ $system == linux ]
@@ -177,9 +176,13 @@
 analyzer/test/*: Skip # Issue 26813
 analyzer/tool/*: Skip # Issue 26813
 
-# Analyze dev_compiler but only run its tests on the vm
 [ $compiler != dart2analyzer && $runtime != vm ]
 dev_compiler/test/*: Skip
+modular_test/test/dependency_parser_test: SkipByDesign
+modular_test/test/find_sdk_root1_test: SkipByDesign
+modular_test/test/io_pipeline_test: SkipByDesign
+modular_test/test/loader/loader_test: SkipByDesign
+modular_test/test/src/find_sdk_root2_test: SkipByDesign
 
 [ $compiler == dart2js && $runtime == chrome && $system == macos ]
 third_party/di_tests/di_test: Pass, Slow # Issue 22896