SDK mocking for improved test throughput.

Improves `all_test` execution from ~30 seconds to ~12. :)

R=scheglov@google.com

Review URL: https://codereview.chromium.org//1415723007 .
diff --git a/lib/src/analysis.dart b/lib/src/analysis.dart
index a6bf01c..0cd13a3 100644
--- a/lib/src/analysis.dart
+++ b/lib/src/analysis.dart
@@ -25,6 +25,7 @@
 import 'package:linter/src/linter.dart';
 import 'package:linter/src/project.dart';
 import 'package:linter/src/rules.dart';
+import 'package:linter/src/sdk.dart';
 import 'package:package_config/packages.dart' show Packages;
 import 'package:package_config/packages_file.dart' as pkgfile show parse;
 import 'package:package_config/src/packages_impl.dart' show MapPackages;
@@ -62,8 +63,12 @@
   int get numSourcesAnalyzed => _sourcesAnalyzed.length;
 
   List<UriResolver> get resolvers {
-    DartSdk sdk = new DirectoryBasedDartSdk(new JavaFile(sdkDir));
+    DartSdk sdk = options.useMockSdk
+        ? new MockSdk()
+        : new DirectoryBasedDartSdk(new JavaFile(sdkDir));
+
     List<UriResolver> resolvers = [new DartUriResolver(sdk)];
+
     if (options.packageRootPath != null) {
       JavaFile packageDirectory = new JavaFile(options.packageRootPath);
       resolvers.add(new PackageUriResolver([packageDirectory]));
@@ -78,6 +83,7 @@
             PhysicalResourceProvider.INSTANCE, packageMap));
       }
     }
+
     // File URI resolver must come last so that files inside "/lib" are
     // are analyzed via "package:" URI's.
     resolvers.add(new FileUriResolver());
@@ -211,16 +217,19 @@
   /// The path to the package root.
   String packageRootPath;
 
+  /// If non-null, the function to use to run pub list.  This is used to mock
+  /// out executions of pub list when testing the linter.
+  RunPubList runPubList = null;
+
   /// Whether to show SDK warnings.
   bool showSdkWarnings = false;
 
+  /// Whether to use a mock SDK (to speed up testing).
+  bool useMockSdk = false;
+
   /// Whether to show lints for the transitive closure of imported and exported
   /// libraries.
   bool visitTransitiveClosure = false;
-
-  /// If non-null, the function to use to run pub list.  This is used to mock
-  /// out executions of pub list when testing the linter.
-  RunPubList runPubList = null;
 }
 
 /// Prints logging information comments to the [outSink] and error messages to
diff --git a/lib/src/linter.dart b/lib/src/linter.dart
index fd4cc1b..68b40f7 100644
--- a/lib/src/linter.dart
+++ b/lib/src/linter.dart
@@ -61,9 +61,6 @@
   /// Creates a new linter.
   DartLinter(this.options, {this.reporter: const PrintingReporter()});
 
