Wire up placeholder executable in pkg/analyzer_fe_comparison.

The placeholder executable is replaced by a shell script that invokes
pkg/analyzer_fe_comparison (in a similar way to how
sdk/bin/dartanalyzer invokes pkg/analyzer_cli).  This required some
rewriting of pkg/analyzer_fe_comparison to support analysis of a
single test case (as opposed to analysis of a package, which is what
it did before).

It's now possible to run the comparison tool over the test suite in
language_2.  There are many failures, which I'll begin addressing in
follow-up CLs.

Change-Id: I52d8f3b53064e8e29028571d416f2573c6cba35d
Reviewed-on: https://dart-review.googlesource.com/74820
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer_fe_comparison/bin/compare_programs.dart b/pkg/analyzer_fe_comparison/bin/compare_programs.dart
new file mode 100644
index 0000000..0118aee
--- /dev/null
+++ b/pkg/analyzer_fe_comparison/bin/compare_programs.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:analyzer/src/command_line/arguments.dart';
+import 'package:analyzer_fe_comparison/comparison.dart';
+import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
+
+/// Compares the analyzer and front_end behavior when compiling a program.
+main(List<String> args) async {
+  ArgResults options = _parseArgs(args);
+  var sourcePaths = options.rest;
+  if (sourcePaths.length != 1) {
+    throw new StateError('Exactly one source file must be specified.');
+  }
+  var sourcePath = sourcePaths[0];
+  var scriptPath = Platform.script.toFilePath();
+  var sdkRepoPath =
+      path.normalize(path.join(path.dirname(scriptPath), '..', '..', '..'));
+  var buildPath = await _findBuildDir(sdkRepoPath, 'ReleaseX64');
+  var dillPath = path.join(buildPath, 'vm_platform_strong.dill');
+  var packagesFilePath = path.join(sdkRepoPath, '.packages');
+
+  await compareTestPrograms(sourcePath, dillPath, packagesFilePath);
+}
+
+Future<String> _findBuildDir(String sdkRepoPath, String targetName) async {
+  for (var subdirName in ['out', 'xcodebuild']) {
+    var candidatePath = path.join(sdkRepoPath, subdirName, targetName);
+    if (await new Directory(candidatePath).exists()) {
+      return candidatePath;
+    }
+  }
+  throw new StateError('Cannot find build directory');
+}
+
+ArgResults _parseArgs(List<String> args) {
+  var parser = new ArgParser(allowTrailingOptions: true);
+  parser.addOption('dart-sdk', help: 'The path to the Dart SDK.');
+  if (args.contains('--ignore-unrecognized-flags')) {
+    args = filterUnknownArguments(args, parser);
+  }
+  return parser.parse(args);
+}
diff --git a/pkg/analyzer_fe_comparison/bin/compare_sdk_tests b/pkg/analyzer_fe_comparison/bin/compare_sdk_tests
index 96a4e46..b84851f 100755
--- a/pkg/analyzer_fe_comparison/bin/compare_sdk_tests
+++ b/pkg/analyzer_fe_comparison/bin/compare_sdk_tests
@@ -3,5 +3,40 @@
 # 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.
 
-# TODO(paulberry)
-echo "Placeholder text"
+# Run analyzer_fe_comparison on the Dart VM.  This script assumes the Dart
+# repo's directory structure.
+
+function follow_links() {
+  file="$1"
+  while [ -h "$file" ]; do
+    # On Mac OS, readlink -f doesn't work.
+    file="$(readlink "$file")"
+  done
+  echo "$file"
+}
+
+# Unlike $0, $BASH_SOURCE points to the absolute path of this file.
+PROG_NAME="$(follow_links "$BASH_SOURCE")"
+
+# Find directories.
+PKG_BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
+DART_ROOT="$(cd "${PKG_BIN_DIR}/../../.." ; pwd -P)"
+SDK_DIR="${DART_ROOT}/sdk"
+BIN_DIR="${SDK_DIR}/bin"
+
+SDK_ARG="--dart-sdk=$SDK_DIR"
+
+DART="$BIN_DIR/dart"
+
+unset EXTRA_VM_OPTIONS
+declare -a EXTRA_VM_OPTIONS
+
+# We allow extra vm options to be passed in through an environment variable.
+if [[ $DART_VM_OPTIONS ]]; then
+  read -a OPTIONS <<< "$DART_VM_OPTIONS"
+  EXTRA_VM_OPTIONS+=("${OPTIONS[@]}")
+fi
+
+COMPARE_PROGRAMS="$PKG_BIN_DIR/compare_programs.dart"
+
+exec "$DART" "--packages=$DART_ROOT/.packages" "${EXTRA_VM_OPTIONS[@]}" "$COMPARE_PROGRAMS" "$SDK_ARG" "$@"
diff --git a/pkg/analyzer_fe_comparison/lib/comparison.dart b/pkg/analyzer_fe_comparison/lib/comparison.dart
index 03138b7..fe3f57e 100644
--- a/pkg/analyzer_fe_comparison/lib/comparison.dart
+++ b/pkg/analyzer_fe_comparison/lib/comparison.dart
@@ -2,15 +2,16 @@
 // 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:analyzer_fe_comparison/src/analyzer.dart';
