Remove dependency on expected_output; fold impl into tool/ (#346)

Remove dependency on expected_output; fold impl into tool/
diff --git a/pubspec.yaml b/pubspec.yaml
index bf43821..325b6a1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -21,7 +21,6 @@
   build_version: ^2.0.0
   build_web_compilers: '>=1.0.0 <3.0.0'
   collection: ^1.2.0
-  expected_output: ^1.2.1
   html: '>=0.12.2 <0.15.0'
   io: ^0.3.2+1
   js: ^0.6.1
diff --git a/test/markdown_test.dart b/test/markdown_test.dart
index 524c9b9..03785c8 100644
--- a/test/markdown_test.dart
+++ b/test/markdown_test.dart
@@ -7,8 +7,8 @@
 
 import 'util.dart';
 
-void main() {
-  testDirectory('original');
+void main() async {
+  await testDirectory('original');
 
   // Block syntax extensions
   testFile('extensions/fenced_code_blocks.unit',
@@ -25,8 +25,8 @@
   testFile('extensions/strikethrough.unit',
       inlineSyntaxes: [StrikethroughSyntax()]);
 
-  testDirectory('common_mark');
-  testDirectory('gfm', extensionSet: ExtensionSet.gitHubFlavored);
+  await testDirectory('common_mark');
+  await testDirectory('gfm', extensionSet: ExtensionSet.gitHubFlavored);
 
   group('Corner cases', () {
     validateCore('Incorrect Links', '''
diff --git a/test/util.dart b/test/util.dart
index 7d011de..90f0c74 100644
--- a/test/util.dart
+++ b/test/util.dart
@@ -5,21 +5,17 @@
 // Used by `dataCasesUnder` below to find the current directory.
 library markdown.test.util;
 
-import 'dart:mirrors';
+import 'dart:isolate';
 
-import 'package:expected_output/expected_output.dart';
 import 'package:io/ansi.dart' as ansi;
 import 'package:markdown/markdown.dart';
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
+import '../tool/expected_output.dart';
 
 /// Run tests defined in "*.unit" files inside directory [name].
-void testDirectory(
-  String name, {
-  ExtensionSet extensionSet,
-}) {
-  for (var dataCase
-      in dataCasesUnder(library: #markdown.test.util, subdirectory: name)) {
+Future<void> testDirectory(String name, {ExtensionSet extensionSet}) async {
+  await for (var dataCase in dataCasesUnder(testDirectory: name)) {
     var description =
         '${dataCase.directory}/${dataCase.file}.unit ${dataCase.description}';
     validateCore(
@@ -31,15 +27,14 @@
   }
 }
 
-// Locate the "test" directory. Use mirrors so that this works with the test
-// package, which loads this suite into an isolate.
-String get _testDir =>
-    p.dirname(currentMirrorSystem().findLibrary(#markdown.test.util).uri.path);
-
 void testFile(String file,
     {Iterable<BlockSyntax> blockSyntaxes,
-    Iterable<InlineSyntax> inlineSyntaxes}) {
-  for (var dataCase in dataCasesInFile(path: p.join(_testDir, file))) {
+    Iterable<InlineSyntax> inlineSyntaxes}) async {
+  var markdownLibRoot = p.dirname((await Isolate.resolvePackageUri(
+          Uri.parse('package:markdown/markdown.dart')))
+      .path);
+  var directory = p.join(p.dirname(markdownLibRoot), 'test');
+  for (var dataCase in dataCasesInFile(path: p.join(directory, file))) {
     var description =
         '${dataCase.directory}/${dataCase.file}.unit ${dataCase.description}';
     validateCore(description, dataCase.input, dataCase.expectedOutput,
diff --git a/tool/expected_output.dart b/tool/expected_output.dart
new file mode 100644
index 0000000..a85ccde
--- /dev/null
+++ b/tool/expected_output.dart
@@ -0,0 +1,158 @@
+// 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 'dart:isolate';
+
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+
+/// Parse and yield data cases (each a [DataCase]) from [path].
+Iterable<DataCase> dataCasesInFile({String path, String baseDir}) sync* {
+  var file = p.basename(path).replaceFirst(RegExp(r'\..+$'), '');
+  baseDir ??= p.relative(p.dirname(path), from: p.dirname(p.dirname(path)));
+
+  // Explicitly create a File, in case the entry is a Link.
+  var lines = File(path).readAsLinesSync();
+
+  var frontMatter = StringBuffer();
+
+  var i = 0;
+
+  while (!lines[i].startsWith('>>>')) {
+    frontMatter.write('${lines[i++]}\n');
+  }
+
+  while (i < lines.length) {
+    var description = lines[i++].replaceFirst(RegExp(r'>>>\s*'), '').trim();
+    var skip = description.startsWith('skip:');
+    if (description == '') {
+      description = 'line ${i + 1}';
+    } else {
+      description = 'line ${i + 1}: $description';
+    }
+
+    var input = '';
+    while (!lines[i].startsWith('<<<')) {
+      input += lines[i++] + '\n';
+    }
+
+    var expectedOutput = '';
+    while (++i < lines.length && !lines[i].startsWith('>>>')) {
+      expectedOutput += lines[i] + '\n';
+    }
+
+    var dataCase = DataCase(
+        directory: baseDir,
+        file: file,
+        front_matter: frontMatter.toString(),
+        description: description,
+        skip: skip,
+        input: input,
+        expectedOutput: expectedOutput);
+    yield dataCase;
+  }
+}
+
+/// Parse and return data cases (each a [DataCase]) from [directory].
+///
+/// By default, only read data cases from files with a `.unit` extension. Data
+/// cases are read from files located immediately in [directory], or
+/// recursively, according to [recursive].
+Iterable<DataCase> _dataCases({
+  String directory,
+  String extension = 'unit',
+  bool recursive = true,
+}) {
+  var entries =
+      Directory(directory).listSync(recursive: recursive, followLinks: false);
+  var results = <DataCase>[];
+  for (var entry in entries) {
+    if (!entry.path.endsWith(extension)) {
+      continue;
+    }
+
+    var relativeDir =
+        p.relative(p.dirname(entry.path), from: p.dirname(directory));
+
+    results.addAll(dataCasesInFile(path: entry.path, baseDir: relativeDir));
+  }
+
+  // The API makes no guarantees on order. This is just here for stability in
+  // tests.
+  results.sort((a, b) {
+    var compare = a.directory.compareTo(b.directory);
+    if (compare != 0) return compare;
+
+    return a.file.compareTo(b.file);
+  });
+  return results;
+}
+
+/// Parse and yield data cases (each a [DataCase]) from the directory containing
+/// [library], optionally under [subdirectory].
+///
+/// By default, only read data cases from files with a `.unit` extension. Data
+/// cases are read from files located immediately in [directory], or
+/// recursively, according to [recursive].
+///
+/// The typical use case of this method is to declare a library at the top of a
+/// Dart test file, then reference the symbol with a pound sign. Example:
+///
+/// ```dart
+/// library my_package.test.this_test;
+///
+/// import 'package:expected_output/expected_output.dart';
+/// import 'package:test/test.dart';
+///
+/// void main() {
+///   for (var dataCase in dataCasesUnder(library: #my_package.test.this_test)) {
+///     // ...
+///   }
+/// }
+/// ```
+Stream<DataCase> dataCasesUnder({
+  @required String testDirectory,
+  String extension = 'unit',
+  bool recursive = true,
+}) async* {
+  var markdownLibRoot = p.dirname((await Isolate.resolvePackageUri(
+          Uri.parse('package:markdown/markdown.dart')))
+      .path);
+  var directory =
+      p.joinAll([p.dirname(markdownLibRoot), 'test', testDirectory]);
+  for (var dataCase in _dataCases(
+      directory: directory, extension: extension, recursive: recursive)) {
+    yield dataCase;
+  }
+}
+
+/// All of the data pertaining to a particular test case, namely the [input] and
+/// [expectedOutput].
+class DataCase {
+  final String directory;
+  final String file;
+
+  // ignore: non_constant_identifier_names
+  final String front_matter;
+  final String description;
+  final bool skip;
+  final String input;
+  final String expectedOutput;
+
+  DataCase({
+    this.directory,
+    this.file,
+    // ignore: non_constant_identifier_names
+    this.front_matter,
+    this.description,
+    this.skip,
+    this.input,
+    this.expectedOutput,
+  });
+
+  /// A good standard description for `test()`, derived from the data directory,
+  /// the particular data file, and the test case description.
+  String get testDescription => [directory, file, description].join(' ');
+}
diff --git a/tool/stats.dart b/tool/stats.dart
index 0edabca..a5c9a3f 100644
--- a/tool/stats.dart
+++ b/tool/stats.dart
@@ -5,9 +5,9 @@
 
 import 'package:args/args.dart';
 import 'package:collection/collection.dart';
-import 'package:expected_output/expected_output.dart';
 import 'package:path/path.dart' as p;
 
+import '../tool/expected_output.dart';
 import 'stats_lib.dart';
 
 final _configs =