-  factory DartLinter.forRules(Iterable<LintRule> ruleSet) =>
-      new DartLinter(new LinterOptions(ruleSet));
-
   Iterable<AnalysisErrorInfo> lintFiles(List<File> files) {
     List<AnalysisErrorInfo> errors = [];
     var analysisDriver = new AnalysisDriver(options);
diff --git a/lib/src/sdk.dart b/lib/src/sdk.dart
new file mode 100644
index 0000000..9dfa1de
--- /dev/null
+++ b/lib/src/sdk.dart
@@ -0,0 +1,421 @@
+// Copyright (c) 2015, 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.
+
+library linter.src.sdk;
+
+import 'package:analyzer/file_system/file_system.dart' as resource;
+import 'package:analyzer/file_system/memory_file_system.dart' as resource;
+import 'package:analyzer/src/context/cache.dart'
+    show AnalysisCache, CachePartition;
+import 'package:analyzer/src/context/context.dart' show AnalysisContextImpl;
+import 'package:analyzer/src/generated/engine.dart'
+    show AnalysisEngine, ChangeSet;
+import 'package:analyzer/src/generated/java_io.dart';
+import 'package:analyzer/src/generated/sdk.dart'
+    show DartSdk, LibraryMap, SdkLibrary;
+import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk;
+import 'package:analyzer/src/generated/source.dart'
+    show DartUriResolver, Source, SourceFactory;
+
+/// Mock SDK for testing purposes.
+class MockSdk implements DartSdk, DirectoryBasedDartSdk {
+  static const _MockSdkLibrary LIB_CORE = const _MockSdkLibrary(
+      'dart:core',
+      '/lib/core/core.dart',
+      '''
+library dart.core;
+
+import 'dart:async';
+
+class Object {
+  bool operator ==(other) => identical(this, other);
+  String toString() => 'a string';
+  int get hashCode => 0;
+}
+
+class Function {}
+class StackTrace {}
+class Symbol {}
+class Type {}
+
+abstract class Comparable<T> {
+  int compareTo(T other);
+}
+
+abstract class String implements Comparable<String> {
+  external factory String.fromCharCodes(Iterable<int> charCodes,
+                                        [int start = 0, int end]);
+  bool get isEmpty => false;
+  bool get isNotEmpty => false;
+  int get length => 0;
+  String toUpperCase();
+  List<int> get codeUnits;
+}
+
+class bool extends Object {}
+abstract class num implements Comparable<num> {
+  bool operator <(num other);
+  bool operator <=(num other);
+  bool operator >(num other);
+  bool operator >=(num other);
+  num operator +(num other);
+  num operator -(num other);
+  num operator *(num other);
+  num operator /(num other);
+  int toInt();
+  num abs();
+  int round();
+}
+abstract class int extends num {
+  bool get isEven => false;
+  int operator -();
+  external static int parse(String source,
+                            { int radix,
+                              int onError(String source) });
+}
+class double extends num {}
+class DateTime extends Object {}
+class Null extends Object {}
+
+class Deprecated extends Object {
+  final String expires;
+  const Deprecated(this.expires);
+}
+const Object deprecated = const Deprecated("next release");
+
+class Iterator<E> {
+  bool moveNext();
+  E get current;
+}
+
+abstract class Iterable<E> {
+  Iterator<E> get iterator;
+  bool get isEmpty;
+}
+
+abstract class List<E> implements Iterable<E> {
+  void add(E value);
+  E operator [](int index);
+  void operator []=(int index, E value);
+  Iterator<E> get iterator => null;
+  void clear();
+}
+
+abstract class Map<K, V> extends Object {
+  Iterable<K> get keys;
+}
+
+external bool identical(Object a, Object b);
+
+void print(Object object) {}
+
+class _Override {
+  const _Override();
+}
+const Object override = const _Override();
+''');
+
+  static const _MockSdkLibrary LIB_ASYNC = const _MockSdkLibrary(
+      'dart:async',
+      '/lib/async/async.dart',
+      '''
+library dart.async;
+
+import 'dart:math';
+
+part 'stream.dart';
+
+class Future<T> {
+  factory Future.delayed(Duration duration, [T computation()]) => null;
+  factory Future.value([value]) => null;
+  static Future wait(List<Future> futures) => null;
+}
+''',
+      const <_MockSdkFile>[
+    const _MockSdkFile(
+        '/lib/async/stream.dart',
+        r'''
+part of dart.async;
+class Stream<T> {}
+abstract class StreamTransformer<S, T> {}
+''')
+  ]);
+
+  static const _MockSdkLibrary LIB_COLLECTION = const _MockSdkLibrary(
+      'dart:collection',
+      '/lib/collection/collection.dart',
+      '''
+library dart.collection;
+
+abstract class HashMap<K, V> implements Map<K, V> {}
+''');
+
+  static const _MockSdkLibrary LIB_CONVERT = const _MockSdkLibrary(
+      'dart:convert',
+      '/lib/convert/convert.dart',
+      '''
+library dart.convert;
+
+import 'dart:async';
+
+abstract class Converter<S, T> implements StreamTransformer {}
+class JsonDecoder extends Converter<String, Object> {}
+''');
+
+  static const _MockSdkLibrary LIB_MATH = const _MockSdkLibrary(
+      'dart:math',
+      '/lib/math/math.dart',
+      '''
+library dart.math;
+const double E = 2.718281828459045;
+const double PI = 3.1415926535897932;
+const double LN10 =  2.302585092994046;
+num min(num a, num b) => 0;
+num max(num a, num b) => 0;
+external double cos(num x);
+external double sin(num x);
+external double sqrt(num x);
+class Random {
+  bool nextBool() => true;
+  double nextDouble() => 2.0;
+  int nextInt() => 1;
+}
+''');
+
+  static const _MockSdkLibrary LIB_HTML = const _MockSdkLibrary(
+      'dart:html',
+      '/lib/html/dartium/html_dartium.dart',
+      '''
+library dart.html;
+class HtmlElement {}
+''');
+
+  static const List<SdkLibrary> LIBRARIES = const [
+    LIB_CORE,
+    LIB_ASYNC,
+    LIB_COLLECTION,
+    LIB_CONVERT,
+    LIB_MATH,
+    LIB_HTML,
+  ];
+
+  final resource.MemoryResourceProvider provider =
+      new resource.MemoryResourceProvider();
+
+  /**
+   * The [AnalysisContextImpl] which is used for all of the sources.
+   */
+  AnalysisContextImpl _analysisContext;
+
+  MockSdk() {
+    LIBRARIES.forEach((_MockSdkLibrary library) {
+      provider.newFile(library.path, library.content);
+      library.parts.forEach((file) {
+        provider.newFile(file.path, file.content);
+      });
+    });
+  }
+
+  @override
+  AnalysisContextImpl get context {
+    if (_analysisContext == null) {
+      _analysisContext = new _SdkAnalysisContext(this);
+      SourceFactory factory = new SourceFactory([new DartUriResolver(this)]);
+      _analysisContext.sourceFactory = factory;
+      ChangeSet changeSet = new ChangeSet();
+      for (String uri in uris) {
+        Source source = factory.forUri(uri);
+        changeSet.addedSource(source);
+      }
+      _analysisContext.applyChanges(changeSet);
+    }
+    return _analysisContext;
+  }
+
+  @override
+  JavaFile get dart2JsExecutable => null;
+
+  @override
+  String get dartiumBinaryName => null;
+
+  @override
+  JavaFile get dartiumExecutable => null;
+
+  @override
+  JavaFile get dartiumWorkingDirectory => null;
+
+  @override
+  JavaFile get directory => null;
+
+  @override
+  JavaFile get docDirectory => null;
+
+  @override
+  bool get hasDocumentation => null;
+
+  @override
+  bool get isDartiumInstalled => null;
+
+  @override
+  JavaFile get libraryDirectory => null;
+
+  @override
+  JavaFile get pubExecutable => null;
+
+  @override
+  List<SdkLibrary> get sdkLibraries => LIBRARIES;
+
+  @override
+  String get sdkVersion => throw unimplemented;
+
+  UnimplementedError get unimplemented => new UnimplementedError();
+
+  @override
+  List<String> get uris {
+    List<String> uris = <String>[];
+    for (SdkLibrary library in LIBRARIES) {
+      uris.add(library.shortName);
+    }
+    return uris;
+  }
+
+  @override
+  String get vmBinaryName => null;
+
+  @override
+  JavaFile get vmExecutable => null;
+
+  @override
+  Source fromFileUri(Uri uri) {
+    String filePath = uri.path;
+    String libPath = '/lib';
+    if (!filePath.startsWith("$libPath/")) {
+      return null;
+    }
+    for (SdkLibrary library in LIBRARIES) {
+      String libraryPath = library.path;
+      if (filePath.replaceAll('\\', '/') == libraryPath) {
+        try {
+          resource.File file = provider.getResource(uri.path);
+          Uri dartUri = Uri.parse(library.shortName);
+          return file.createSource(dartUri);
+        } catch (exception) {
+          return null;
+        }
+      }
+      if (filePath.startsWith("$libraryPath/")) {
+        String pathInLibrary = filePath.substring(libraryPath.length + 1);
+        String path = '${library.shortName}/${pathInLibrary}';
+        try {
+          resource.File file = provider.getResource(uri.path);
+          Uri dartUri = new Uri(scheme: 'dart', path: path);
+          return file.createSource(dartUri);
+        } catch (exception) {
+          return null;
+        }
+      }
+    }
+    return null;
+  }
+
+  @override
+  JavaFile getDartiumWorkingDirectory(JavaFile installDir) => null;
+
+  @override
+  JavaFile getDocFileFor(String libraryName) => null;
+
+  @override
+  SdkLibrary getSdkLibrary(String dartUri) {
+    // getSdkLibrary() is only used to determine whether a library is internal
+    // to the SDK.  The mock SDK doesn't have any internals, so it's safe to
+    // return null.
+    return null;
+  }
+
+  @override
+  LibraryMap initialLibraryMap(bool useDart2jsPaths) => null;
+
+  @override
+  Source mapDartUri(String dartUri) {
+    const Map<String, String> uriToPath = const {
+      "dart:core": "/lib/core/core.dart",
+      "dart:html": "/lib/html/dartium/html_dartium.dart",
+      "dart:async": "/lib/async/async.dart",
+      "dart:async/stream.dart": "/lib/async/stream.dart",
+      "dart:collection": "/lib/collection/collection.dart",
+      "dart:convert": "/lib/convert/convert.dart",
+      "dart:math": "/lib/math/math.dart"
+    };
+
+    String path = uriToPath[dartUri];
+    if (path != null) {
+      resource.File file = provider.getResource(path);
+      Uri uri = new Uri(scheme: 'dart', path: dartUri.substring(5));
+      return file.createSource(uri);
+    }
+
+    // If we reach here then we tried to use a dartUri that's not in the
+    // table above.
+    return null;
+  }
+}
+
+class _MockSdkFile {
+  final String path;
+  final String content;
+
+  const _MockSdkFile(this.path, this.content);
+}
+
+class _MockSdkLibrary implements SdkLibrary {
+  final String shortName;
+  final String path;
+  final String content;
+  final List<_MockSdkFile> parts;
+
+  const _MockSdkLibrary(this.shortName, this.path, this.content,
+      [this.parts = const <_MockSdkFile>[]]);
+
+  @override
+  String get category => throw unimplemented;
+
+  @override
+  bool get isDart2JsLibrary => throw unimplemented;
+
+  @override
+  bool get isDocumented => throw unimplemented;
+
+  @override
+  bool get isImplementation => throw unimplemented;
+
+  @override
+  bool get isInternal => throw unimplemented;
+
+  @override
+  bool get isShared => throw unimplemented;
+
+  @override
+  bool get isVmLibrary => throw unimplemented;
+
+  UnimplementedError get unimplemented => new UnimplementedError();
+}
+
+/**
+ * An [AnalysisContextImpl] that only contains sources for a Dart SDK.
+ */
+class _SdkAnalysisContext extends AnalysisContextImpl {
+  final DartSdk sdk;
+
+  _SdkAnalysisContext(this.sdk);
+
+  @override
+  AnalysisCache createCacheFromSourceFactory(SourceFactory factory) {
+    if (factory == null) {
+      return super.createCacheFromSourceFactory(factory);
+    }
+    return new AnalysisCache(<CachePartition>[
+      AnalysisEngine.instance.partitionManager_new.forSdk(sdk)
+    ]);
+  }
+}
diff --git a/test/rule_test.dart b/test/rule_test.dart
index 65aa529..9d8a4d4 100644
--- a/test/rule_test.dart
+++ b/test/rule_test.dart
@@ -353,8 +353,12 @@
       ++lineNumber;
     }
 
-    DartLinter driver = new DartLinter.forRules(
-        [ruleRegistry[ruleName]].where((rule) => rule != null));
+    LinterOptions options = new LinterOptions(
+        [ruleRegistry[ruleName]].where((rule) => rule != null))
+      ..useMockSdk = true
+      ..packageRootPath = '.';
+
+    DartLinter driver = new DartLinter(options);
 
     Iterable<AnalysisErrorInfo> lints = driver.lintFiles([file]);
 
@@ -369,7 +373,6 @@
     try {
       expect(actual, unorderedMatches(expected));
     } on Error catch (e) {
-
       if (debug) {
         // Dump results for debugging purposes.