+import 'package:analyzer_fe_comparison/src/analyzer.dart' as analyzer;
 import 'package:analyzer_fe_comparison/src/comparison_node.dart';
-import 'package:analyzer_fe_comparison/src/kernel.dart';
+import 'package:analyzer_fe_comparison/src/kernel.dart' as kernel;
+import 'package:path/path.dart' as path;
 
-/// Compares the analyzer and kernel representations of a project, and prints
+/// Compares the analyzer and kernel representations of a package, and prints
 /// the resulting diff.
-void compare(
+void comparePackages(
     String platformPath, String projectLibPath, String packagesFilePath) async {
-  ComparisonNode analyzerNode = await driveAnalyzer(projectLibPath);
+  ComparisonNode analyzerNode = await analyzer.analyzePackage(projectLibPath);
   var packagesFileUri = Uri.file(packagesFilePath);
   var inputs = <Uri>[];
   for (var library in analyzerNode.children) {
@@ -18,6 +19,43 @@
   }
   var platformUri = Uri.file(platformPath);
   ComparisonNode kernelNode =
-      await driveKernel(inputs, packagesFileUri, platformUri);
+      await kernel.analyzePackage(inputs, packagesFileUri, platformUri);
   print(ComparisonNode.diff(kernelNode, analyzerNode));
 }
+
+/// Compares the analyzer and kernel representations of a test file, and prints
+/// the resulting diff.
+///
+/// Only libraries reached by a "file:" URI are compared.
+void compareTestPrograms(
+    String sourcePath, String platformPath, String packagesFilePath) async {
+  var packagesFileUri = Uri.file(packagesFilePath);
+  var platformUri = Uri.file(platformPath);
+  ComparisonNode kernelNode = await kernel.analyzeProgram(
+      path.toUri(sourcePath),
+      packagesFileUri,
+      platformUri,
+      (uri) => uri.scheme == 'file');
+  String startingPath;
+  var inputs = <String>[];
+  for (var library in kernelNode.children) {
+    var filePath = path.fromUri(Uri.parse(library.text));
+    if (startingPath == null) {
+      startingPath = path.dirname(filePath);
+    } else {
+      while (!path.isWithin(startingPath, filePath)) {
+        startingPath = path.dirname(startingPath);
+      }
+    }
+    inputs.add(filePath);
+  }
+  ComparisonNode analyzerNode =
+      await analyzer.analyzeFiles(startingPath, inputs);
+  var diff = ComparisonNode.diff(kernelNode, analyzerNode);
+  if (diff.children.isEmpty) {
+    print('No differences found!');
+  } else {
+    print('Differences found:');
+    print(diff);
+  }
+}
diff --git a/pkg/analyzer_fe_comparison/lib/src/analyzer.dart b/pkg/analyzer_fe_comparison/lib/src/analyzer.dart
index 207ede3..9da54d4 100644
--- a/pkg/analyzer_fe_comparison/lib/src/analyzer.dart
+++ b/pkg/analyzer_fe_comparison/lib/src/analyzer.dart
@@ -5,6 +5,9 @@
 import 'dart:async';
 
 import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/context_root.dart';
+import 'package:analyzer/dart/analysis/session.dart';
+import 'package:analyzer/dart/analysis/uri_converter.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -13,39 +16,76 @@
 import 'package:analyzer/src/generated/source.dart' show SourceKind;
 import 'package:analyzer_fe_comparison/src/comparison_node.dart';
 
-/// Analyzes the project located at [libPath] using the analyzer, and returns a
+/// Analyzes the files in [filePaths] using the analyzer, and returns a
+/// [ComparisonNode] representing them.
+Future<ComparisonNode> analyzeFiles(
+    String startingPath, List<String> filePaths) async {
+  var driver = await _AnalyzerDriver.create(startingPath);
+  return driver.analyzeFiles(filePaths);
+}
+
+/// Analyzes the package located at [libPath] using the analyzer, and returns a
 /// [ComparisonNode] representing it.
-Future<ComparisonNode> driveAnalyzer(String libPath) async {
-  var contextCollection = AnalysisContextCollection(includedPaths: [libPath]);
-  var contexts = contextCollection.contexts;
-  if (contexts.length != 1) {
-    throw new StateError('Expected exactly one context');
-  }
-  var context = contexts[0];
-  var session = context.currentSession;
-  var typeProvider = await session.typeProvider;
-  var uriConverter = session.uriConverter;
-  var contextRoot = context.contextRoot;
-  var libraryNodes = <ComparisonNode>[];
-  for (var filePath in contextRoot.analyzedFiles()) {
-    var kind = await session.getSourceKind(filePath);
-    if (kind == SourceKind.LIBRARY) {
-      var importUri = uriConverter.pathToUri(filePath);
-      var libraryElement = await session.getLibraryByUri(importUri.toString());
-      var childNodes = <ComparisonNode>[];
-      if (libraryElement.name.isNotEmpty) {
-        childNodes.add(ComparisonNode('name=${libraryElement.name}'));
+Future<ComparisonNode> analyzePackage(String libPath) async {
+  var driver = await _AnalyzerDriver.create(libPath);
+  return driver.analyzePackage();
+}
+
+class _AnalyzerDriver {
+  final AnalysisSession _session;
+
+  final TypeProvider _typeProvider;
+
+  final UriConverter _uriConverter;
+
+  final ContextRoot _contextRoot;
+
+  _AnalyzerDriver._(
+      this._session, this._typeProvider, this._uriConverter, this._contextRoot);
+
+  Future<ComparisonNode> analyzeFiles(Iterable<String> filePaths) async {
+    var libraryNodes = <ComparisonNode>[];
+    for (var filePath in filePaths) {
+      var kind = await _session.getSourceKind(filePath);
+      if (kind == SourceKind.LIBRARY) {
+        var importUri = _uriConverter.pathToUri(filePath);
+        var libraryElement =
+            await _session.getLibraryByUri(importUri.toString());
+        var childNodes = <ComparisonNode>[];
+        if (libraryElement.name.isNotEmpty) {
+          childNodes.add(ComparisonNode('name=${libraryElement.name}'));
+        }
+        for (var compilationUnit in libraryElement.units) {
+          var unitResult =
+              await _session.getResolvedAst(compilationUnit.source.fullName);
+          _AnalyzerVisitor(_typeProvider, childNodes)
+              ._visitList(unitResult.unit.declarations);
+        }
+        libraryNodes
+            .add(ComparisonNode.sorted(importUri.toString(), childNodes));
       }
-      for (var compilationUnit in libraryElement.units) {
-        var unitResult =
-            await session.getResolvedAst(compilationUnit.source.fullName);
-        _AnalyzerVisitor(typeProvider, childNodes)
-            ._visitList(unitResult.unit.declarations);
-      }
-      libraryNodes.add(ComparisonNode.sorted(importUri.toString(), childNodes));
     }
+    return ComparisonNode.sorted('Component', libraryNodes);
   }
-  return ComparisonNode.sorted('Component', libraryNodes);
+
+  Future<ComparisonNode> analyzePackage() async {
+    return analyzeFiles(_contextRoot.analyzedFiles());
+  }
+
+  static Future<_AnalyzerDriver> create(String startingPath) async {
+    var contextCollection =
+        AnalysisContextCollection(includedPaths: [startingPath]);
+    var contexts = contextCollection.contexts;
+    if (contexts.length != 1) {
+      throw new StateError('Expected exactly one context');
+    }
+    var context = contexts[0];
+    var session = context.currentSession;
+    var typeProvider = await session.typeProvider;
+    var uriConverter = session.uriConverter;
+    var contextRoot = context.contextRoot;
+    return _AnalyzerDriver._(session, typeProvider, uriConverter, contextRoot);
+  }
 }
 
 /// Visitor for serializing the contents of an analyzer AST into
diff --git a/pkg/analyzer_fe_comparison/lib/src/kernel.dart b/pkg/analyzer_fe_comparison/lib/src/kernel.dart
index 935dd2c..d9d8c89 100644
--- a/pkg/analyzer_fe_comparison/lib/src/kernel.dart
+++ b/pkg/analyzer_fe_comparison/lib/src/kernel.dart
@@ -13,22 +13,10 @@
 
 /// Compiles the given [inputs] to kernel using the front_end, and returns a
 /// [ComparisonNode] representing them.
-Future<ComparisonNode> driveKernel(
+Future<ComparisonNode> analyzePackage(
     List<Uri> inputs, Uri packagesFileUri, Uri platformUri) async {
-  var targetFlags = TargetFlags(strongMode: true, syncAsync: true);
-  var target = NoneTarget(targetFlags);
-  var fileSystem = StandardFileSystem.instance;
-
-  var compilerOptions = CompilerOptions()
-    ..fileSystem = fileSystem
-    ..packagesFileUri = packagesFileUri
-    ..sdkSummary = platformUri
-    ..strongMode = true
-    ..target = target
-    ..throwOnErrorsForDebugging = true
-    ..embedSourceText = false;
-
-  var component = await kernelForComponent(inputs, compilerOptions);
+  var component = await kernelForComponent(
+      inputs, _makeCompilerOptions(packagesFileUri, platformUri));
   var libraryNodes = <ComparisonNode>[];
   var visitor = _KernelVisitor(libraryNodes);
   for (var library in component.libraries) {
@@ -39,6 +27,39 @@
   return ComparisonNode.sorted('Component', libraryNodes);
 }
 
+/// Compiles the given [input] to kernel using the front_end, and returns a
+/// [ComparisonNode] representing it.
+///
+/// Only libraries whose URI passes the [uriFilter] are included in the results.
+Future<ComparisonNode> analyzeProgram(Uri input, Uri packagesFileUri,
+    Uri platformUri, bool uriFilter(Uri uri)) async {
+  var component = await kernelForProgram(
+      input, _makeCompilerOptions(packagesFileUri, platformUri));
+  var libraryNodes = <ComparisonNode>[];
+  var visitor = _KernelVisitor(libraryNodes);
+  for (var library in component.libraries) {
+    if (uriFilter(library.importUri)) {
+      library.accept(visitor);
+    }
+  }
+  return ComparisonNode.sorted('Component', libraryNodes);
+}
+
+CompilerOptions _makeCompilerOptions(Uri packagesFileUri, Uri platformUri) {
+  var targetFlags = TargetFlags(strongMode: true, syncAsync: true);
+  var target = NoneTarget(targetFlags);
+  var fileSystem = StandardFileSystem.instance;
+
+  return CompilerOptions()
+    ..fileSystem = fileSystem
+    ..packagesFileUri = packagesFileUri
+    ..sdkSummary = platformUri
+    ..strongMode = true
+    ..target = target
+    ..throwOnErrorsForDebugging = false
+    ..embedSourceText = false;
+}
+
 /// Visitor for serializing a kernel representation of a program into
 /// ComparisonNodes.
 ///
diff --git a/pkg/analyzer_fe_comparison/tool/compare_packages.dart b/pkg/analyzer_fe_comparison/tool/compare_packages.dart
index efb0efe..e1a7b71 100644
--- a/pkg/analyzer_fe_comparison/tool/compare_packages.dart
+++ b/pkg/analyzer_fe_comparison/tool/compare_packages.dart
@@ -19,7 +19,7 @@
   var dillPath = path.join(buildPath, 'vm_platform_strong.dill');
   var analyzerLibPath = path.join(sdkRepoPath, 'pkg', 'analyzer', 'lib');
   var packagesFilePath = path.join(sdkRepoPath, '.packages');
-  compare(dillPath, analyzerLibPath, packagesFilePath);
+  comparePackages(dillPath, analyzerLibPath, packagesFilePath);
 }
 
 Future<String> _findBuildDir(String sdkRepoPath, String targetName) async {
diff --git a/tools/testing/dart/command_output.dart b/tools/testing/dart/command_output.dart
index 7e53d0e..d26a4dd 100644
--- a/tools/testing/dart/command_output.dart
+++ b/tools/testing/dart/command_output.dart
@@ -584,8 +584,12 @@
     if (hasTimedOut) return Expectation.timeout;
     if (hasNonUtf8) return Expectation.nonUtf8Error;
 
-    // TODO(paulberry): parse command output
-    return Expectation.pass;
+    if (exitCode != 0) return Expectation.fail;
+    for (var line in decodeUtf8(this.stdout).split('\n')) {
+      if (line.indexOf('No differences found') != -1) return Expectation.pass;
+      if (line.indexOf('Differences found') != -1) return Expectation.fail;
+    }
+    return Expectation.fail;
   }
 
   /// Cloned code from member result(), with changes.
@@ -596,8 +600,12 @@
     if (hasTimedOut) return Expectation.timeout;
     if (hasNonUtf8) return Expectation.nonUtf8Error;
 
-    // TODO(paulberry): parse command output
-    return Expectation.pass;
+    if (exitCode != 0) return Expectation.fail;
+    for (var line in decodeUtf8(this.stdout).split('\n')) {
+      if (line.indexOf('No differences found') != -1) return Expectation.pass;
+      if (line.indexOf('Differences found') != -1) return Expectation.fail;
+    }
+    return Expectation.fail;
   }
 }