diff --git a/bin/dartdoc.dart b/bin/dartdoc.dart
index f73248a22a..83b3f6180f 100644
--- a/bin/dartdoc.dart
+++ b/bin/dartdoc.dart
@@ -17,7 +17,9 @@ Future<void> main(List<String> arguments) async {
     // There was an error while parsing options.
     return;
   }
-  final packageBuilder = PubPackageBuilder(config, pubPackageMetaProvider);
+  final packageConfigProvider = PhysicalPackageConfigProvider();
+  final packageBuilder =
+      PubPackageBuilder(config, pubPackageMetaProvider, packageConfigProvider);
   final dartdoc = config.generateDocs
       ? await Dartdoc.fromContext(config, packageBuilder)
       : await Dartdoc.withEmptyGenerator(config, packageBuilder);
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index 016304fedb..63d09b1a05 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -33,6 +33,7 @@ export 'package:dartdoc/src/dartdoc_options.dart';
 export 'package:dartdoc/src/element_type.dart';
 export 'package:dartdoc/src/generator/generator.dart';
 export 'package:dartdoc/src/model/model.dart';
+export 'package:dartdoc/src/package_config_provider.dart';
 export 'package:dartdoc/src/package_meta.dart';
 
 const String programName = 'dartdoc';
diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart
index a2e07cc8fd..cfb1ee94a7 100644
--- a/lib/src/dartdoc_options.dart
+++ b/lib/src/dartdoc_options.dart
@@ -669,8 +669,8 @@ abstract class DartdocOption<T> {
   String get _directoryCurrentPath => resourceProvider.pathContext.current;
 
   /// Calls [valueAt] on the directory this element is defined in.
-  T valueAtElement(Element element) => valueAt(resourceProvider.getFolder(
-      resourceProvider.pathContext.canonicalize(
+  T valueAtElement(Element element) =>
+      valueAt(resourceProvider.getFolder(resourceProvider.pathContext.normalize(
           resourceProvider.pathContext.basename(element.source.fullName))));
 
   /// Adds a DartdocOption to the children of this DartdocOption.
@@ -1683,7 +1683,7 @@ Future<List<DartdocOption<Object>>> createDartdocOptions(
         return resourceProvider.pathContext
             .join(flutterRoot, 'bin', 'cache', 'dart-sdk');
       }
-      return resourceProvider.defaultSdkDir.path;
+      return packageMetaProvider.defaultSdkDir.path;
     }, packageMetaProvider.resourceProvider,
         help: 'Path to the SDK directory.', isDir: true, mustExist: true),
     DartdocOptionArgFile<bool>(
diff --git a/lib/src/io_utils.dart b/lib/src/io_utils.dart
index c4a3cfaf18..27fcbeaa26 100644
--- a/lib/src/io_utils.dart
+++ b/lib/src/io_utils.dart
@@ -11,7 +11,8 @@ import 'dart:io' as io;
 
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/physical_file_system.dart';
-import 'package:dartdoc/src/package_meta.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer/src/test_utilities/mock_sdk.dart';
 import 'package:path/path.dart' as path;
 
 Encoding utf8AllowMalformed = Utf8Codec(allowMalformed: true);
@@ -34,6 +35,14 @@ String resolveTildePath(String originalPath) {
   return path.join(homeDir, originalPath.substring(2));
 }
 
+bool isSdkLibraryDocumented(SdkLibrary library) {
+  if (library is MockSdkLibrary) {
+    // Not implemented in [MockSdkLibrary].
+    return true;
+  }
+  return library.isDocumented;
+}
+
 extension ResourceProviderExtensions on ResourceProvider {
   Folder createSystemTemp(String prefix) {
     if (this is PhysicalResourceProvider) {
@@ -43,20 +52,6 @@ extension ResourceProviderExtensions on ResourceProvider {
     }
   }
 
-  Folder get defaultSdkDir {
-    if (this is PhysicalResourceProvider) {
-      var sdkDir = getFile(pathContext.absolute(io.Platform.resolvedExecutable))
-          .parent
-          .parent;
-      assert(pathContext.equals(
-          sdkDir.path, PubPackageMeta.sdkDirParent(sdkDir, this).path));
-      return sdkDir;
-    } else {
-      // TODO(srawlins): Return what is needed for tests.
-      return null;
-    }
-  }
-
   String get resolvedExecutable {
     if (this is PhysicalResourceProvider) {
       return io.Platform.resolvedExecutable;
@@ -94,48 +89,6 @@ extension ResourceProviderExtensions on ResourceProvider {
   }
 }
 
-/// Lists the contents of [dir].
-///
-/// If [recursive] is `true`, lists subdirectory contents (defaults to `false`).
-///
-/// Excludes files and directories beginning with `.`
-///
-/// The returned paths are guaranteed to begin with [dir].
-Iterable<String> listDir(String dir,
-    {bool recursive = false,
-    Iterable<io.FileSystemEntity> Function(io.Directory dir) listDir}) {
-  listDir ??= (io.Directory dir) => dir.listSync();
-
-  return _doList(dir, <String>{}, recursive, listDir);
-}
-
-Iterable<String> _doList(
-    String dir,
-    Set<String> listedDirectories,
-    bool recurse,
-    Iterable<io.FileSystemEntity> Function(io.Directory dir) listDir) sync* {
-  // Avoid recursive symlinks.
-  var resolvedPath = io.Directory(dir).resolveSymbolicLinksSync();
-  if (!listedDirectories.contains(resolvedPath)) {
-    listedDirectories = Set<String>.from(listedDirectories);
-    listedDirectories.add(resolvedPath);
-
-    for (var entity in listDir(io.Directory(dir))) {
-      // Skip hidden files and directories
-      if (path.basename(entity.path).startsWith('.')) {
-        continue;
-      }
-
-      yield entity.path;
-      if (entity is io.Directory) {
-        if (recurse) {
-          yield* _doList(entity.path, listedDirectories, recurse, listDir);
-        }
-      }
-    }
-  }
-}
-
 /// Converts `.` and `:` into `-`, adding a ".html" extension.
 ///
 /// For example:
diff --git a/lib/src/model/library.dart b/lib/src/model/library.dart
index d6f2a43b6c..2b7a0c1640 100644
--- a/lib/src/model/library.dart
+++ b/lib/src/model/library.dart
@@ -10,6 +10,7 @@ import 'package:analyzer/dart/element/visitor.dart';
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
 import 'package:analyzer/src/generated/sdk.dart';
+import 'package:dartdoc/src/io_utils.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/package_meta.dart' show PackageMeta;
 import 'package:dartdoc/src/quiver.dart' as quiver;
@@ -212,7 +213,8 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
   @override
   bool get isPublic {
     if (!super.isPublic) return false;
-    if (sdkLib != null && (sdkLib.isInternal || !sdkLib.isDocumented)) {
+    if (sdkLib != null &&
+        (sdkLib.isInternal || !isSdkLibraryDocumented(sdkLib))) {
       return false;
     }
     if (config.isLibraryExcluded(name) ||
diff --git a/lib/src/model/package.dart b/lib/src/model/package.dart
index a8053e517a..c5a4fafcba 100644
--- a/lib/src/model/package.dart
+++ b/lib/src/model/package.dart
@@ -12,7 +12,9 @@ import 'package:dartdoc/src/warnings.dart';
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 
-final RegExp substituteNameVersion = RegExp(r'%([bnv])%');
+@Deprecated('Public variable intended to be private; will be removed as early '
+    'as Dartdoc 1.0.0')
+RegExp get substituteNameVersion => Package._substituteNameVersion;
 
 // All hrefs are emitted as relative paths from the output root. We are unable
 // to compute them from the page we are generating, and many properties computed
@@ -224,51 +226,56 @@ class Package extends LibraryContainer
   String _baseHref;
 
   String get baseHref {
-    if (_baseHref == null) {
-      if (documentedWhere == DocumentLocation.remote) {
-        _baseHref =
-            config.linkToUrl.replaceAllMapped(substituteNameVersion, (m) {
-          switch (m.group(1)) {
-            // Return the prerelease tag of the release if a prerelease,
-            // or 'stable' otherwise. Mostly coded around
-            // the Dart SDK's use of dev/stable, but theoretically applicable
-            // elsewhere.
-            case 'b':
-              {
-                var version = Version.parse(packageMeta.version);
-                var tag = 'stable';
-                if (version.isPreRelease) {
-                  // version.preRelease is a List<dynamic> with a mix of
-                  // integers and strings.  Given this, handle
-                  // 2.8.0-dev.1.0, 2.9.0-1.0.dev, and similar
-                  // variations.
-                  tag = version.preRelease.whereType<String>().first;
-                  // Who knows about non-SDK packages, but assert that SDKs
-                  // must conform to the known format.
-                  assert(
-                      packageMeta.isSdk == false || int.tryParse(tag) == null,
-                      'Got an integer as string instead of the expected "dev" tag');
-                }
-                return tag;
-              }
-            case 'n':
-              return name;
-            // The full version string of the package.
-            case 'v':
-              return packageMeta.version;
-            default:
-              assert(false, 'Unsupported case: ${m.group(1)}');
-              return null;
-          }
-        });
-        if (!_baseHref.endsWith('/')) _baseHref = '${_baseHref}/';
-      } else {
-        _baseHref = config.useBaseHref ? '' : HTMLBASE_PLACEHOLDER;
-      }
+    if (_baseHref != null) {
+      return _baseHref;
     }
+
+    if (documentedWhere == DocumentLocation.remote) {
+      _baseHref = _remoteBaseHref;
+      if (!_baseHref.endsWith('/')) _baseHref = '${_baseHref}/';
+    } else {
+      _baseHref = config.useBaseHref ? '' : HTMLBASE_PLACEHOLDER;
+    }
+
     return _baseHref;
   }
 
+  String get _remoteBaseHref {
+    return config.linkToUrl.replaceAllMapped(_substituteNameVersion, (m) {
+      switch (m.group(1)) {
+        // Return the prerelease tag of the release if a prerelease, or 'stable'
+        // otherwise.  Mostly coded around the Dart SDK's use of dev/stable, but
+        // theoretically applicable elsewhere.
+        case 'b':
+          {
+            var version = Version.parse(packageMeta.version);
+            var tag = 'stable';
+            if (version.isPreRelease) {
+              // `version.preRelease` is a `List<dynamic>` with a mix of
+              // integers and strings.  Given this, handle
+              // "2.8.0-dev.1.0, 2.9.0-1.0.dev", and similar variations.
+              tag = version.preRelease.whereType<String>().first;
+              // Who knows about non-SDK packages, but SDKs must conform to the
+              // known format.
+              assert(packageMeta.isSdk == false || int.tryParse(tag) == null,
+                  'Got an integer as string instead of the expected "dev" tag');
+            }
+            return tag;
+          }
+        case 'n':
+          return name;
+        // The full version string of the package.
+        case 'v':
+          return packageMeta.version;
+        default:
+          assert(false, 'Unsupported case: ${m.group(1)}');
+          return null;
+      }
+    });
+  }
+
+  static final _substituteNameVersion = RegExp(r'%([bnv])%');
+
   @override
   String get href => '$baseHref$filePath';
 
diff --git a/lib/src/model/package_builder.dart b/lib/src/model/package_builder.dart
index 57b863799a..54519d9297 100644
--- a/lib/src/model/package_builder.dart
+++ b/lib/src/model/package_builder.dart
@@ -3,14 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:io';
 
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/element/element.dart';
-import 'package:analyzer/file_system/file_system.dart' as file_system;
-import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/context/builder.dart';
+import 'package:analyzer/src/context/packages.dart';
 import 'package:analyzer/src/dart/analysis/byte_store.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
@@ -21,18 +20,17 @@ import 'package:analyzer/src/generated/java_io.dart';
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/generated/source_io.dart';
-import 'package:dartdoc/src/quiver.dart' as quiver;
 import 'package:analyzer/src/source/package_map_resolver.dart';
 import 'package:dartdoc/src/dartdoc_options.dart';
-import 'package:dartdoc/src/io_utils.dart';
 import 'package:dartdoc/src/logging.dart';
 import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/quiver.dart' as quiver;
+import 'package:dartdoc/src/package_config_provider.dart';
 import 'package:dartdoc/src/package_meta.dart'
     show PackageMeta, PackageMetaProvider;
 import 'package:dartdoc/src/render/renderer_factory.dart';
 import 'package:dartdoc/src/special_elements.dart';
 import 'package:meta/meta.dart';
-import 'package:package_config/package_config.dart' show findPackageConfig;
 import 'package:path/path.dart' as path;
 
 /// Everything you need to instantiate a PackageGraph object for documenting.
@@ -45,8 +43,10 @@ abstract class PackageBuilder {
 class PubPackageBuilder implements PackageBuilder {
   final DartdocOptionContext config;
   final PackageMetaProvider packageMetaProvider;
+  final PackageConfigProvider packageConfigProvider;
 
-  PubPackageBuilder(this.config, this.packageMetaProvider);
+  PubPackageBuilder(
+      this.config, this.packageMetaProvider, this.packageConfigProvider);
 
   @override
   Future<PackageGraph> buildPackageGraph() async {
@@ -78,11 +78,13 @@ class PubPackageBuilder implements PackageBuilder {
     return newGraph;
   }
 
-  FolderBasedDartSdk _sdk;
+  /*late final*/ DartSdk _sdk;
+
+  DartSdk get sdk {
+    _sdk ??= packageMetaProvider.defaultSdk ??
+        FolderBasedDartSdk(
+            resourceProvider, resourceProvider.getFolder(config.sdkDir));
 
-  FolderBasedDartSdk get sdk {
-    _sdk ??= FolderBasedDartSdk(PhysicalResourceProvider.INSTANCE,
-        PhysicalResourceProvider.INSTANCE.getFolder(config.sdkDir));
     return _sdk;
   }
 
@@ -90,30 +92,32 @@ class PubPackageBuilder implements PackageBuilder {
 
   EmbedderSdk get embedderSdk {
     if (_embedderSdk == null && !config.topLevelPackageMeta.isSdk) {
-      _embedderSdk = EmbedderSdk(PhysicalResourceProvider.INSTANCE,
-          EmbedderYamlLocator(_packageMap).embedderYamls);
+      _embedderSdk = EmbedderSdk(
+          resourceProvider, EmbedderYamlLocator(_packageMap).embedderYamls);
     }
     return _embedderSdk;
   }
 
+  ResourceProvider get resourceProvider => packageMetaProvider.resourceProvider;
+
   Future<void> _calculatePackageMap() async {
     assert(_packageMap == null);
-    _packageMap = <String, List<file_system.Folder>>{};
-    file_system.Folder cwd =
-        PhysicalResourceProvider.INSTANCE.getResource(config.inputDir);
-    var info = await findPackageConfig(Directory(cwd.path));
+    _packageMap = <String, List<Folder>>{};
+    Folder cwd = resourceProvider.getResource(config.inputDir);
+    var info = await packageConfigProvider
+        .findPackageConfig(resourceProvider.getFolder(cwd.path));
     if (info == null) return;
 
     for (var package in info.packages) {
       var packagePath = path.normalize(path.fromUri(package.packageUriRoot));
-      var resource = PhysicalResourceProvider.INSTANCE.getResource(packagePath);
-      if (resource is file_system.Folder) {
+      var resource = resourceProvider.getResource(packagePath);
+      if (resource is Folder) {
         _packageMap[package.name] = [resource];
       }
     }
   }
 
-  /*late final*/ Map<String, List<file_system.Folder>> _packageMap;
+  /*late final*/ Map<String, List<Folder>> _packageMap;
 
   DartUriResolver _embedderResolver;
 
@@ -123,9 +127,8 @@ class PubPackageBuilder implements PackageBuilder {
   }
 
   SourceFactory get sourceFactory {
-    var resolvers = <UriResolver>[];
     final UriResolver packageResolver =
-        PackageMapUriResolver(PhysicalResourceProvider.INSTANCE, _packageMap);
+        PackageMapUriResolver(resourceProvider, _packageMap);
     UriResolver sdkResolver;
     if (embedderSdk == null || embedderSdk.urlMappings.isEmpty) {
       // The embedder uri resolver has no mappings. Use the default Dart SDK
@@ -141,15 +144,15 @@ class PubPackageBuilder implements PackageBuilder {
     /// never resolve to embedded SDK files, and the resolvers list must still
     /// contain a DartUriResolver.  This hack won't be necessary once analyzer
     /// has a clean public API.
-    resolvers.add(PackageWithoutSdkResolver(packageResolver, sdkResolver));
-    resolvers.add(sdkResolver);
-    resolvers.add(
-        file_system.ResourceUriResolver(PhysicalResourceProvider.INSTANCE));
+    var resolvers = [
+      PackageWithoutSdkResolver(packageResolver, sdkResolver),
+      sdkResolver,
+      ResourceUriResolver(resourceProvider),
+    ];
 
     assert(
         resolvers.any((UriResolver resolver) => resolver is DartUriResolver));
-    var sourceFactory = SourceFactory(resolvers);
-    return sourceFactory;
+    return SourceFactory(resolvers);
   }
 
   AnalysisDriver _driver;
@@ -166,15 +169,9 @@ class PubPackageBuilder implements PackageBuilder {
       // TODO(jcollins-g): Make use of currently not existing API for managing
       //                   many AnalysisDrivers
       // TODO(jcollins-g): make use of DartProject isApi()
-      _driver = AnalysisDriver(
-          scheduler,
-          log,
-          PhysicalResourceProvider.INSTANCE,
-          MemoryByteStore(),
-          FileContentOverlay(),
-          null,
-          sourceFactory,
-          options);
+      _driver = AnalysisDriver(scheduler, log, resourceProvider,
+          MemoryByteStore(), FileContentOverlay(), null, sourceFactory, options,
+          packages: Packages.empty);
       driver.results.listen((_) => logProgress(''));
       driver.exceptions.listen((_) {});
       scheduler.start();
@@ -200,7 +197,9 @@ class PubPackageBuilder implements PackageBuilder {
 
     if (name.startsWith(directoryCurrentPath)) {
       name = name.substring(directoryCurrentPath.length);
-      if (name.startsWith(Platform.pathSeparator)) name = name.substring(1);
+      if (name.startsWith(resourceProvider.pathContext.separator)) {
+        name = name.substring(1);
+      }
     }
     var javaFile = JavaFile(filePath).getAbsoluteFile();
     Source source = FileBasedSource(javaFile);
@@ -305,7 +304,8 @@ class PubPackageBuilder implements PackageBuilder {
     var packageDirs = {basePackageDir};
 
     if (autoIncludeDependencies) {
-      var info = await findPackageConfig(Directory(basePackageDir));
+      var info = await packageConfigProvider
+          .findPackageConfig(resourceProvider.getFolder(basePackageDir));
       for (var package in info.packages) {
         if (!filterExcludes || !config.exclude.contains(package.name)) {
           packageDirs.add(
@@ -322,7 +322,7 @@ class PubPackageBuilder implements PackageBuilder {
       // containing '/packages' will be added. The only exception is if the file
       // to analyze already has a '/package' in its path.
       for (var lib
-          in listDir(packageDir, recursive: true, listDir: _packageDirList)) {
+          in _listDir(packageDir, recursive: true, listDir: _packageDirList)) {
         if (lib.endsWith('.dart') &&
             (!lib.contains('${sep}packages${sep}') ||
                 packageDir.contains('${sep}packages${sep}'))) {
@@ -330,7 +330,7 @@ class PubPackageBuilder implements PackageBuilder {
           if (path.isWithin(packageLibDir, lib) &&
               !path.isWithin(packageLibSrcDir, lib)) {
             // Only add the file if it does not contain 'part of'.
-            var contents = File(lib).readAsStringSync();
+            var contents = resourceProvider.getFile(lib).readAsStringSync();
 
             if (contents.startsWith('part of ') ||
                 contents.contains('\npart of ')) {
@@ -344,6 +344,44 @@ class PubPackageBuilder implements PackageBuilder {
     }
   }
 
+  /// Lists the contents of [dir].
+  ///
+  /// If [recursive] is `true`, lists subdirectory contents (defaults to `false`).
+  ///
+  /// Excludes files and directories beginning with `.`
+  ///
+  /// The returned paths are guaranteed to begin with [dir].
+  Iterable<String> _listDir(String dir,
+      {bool recursive = false,
+      Iterable<Resource> Function(Folder dir) listDir}) {
+    listDir ??= (Folder dir) => dir.getChildren();
+
+    return _doList(dir, <String>{}, recursive, listDir);
+  }
+
+  Iterable<String> _doList(String dir, Set<String> listedDirectories,
+      bool recurse, Iterable<Resource> Function(Folder dir) listDir) sync* {
+    // Avoid recursive symlinks.
+    var resolvedPath =
+        resourceProvider.getFolder(dir).resolveSymbolicLinksSync().path;
+    if (!listedDirectories.contains(resolvedPath)) {
+      listedDirectories = Set<String>.from(listedDirectories);
+      listedDirectories.add(resolvedPath);
+
+      for (var resource in listDir(resourceProvider.getFolder(dir))) {
+        // Skip hidden files and directories
+        if (path.basename(resource.path).startsWith('.')) {
+          continue;
+        }
+
+        yield resource.path;
+        if (resource is Folder && recurse) {
+          yield* _doList(resource.path, listedDirectories, recurse, listDir);
+        }
+      }
+    }
+  }
+
   /// Calculate includeExternals based on a list of files.  Assumes each
   /// file might be part of a [DartdocOptionContext], and loads those
   /// objects to find any [DartdocOptionContext.includeExternal] configurations
@@ -369,7 +407,8 @@ class PubPackageBuilder implements PackageBuilder {
     }
     files = quiver.concat([files, _includeExternalsFrom(files)]);
     return {
-      ...files.map((s) => File(s).absolute.path),
+      ...files.map((s) => resourceProvider.pathContext
+          .absolute(resourceProvider.getFile(s).path)),
       ...getEmbedderSdkFiles(),
     };
   }
@@ -377,7 +416,9 @@ class PubPackageBuilder implements PackageBuilder {
   Iterable<String> getEmbedderSdkFiles() {
     return [
       for (var dartUri in _embedderSdkUris)
-        File(embedderSdk.mapDartUri(dartUri).fullName).absolute.path
+        resourceProvider.pathContext.absolute(resourceProvider
+            .getFile(embedderSdk.mapDartUri(dartUri).fullName)
+            .path),
     ];
   }
 
@@ -390,12 +431,12 @@ class PubPackageBuilder implements PackageBuilder {
   }
 
   Future<void> getLibraries(PackageGraph uninitializedPackageGraph) async {
-    DartSdk findSpecialsSdk = sdk;
+    var findSpecialsSdk = sdk;
     if (embedderSdk != null && embedderSdk.urlMappings.isNotEmpty) {
       findSpecialsSdk = embedderSdk;
     }
     var files = await _getFiles();
-    var specialFiles = specialLibraryFiles(findSpecialsSdk).toSet();
+    var specialFiles = specialLibraryFiles(findSpecialsSdk);
 
     /// Returns true if this library element should be included according
     /// to the configuration.
@@ -429,21 +470,21 @@ class PubPackageBuilder implements PackageBuilder {
   /// it like a package and only return the `lib` dir.
   ///
   /// This ensures that packages don't have non-`lib` content documented.
-  static Iterable<FileSystemEntity> _packageDirList(Directory dir) sync* {
-    var entities = dir.listSync();
+  static Iterable<Resource> _packageDirList(Folder dir) sync* {
+    var resources = dir.getChildren();
 
-    var pubspec = entities.firstWhere(
+    var pubspec = resources.firstWhere(
         (e) => e is File && path.basename(e.path) == 'pubspec.yaml',
         orElse: () => null);
 
-    var libDir = entities.firstWhere(
-        (e) => e is Directory && path.basename(e.path) == 'lib',
+    var libDir = resources.firstWhere(
+        (e) => e is Folder && path.basename(e.path) == 'lib',
         orElse: () => null);
 
     if (pubspec != null && libDir != null) {
       yield libDir;
     } else {
-      yield* entities;
+      yield* resources;
     }
   }
 }
diff --git a/lib/src/package_config_provider.dart b/lib/src/package_config_provider.dart
new file mode 100644
index 0000000000..259ed379cf
--- /dev/null
+++ b/lib/src/package_config_provider.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2020, 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' as io;
+
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:package_config/package_config.dart' as package_config;
+
+/// A provider of PackageConfig-finding methods.
+///
+/// This provides an abstraction around package_config, which can only work
+/// with the physical file system.
+abstract class PackageConfigProvider {
+  Future<package_config.PackageConfig> findPackageConfig(Folder dir);
+}
+
+class PhysicalPackageConfigProvider implements PackageConfigProvider {
+  @override
+  Future<package_config.PackageConfig> findPackageConfig(Folder dir) =>
+      package_config.findPackageConfig(io.Directory(dir.path));
+}
+
+class FakePackageConfigProvider implements PackageConfigProvider {
+  /// A mapping of package config search locations to configured packages.
+  final _packageConfigData = <String, List<package_config.Package>>{};
+
+  void addPackageToConfigFor(String location, String name, Uri root) {
+    _packageConfigData.putIfAbsent(location, () => []);
+    _packageConfigData[location].add(package_config.Package(name, root));
+  }
+
+  @override
+  Future<package_config.PackageConfig> findPackageConfig(Folder dir) async {
+    return package_config.PackageConfig(_packageConfigData[dir.path]);
+  }
+}
diff --git a/lib/src/package_meta.dart b/lib/src/package_meta.dart
index e0b6f27b32..369782f61b 100644
--- a/lib/src/package_meta.dart
+++ b/lib/src/package_meta.dart
@@ -10,6 +10,7 @@ import 'dart:io' show Platform, Process;
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/src/generated/sdk.dart';
 import 'package:dartdoc/dartdoc.dart';
 import 'package:path/path.dart' as p;
 import 'package:yaml/yaml.dart';
@@ -33,11 +34,15 @@ final List<List<String>> __sdkDirFilePathsPosix = [
 ];
 
 final PackageMetaProvider pubPackageMetaProvider = PackageMetaProvider(
-  PubPackageMeta.fromElement,
-  PubPackageMeta.fromFilename,
-  PubPackageMeta.fromDir,
-  PhysicalResourceProvider.INSTANCE,
-);
+    PubPackageMeta.fromElement,
+    PubPackageMeta.fromFilename,
+    PubPackageMeta.fromDir,
+    PhysicalResourceProvider.INSTANCE,
+    PhysicalResourceProvider.INSTANCE
+        .getFile(PhysicalResourceProvider.INSTANCE.pathContext
+            .absolute(Platform.resolvedExecutable))
+        .parent
+        .parent);
 
 /// Sets the supported way of constructing [PackageMeta] objects.
 ///
@@ -49,6 +54,8 @@ final PackageMetaProvider pubPackageMetaProvider = PackageMetaProvider(
 /// [PackageMeta] objects is built.
 class PackageMetaProvider {
   final ResourceProvider resourceProvider;
+  final Folder defaultSdkDir;
+  final DartSdk defaultSdk;
 
   final PackageMeta Function(LibraryElement, String, ResourceProvider)
       _fromElement;
@@ -61,7 +68,8 @@ class PackageMetaProvider {
   PackageMeta fromDir(Folder dir) => _fromDir(dir, resourceProvider);
 
   PackageMetaProvider(this._fromElement, this._fromFilename, this._fromDir,
-      this.resourceProvider);
+      this.resourceProvider, this.defaultSdkDir,
+      {this.defaultSdk});
 }
 
 /// Describes a single package in the context of `dartdoc`.
@@ -91,9 +99,10 @@ abstract class PackageMeta {
 
   p.Context get pathContext => resourceProvider.pathContext;
 
-  /// Returns true if this represents a 'Dart' SDK.  A package can be part of
-  /// Dart and Flutter at the same time, but if we are part of a Dart SDK
-  /// sdkType should never return null.
+  /// Returns true if this represents a 'Dart' SDK.
+  ///
+  /// A package can be part of Dart and Flutter at the same time, but if we are
+  /// part of a Dart SDK, [sdkType] should never return null.
   bool get isSdk;
 
   /// Returns 'Dart' or 'Flutter' (preferentially, 'Flutter' when the answer is
@@ -152,11 +161,10 @@ abstract class PubPackageMeta extends PackageMeta {
       __sdkDirFilePaths = [];
       if (Platform.isWindows) {
         for (var paths in __sdkDirFilePathsPosix) {
-          var windowsPaths = <String>[];
-          for (var path in paths) {
-            windowsPaths
-                .add(p.joinAll(p.Context(style: p.Style.posix).split(path)));
-          }
+          var windowsPaths = [
+            for (var path in paths)
+              p.joinAll(p.Context(style: p.Style.posix).split(path)),
+          ];
           __sdkDirFilePaths.add(windowsPaths);
         }
       } else {
@@ -166,19 +174,19 @@ abstract class PubPackageMeta extends PackageMeta {
     return __sdkDirFilePaths;
   }
 
-  /// Returns the directory of the SDK if the given directory is inside a Dart
-  /// SDK.  Returns null if the directory isn't a subdirectory of the SDK.
   static final _sdkDirParent = <String, Folder>{};
 
+  /// If [dir] is inside a Dart SDK, returns the directory of the SDK, and `null`
+  /// otherwise.
   static Folder sdkDirParent(Folder dir, ResourceProvider resourceProvider) {
-    var dirPathCanonical = p.canonicalize(dir.path);
+    var pathContext = resourceProvider.pathContext;
+    var dirPathCanonical = pathContext.canonicalize(dir.path);
     if (!_sdkDirParent.containsKey(dirPathCanonical)) {
       _sdkDirParent[dirPathCanonical] = null;
       while (dir.exists) {
         if (_sdkDirFilePaths.every((List<String> l) {
-          return l.any((f) => resourceProvider
-              .getFile(resourceProvider.pathContext.join(dir.path, f))
-              .exists);
+          return l.any((f) =>
+              resourceProvider.getFile(pathContext.join(dir.path, f)).exists);
         })) {
           _sdkDirParent[dirPathCanonical] = dir;
           break;
diff --git a/test/comment_processable_test.dart b/test/comment_processable_test.dart
index 1be4c2d7bd..72a1b13f93 100644
--- a/test/comment_processable_test.dart
+++ b/test/comment_processable_test.dart
@@ -6,6 +6,7 @@ import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
 import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/test_utilities/mock_sdk.dart';
 import 'package:dartdoc/src/dartdoc_options.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/package_meta.dart';
@@ -42,6 +43,7 @@ name: foo
       PubPackageMeta.fromFilename,
       PubPackageMeta.fromDir,
       resourceProvider,
+      resourceProvider.getFolder(resourceProvider.convertPath(sdkRoot)),
     );
     var optionSet = await DartdocOptionSet.fromOptionGenerators(
         'dartdoc', [createDartdocOptions], packageMetaProvider);
diff --git a/test/end2end/dartdoc_test.dart b/test/end2end/dartdoc_test.dart
index e4246cae7d..fbd858ade9 100644
--- a/test/end2end/dartdoc_test.dart
+++ b/test/end2end/dartdoc_test.dart
@@ -31,8 +31,6 @@ final Folder _testPackageBadDir = _getFolder('testing/test_package_bad');
 final Folder _testPackageMinimumDir =
     _getFolder('testing/test_package_minimum');
 final Folder _testSkyEnginePackage = _getFolder('testing/sky_engine');
-final Folder _testPackageWithNoReadme =
-    _getFolder('testing/test_package_small');
 final Folder _testPackageIncludeExclude =
     _getFolder('testing/test_package_include_exclude');
 final Folder _testPackageImportExportError =
@@ -97,7 +95,8 @@ void main() {
 
       return await Dartdoc.fromContext(
         context,
-        PubPackageBuilder(context, pubPackageMetaProvider),
+        PubPackageBuilder(
+            context, pubPackageMetaProvider, PhysicalPackageConfigProvider()),
       );
     }
 
@@ -331,19 +330,6 @@ void main() {
         skip: 'Blocked on getting analysis errors with correct interpretation'
             'from analysis_options');
 
-    test('generate docs for a package that does not have a readme', () async {
-      var dartdoc = await buildDartdoc([], _testPackageWithNoReadme, tempDir);
-
-      var results = await dartdoc.generateDocs();
-      expect(results.packageGraph, isNotNull);
-
-      var p = results.packageGraph;
-      expect(p.defaultPackage.name, 'test_package_small');
-      expect(p.defaultPackage.hasHomepage, isFalse);
-      expect(p.defaultPackage.hasDocumentationFile, isFalse);
-      expect(p.localPublicLibraries, hasLength(1));
-    });
-
     test('generate docs including a single library', () async {
       var dartdoc =
           await buildDartdoc(['--include', 'fake'], _testPackageDir, tempDir);
diff --git a/test/end2end/html_generator_test.dart b/test/end2end/html_generator_test.dart
index fa5059bec3..bd8bf71897 100644
--- a/test/end2end/html_generator_test.dart
+++ b/test/end2end/html_generator_test.dart
@@ -12,6 +12,7 @@ import 'package:dartdoc/src/generator/html_resources.g.dart';
 import 'package:dartdoc/src/generator/templates.dart';
 import 'package:dartdoc/src/model/package_graph.dart';
 import 'package:dartdoc/src/io_utils.dart';
+import 'package:dartdoc/src/package_config_provider.dart';
 import 'package:dartdoc/src/warnings.dart';
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
@@ -119,7 +120,10 @@ void main() {
       setUp(() async {
         generator = await _initGeneratorForTest();
         packageGraph = await utils.bootBasicPackage(
-            testPackageDuplicateDir.path, [], pubPackageMetaProvider);
+            testPackageDuplicateDir.path,
+            [],
+            pubPackageMetaProvider,
+            PhysicalPackageConfigProvider());
         tempOutput = await resourceProvider.createSystemTemp('doc_test_temp');
         writer = DartdocFileWriter(tempOutput.path, resourceProvider);
       });
diff --git a/test/end2end/model_special_cases_test.dart b/test/end2end/model_special_cases_test.dart
index ac6d4bb445..ee3d731b7a 100644
--- a/test/end2end/model_special_cases_test.dart
+++ b/test/end2end/model_special_cases_test.dart
@@ -12,8 +12,8 @@ import 'dart:io';
 
 import 'package:async/async.dart';
 import 'package:dartdoc/dartdoc.dart';
-import 'package:dartdoc/src/io_utils.dart';
 import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/package_config_provider.dart';
 import 'package:dartdoc/src/package_meta.dart';
 import 'package:dartdoc/src/special_elements.dart';
 import 'package:pub_semver/pub_semver.dart';
@@ -27,7 +27,10 @@ final Version _platformVersion = Version.parse(_platformVersionString);
 final _testPackageGraphExperimentsMemo = AsyncMemoizer<PackageGraph>();
 Future<PackageGraph> get _testPackageGraphExperiments =>
     _testPackageGraphExperimentsMemo.runOnce(() => utils.bootBasicPackage(
-        'testing/test_package_experiments', [], pubPackageMetaProvider,
+        'testing/test_package_experiments',
+        [],
+        pubPackageMetaProvider,
+        PhysicalPackageConfigProvider(),
         additionalArguments: [
           '--enable-experiment',
           'non-nullable',
@@ -40,33 +43,28 @@ Future<PackageGraph> get _testPackageGraphGinormous =>
         'testing/test_package',
         ['css', 'code_in_commnets', 'excluded'],
         pubPackageMetaProvider,
+        PhysicalPackageConfigProvider(),
         additionalArguments: [
           '--auto-include-dependencies',
           '--no-link-to-remote'
         ]));
 
-final _testPackageGraphSmallMemo = AsyncMemoizer<PackageGraph>();
-Future<PackageGraph> get _testPackageGraphSmall =>
-    _testPackageGraphSmallMemo.runOnce(() => utils.bootBasicPackage(
-        'testing/test_package_small', [], pubPackageMetaProvider,
-        additionalArguments: ['--no-link-to-remote']));
-
 final _testPackageGraphSdkMemo = AsyncMemoizer<PackageGraph>();
 Future<PackageGraph> get _testPackageGraphSdk =>
     _testPackageGraphSdkMemo.runOnce(_bootSdkPackage);
 
 Future<PackageGraph> _bootSdkPackage() async {
   return PubPackageBuilder(
-          await utils.contextFromArgv([
-            '--input',
-            pubPackageMetaProvider.resourceProvider.defaultSdkDir.path
-          ], pubPackageMetaProvider),
-          pubPackageMetaProvider)
+          await utils.contextFromArgv(
+              ['--input', pubPackageMetaProvider.defaultSdkDir.path],
+              pubPackageMetaProvider),
+          pubPackageMetaProvider,
+          PhysicalPackageConfigProvider())
       .buildPackageGraph();
 }
 
 void main() {
-  var sdkDir = pubPackageMetaProvider.resourceProvider.defaultSdkDir;
+  var sdkDir = pubPackageMetaProvider.defaultSdkDir;
 
   if (sdkDir == null) {
     print('Warning: unable to locate the Dart SDK.');
@@ -265,6 +263,7 @@ void main() {
           'testing/test_package',
           ['css', 'code_in_comments', 'excluded'],
           pubPackageMetaProvider,
+          PhysicalPackageConfigProvider(),
           additionalArguments: ['--inject-html']);
 
       injectionExLibrary =
@@ -421,31 +420,16 @@ void main() {
       expect(SubForDocComments.categories.first.isDocumented, isFalse);
       expect(SubForDocComments.displayedCategories, isEmpty);
     });
-
-    test('Verify that packages without categories get handled', () async {
-      var packageGraphSmall = await _testPackageGraphSmall;
-      expect(packageGraphSmall.localPackages.length, equals(1));
-      expect(packageGraphSmall.localPackages.first.hasCategories, isFalse);
-      var packageCategories = packageGraphSmall.localPackages.first.categories;
-      expect(packageCategories.length, equals(0));
-    }, timeout: Timeout.factor(2));
   });
 
   group('Package', () {
-    PackageGraph ginormousPackageGraph, sdkAsPackageGraph;
+    PackageGraph sdkAsPackageGraph;
 
     setUpAll(() async {
-      ginormousPackageGraph = await _testPackageGraphGinormous;
       sdkAsPackageGraph = await _testPackageGraphSdk;
     });
 
     group('test package', () {
-      test('multiple packages, sorted default', () {
-        expect(ginormousPackageGraph.localPackages, hasLength(5));
-        expect(ginormousPackageGraph.localPackages.first.name,
-            equals('test_package'));
-      });
-
       test('sdk name', () {
         expect(sdkAsPackageGraph.defaultPackage.name, equals('Dart'));
         expect(sdkAsPackageGraph.defaultPackage.kind, equals('SDK'));
@@ -467,16 +451,6 @@ void main() {
       });
     });
 
-    group('test small package', () {
-      test('does not have documentation', () async {
-        var packageGraphSmall = await _testPackageGraphSmall;
-        expect(packageGraphSmall.defaultPackage.hasDocumentation, isFalse);
-        expect(packageGraphSmall.defaultPackage.hasDocumentationFile, isFalse);
-        expect(packageGraphSmall.defaultPackage.documentationFile, isNull);
-        expect(packageGraphSmall.defaultPackage.documentation, isNull);
-      });
-    });
-
     group('SDK-specific cases', () {
       test('Verify Interceptor is hidden from inheritance in docs', () {
         var htmlLibrary = sdkAsPackageGraph.libraries
diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart
index 5b28c259c1..b589aeed4d 100644
--- a/test/end2end/model_test.dart
+++ b/test/end2end/model_test.dart
@@ -8,7 +8,6 @@ import 'dart:io';
 
 import 'package:async/async.dart';
 import 'package:dartdoc/dartdoc.dart';
-import 'package:dartdoc/src/io_utils.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/render/category_renderer.dart';
 import 'package:dartdoc/src/render/enum_field_renderer.dart';
@@ -27,6 +26,7 @@ Future<PackageGraph> get _testPackageGraph =>
         'testing/test_package',
         ['css', 'code_in_comments'],
         pubPackageMetaProvider,
+        PhysicalPackageConfigProvider(),
         additionalArguments: ['--no-link-to-remote']));
 
 /// For testing sort behavior.
@@ -59,7 +59,7 @@ class TestLibraryContainerSdk extends TestLibraryContainer {
 }
 
 void main() {
-  var sdkDir = pubPackageMetaProvider.resourceProvider.defaultSdkDir;
+  var sdkDir = pubPackageMetaProvider.defaultSdkDir;
 
   if (sdkDir == null) {
     print('Warning: unable to locate the Dart SDK.');
diff --git a/test/package_meta_test.dart b/test/package_meta_test.dart
index 377bf198e9..7339818a90 100644
--- a/test/package_meta_test.dart
+++ b/test/package_meta_test.dart
@@ -98,7 +98,8 @@ void main() {
   });
 
   group('PackageMeta.fromSdk', () {
-    var p = pubPackageMetaProvider.fromDir(resourceProvider.defaultSdkDir);
+    var p =
+        pubPackageMetaProvider.fromDir(pubPackageMetaProvider.defaultSdkDir);
 
     test('has a name', () {
       expect(p.name, 'Dart');
diff --git a/test/package_test.dart b/test/package_test.dart
new file mode 100644
index 0000000000..a11671ef7b
--- /dev/null
+++ b/test/package_test.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2020, 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:analyzer/file_system/file_system.dart';
+import 'package:analyzer/file_system/memory_file_system.dart';
+import 'package:analyzer/src/test_utilities/mock_sdk.dart';
+import 'package:dartdoc/src/dartdoc_options.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/package_config_provider.dart';
+import 'package:dartdoc/src/package_meta.dart';
+import 'package:test/test.dart';
+
+import 'src/utils.dart' as utils;
+
+void main() {
+  MemoryResourceProvider resourceProvider;
+  MockSdk mockSdk;
+  Folder sdkFolder;
+  String projectRoot;
+  var packageName = 'my_package';
+  PackageMetaProvider packageMetaProvider;
+  FakePackageConfigProvider packageConfigProvider;
+  PackageGraph packageGraph;
+
+  /// Dartdoc has a few indicator files it uses to verify that a directory
+  /// represents a Dart SDK. These include "bin/dart" and "bin/pub".
+  void writeSdkBinFiles(Folder root) {
+    var sdkBinFolder = root.getChildAssumingFolder('bin');
+    sdkBinFolder.getChildAssumingFile('dart').writeAsStringSync('');
+    sdkBinFolder.getChildAssumingFile('pub').writeAsStringSync('');
+  }
+
+  void writeSdk() {
+    mockSdk = MockSdk(resourceProvider: resourceProvider);
+    // The [MockSdk] only works in non-canonicalized paths, which include
+    // "C:\sdk", on Windows. Howerver, dartdoc works almost exclusively with
+    // canonical paths ("c:\sdk"). Copy all MockSdk files to the canonicalized
+    // path.
+    for (var l in mockSdk.sdkLibraries) {
+      var p = l.path;
+      resourceProvider
+          .getFile(resourceProvider.pathContext.canonicalize(p))
+          .writeAsStringSync(resourceProvider.getFile(p).readAsStringSync());
+    }
+    sdkFolder = resourceProvider.getFolder(resourceProvider.pathContext
+        .canonicalize(resourceProvider.convertPath(sdkRoot)))
+      ..create();
+    sdkFolder.getChildAssumingFile('version').writeAsStringSync('2.9.0');
+
+    writeSdkBinFiles(sdkFolder);
+    writeSdkBinFiles(
+        resourceProvider.getFolder(resourceProvider.convertPath(sdkRoot)));
+  }
+
+  void writePackage() {
+    var pathContext = resourceProvider.pathContext;
+    var projectsFolder = resourceProvider.getFolder(
+        pathContext.canonicalize(resourceProvider.convertPath('/projects')));
+    var projectFolder = projectsFolder.getChildAssumingFolder(packageName)
+      ..create;
+    projectRoot = projectFolder.path;
+    projectFolder.getChildAssumingFile('pubspec.yaml').writeAsStringSync('''
+name: $packageName
+version: 0.0.1
+''');
+    projectFolder
+        .getChildAssumingFolder('.dart_tool')
+        .getChildAssumingFile('package_config.json')
+        .writeAsStringSync('');
+    projectFolder.getChildAssumingFolder('lib').create();
+    packageConfigProvider.addPackageToConfigFor(
+        projectRoot, packageName, Uri.file('$projectRoot/'));
+  }
+
+  setUp(() async {
+    resourceProvider = MemoryResourceProvider();
+    writeSdk();
+
+    packageMetaProvider = PackageMetaProvider(
+      PubPackageMeta.fromElement,
+      PubPackageMeta.fromFilename,
+      PubPackageMeta.fromDir,
+      resourceProvider,
+      sdkFolder,
+      defaultSdk: mockSdk,
+    );
+    var optionSet = await DartdocOptionSet.fromOptionGenerators(
+        'dartdoc', [createDartdocOptions], packageMetaProvider);
+    optionSet.parseArguments([]);
+    packageConfigProvider = FakePackageConfigProvider();
+  });
+
+  test('package with no deps has 2 local packages, including SDK', () async {
+    writePackage();
+    resourceProvider
+        .getFile(
+            resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart'))
+        .writeAsStringSync('''
+/// Documentation comment.
+int x;
+''');
+    packageGraph = await utils.bootBasicPackage(
+        projectRoot, [], packageMetaProvider, packageConfigProvider,
+        additionalArguments: [
+          '--auto-include-dependencies',
+          '--no-link-to-remote'
+        ]);
+
+    var localPackages = packageGraph.localPackages;
+    expect(localPackages, hasLength(2));
+    expect(localPackages[0].name, equals(packageName));
+    expect(localPackages[1].name, equals('Dart'));
+  });
+
+  test('package with no deps has 1 local package, excluding SDK', () async {
+    writePackage();
+    resourceProvider
+        .getFile(
+            resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart'))
+        .writeAsStringSync('''
+/// Documentation comment.
+int x;
+''');
+    packageGraph = await utils.bootBasicPackage(
+        projectRoot, [], packageMetaProvider, packageConfigProvider,
+        additionalArguments: ['--no-link-to-remote']);
+
+    var localPackages = packageGraph.localPackages;
+    expect(localPackages, hasLength(1));
+    expect(localPackages[0].name, equals(packageName));
+  });
+
+  test('package with no doc comments has no docs', () async {
+    writePackage();
+    resourceProvider
+        .getFile(
+            resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart'))
+        .writeAsStringSync('''
+// No documentation comment.
+int x;
+''');
+    packageGraph = await utils.bootBasicPackage(
+        projectRoot, [], packageMetaProvider, packageConfigProvider);
+
+    expect(packageGraph.defaultPackage.hasDocumentation, isFalse);
+    expect(packageGraph.defaultPackage.hasDocumentationFile, isFalse);
+    expect(packageGraph.defaultPackage.documentationFile, isNull);
+    expect(packageGraph.defaultPackage.documentation, isNull);
+  });
+
+  test('package with no README has no homepage', () async {
+    writePackage();
+    resourceProvider
+        .getFile(
+            resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart'))
+        .writeAsStringSync('''
+/// Documentation comment.
+int x;
+''');
+    packageGraph = await utils.bootBasicPackage(
+        projectRoot, [], packageMetaProvider, packageConfigProvider);
+
+    expect(packageGraph.defaultPackage.hasHomepage, isFalse);
+    expect(packageGraph.localPublicLibraries, hasLength(1));
+  });
+
+  test('package with no doc comments has no categories', () async {
+    writePackage();
+    resourceProvider
+        .getFile(
+            resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart'))
+        .writeAsStringSync('''
+// No documentation comment.
+int x;
+''');
+    packageGraph = await utils.bootBasicPackage(
+        projectRoot, [], packageMetaProvider, packageConfigProvider);
+
+    expect(packageGraph.localPackages.first.hasCategories, isFalse);
+    expect(packageGraph.localPackages.first.categories, isEmpty);
+  });
+}
diff --git a/test/src/utils.dart b/test/src/utils.dart
index 58334b29ae..6cc3109fa1 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -8,7 +8,7 @@ import 'dart:async';
 
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:dartdoc/dartdoc.dart';
-import 'package:dartdoc/src/io_utils.dart';
+import 'package:dartdoc/src/package_config_provider.dart';
 import 'package:dartdoc/src/package_meta.dart';
 
 /// The number of public libraries in testing/test_package, minus 2 for
@@ -36,24 +36,27 @@ Future<DartdocOptionContext> contextFromArgv(
       pubPackageMetaProvider.resourceProvider);
 }
 
-Future<PackageGraph> bootBasicPackage(String dirPath,
-    List<String> excludeLibraries, PackageMetaProvider packageMetaProvider,
-    {List<String> additionalArguments}) async {
+Future<PackageGraph> bootBasicPackage(
+    String dirPath,
+    List<String> excludeLibraries,
+    PackageMetaProvider packageMetaProvider,
+    PackageConfigProvider packageConfigProvider,
+    {List<String> additionalArguments = const []}) async {
   var resourceProvider = packageMetaProvider.resourceProvider;
   var dir = resourceProvider.getFolder(resourceProvider.pathContext
       .absolute(resourceProvider.pathContext.normalize(dirPath)));
-  additionalArguments ??= <String>[];
   return PubPackageBuilder(
           await contextFromArgv([
             '--input',
             dir.path,
             '--sdk-dir',
-            resourceProvider.defaultSdkDir.path,
+            packageMetaProvider.defaultSdkDir.path,
             '--exclude',
             excludeLibraries.join(','),
             '--allow-tools',
             ...additionalArguments,
           ], packageMetaProvider),
-          packageMetaProvider)
+          packageMetaProvider,
+          packageConfigProvider)
       .buildPackageGraph();
 }
diff --git a/testing/test_package_small/lib/main.dart b/testing/test_package_small/lib/main.dart
deleted file mode 100644
index d7c54ac23c..0000000000
--- a/testing/test_package_small/lib/main.dart
+++ /dev/null
@@ -1,3 +0,0 @@
-main(List<String> args) {
-  print('Hello world!');
-}
diff --git a/testing/test_package_small/pubspec.yaml b/testing/test_package_small/pubspec.yaml
deleted file mode 100644
index a5bd444b2c..0000000000
--- a/testing/test_package_small/pubspec.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-name: test_package_small
-version: 0.0.1
-description: A simple console application.
-#dependencies:
-#  foo_bar: '>=1.0.0 <2.0.0'