Add PubWorkspace, integrate it into ContextBuilder, and therefore fix sealed test

Also add a superclass to BasicWorkspace and PubWorkspace, since the bulk of their
implementation is shared; Only find() and findPackageFor() need to be separate;
the package mapping and resolving can be shared.

Change-Id: I13932d6947d6dc28fc7223594e5bd2526f12f573
Reviewed-on: https://dart-review.googlesource.com/c/89167
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/context/builder.dart b/pkg/analyzer/lib/src/context/builder.dart
index 5e2a359..960c862 100644
--- a/pkg/analyzer/lib/src/context/builder.dart
+++ b/pkg/analyzer/lib/src/context/builder.dart
@@ -40,6 +40,7 @@
 import 'package:analyzer/src/workspace/bazel.dart';
 import 'package:analyzer/src/workspace/gn.dart';
 import 'package:analyzer/src/workspace/package_build.dart';
+import 'package:analyzer/src/workspace/pub.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:args/args.dart';
 import 'package:package_config/packages.dart';
@@ -672,16 +673,19 @@
   static Workspace createWorkspace(ResourceProvider resourceProvider,
       String rootPath, ContextBuilder contextBuilder) {
     if (_hasPackageFileInPath(resourceProvider, rootPath)) {
-      // Bazel workspaces that include package files are treated like normal
-      // (non-Bazel) directories. But may still use package:build.
+      // A Bazel or Gn workspace that includes a '.packages' file is treated
+      // like a normal (non-Bazel/Gn) directory. But may still use
+      // package:build or Pub.
       return PackageBuildWorkspace.find(
               resourceProvider, rootPath, contextBuilder) ??
+          PubWorkspace.find(resourceProvider, rootPath, contextBuilder) ??
           BasicWorkspace.find(resourceProvider, rootPath, contextBuilder);
     }
     Workspace workspace = BazelWorkspace.find(resourceProvider, rootPath);
     workspace ??= GnWorkspace.find(resourceProvider, rootPath);
     workspace ??=
         PackageBuildWorkspace.find(resourceProvider, rootPath, contextBuilder);
+    workspace ??= PubWorkspace.find(resourceProvider, rootPath, contextBuilder);
     return workspace ??
         BasicWorkspace.find(resourceProvider, rootPath, contextBuilder);
   }
diff --git a/pkg/analyzer/lib/src/workspace/basic.dart b/pkg/analyzer/lib/src/workspace/basic.dart
index e8784cc..89b270f 100644
--- a/pkg/analyzer/lib/src/workspace/basic.dart
+++ b/pkg/analyzer/lib/src/workspace/basic.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/source/package_map_resolver.dart';
 import 'package:analyzer/src/summary/package_bundle_reader.dart';
+import 'package:analyzer/src/workspace/simple.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:package_config/packages.dart';
 
@@ -16,23 +17,7 @@
  *
  * A BasicWorkspace should only be used when no other workspace type is valid.
  */
-class BasicWorkspace extends Workspace {
-  /**
-   * The [ResourceProvider] by which paths are converted into [Resource]s.
-   */
-  final ResourceProvider provider;
-
-  /**
-   * The absolute workspace root path.
-   */
-  final String root;
-
-  final ContextBuilder _builder;
-
-  Map<String, List<Folder>> _packageMap;
-
-  Packages _packages;
-
+class BasicWorkspace extends SimpleWorkspace {
   /**
    * The singular package in this workspace.
    *
@@ -40,37 +25,9 @@
    */
   BasicWorkspacePackage _theOnlyPackage;
 
-  BasicWorkspace._(this.provider, this.root, this._builder);
-
-  @override
-  Map<String, List<Folder>> get packageMap {
-    _packageMap ??= _builder.convertPackagesToMap(packages);
-    return _packageMap;
-  }
-
-  Packages get packages {
-    _packages ??= _builder.createPackageMap(root);
-    return _packages;
-  }
-
-  @override
-  UriResolver get packageUriResolver =>
-      new PackageMapUriResolver(provider, packageMap);
-
-  @override
-  SourceFactory createSourceFactory(DartSdk sdk, SummaryDataStore summaryData) {
-    if (summaryData != null) {
-      throw new UnsupportedError(
-          'Summary files are not supported in a basic workspace.');
-    }
-    List<UriResolver> resolvers = <UriResolver>[];
-    if (sdk != null) {
-      resolvers.add(new DartUriResolver(sdk));
-    }
-    resolvers.add(packageUriResolver);
-    resolvers.add(new ResourceUriResolver(provider));
-    return new SourceFactory(resolvers, packages, provider);
-  }
+  BasicWorkspace._(
+      ResourceProvider provider, String root, ContextBuilder builder)
+      : super(provider, root, builder);
 
   @override
   WorkspacePackage findPackageFor(String filePath) {
diff --git a/pkg/analyzer/lib/src/workspace/bazel.dart b/pkg/analyzer/lib/src/workspace/bazel.dart
index d4a83b9c..5e90c71 100644
--- a/pkg/analyzer/lib/src/workspace/bazel.dart
+++ b/pkg/analyzer/lib/src/workspace/bazel.dart
@@ -287,11 +287,12 @@
   /**
    * Find the Bazel workspace that contains the given [filePath].
    *
-   * Return `null` if a workspace markers, such as the `WORKSPACE` file, or
+   * Return `null` if a workspace marker, such as the `WORKSPACE` file, or
    * the sibling `READONLY` folder cannot be found.
    *
    * Return `null` if the workspace does not have `bazel-genfiles` or
-   * `blaze-genfiles` folders, so we don't know where to search generated files.
+   * `blaze-genfiles` folders, since we don't know where to search generated
+   * files.
    *
    * Return `null` if there is a folder 'foo' with the sibling `READONLY`
    * folder, but there is corresponding folder 'foo' in `READONLY`, i.e. the
diff --git a/pkg/analyzer/lib/src/workspace/gn.dart b/pkg/analyzer/lib/src/workspace/gn.dart
index 77ef5e8..191c421 100644
--- a/pkg/analyzer/lib/src/workspace/gn.dart
+++ b/pkg/analyzer/lib/src/workspace/gn.dart
@@ -198,6 +198,10 @@
    * file must be found in [filePath]'s output directory.
    */
   static GnWorkspace find(ResourceProvider provider, String filePath) {
+    Resource resource = provider.getResource(filePath);
+    if (resource is File) {
+      filePath = resource.parent.path;
+    }
     Folder folder = provider.getFolder(filePath);
     while (true) {
       Folder parent = folder.parent;
diff --git a/pkg/analyzer/lib/src/workspace/pub.dart b/pkg/analyzer/lib/src/workspace/pub.dart
new file mode 100644
index 0000000..f89937c
--- /dev/null
+++ b/pkg/analyzer/lib/src/workspace/pub.dart
@@ -0,0 +1,84 @@
+// 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:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/context/builder.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/source/package_map_resolver.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart';
+import 'package:analyzer/src/workspace/simple.dart';
+import 'package:analyzer/src/workspace/workspace.dart';
+import 'package:package_config/packages.dart';
+
+/// Information about a Pub workspace.
+class PubWorkspace extends SimpleWorkspace {
+  /// The name of the file that identifies the root of the workspace.
+  static const String _pubspecName = 'pubspec.yaml';
+
+  /// The singular package in this workspace.
+  ///
+  /// Each Pub workspace is itself one package.
+  PubWorkspacePackage _theOnlyPackage;
+
+  PubWorkspace._(ResourceProvider provider, String root, ContextBuilder builder)
+      : super(provider, root, builder);
+
+  @override
+  WorkspacePackage findPackageFor(String filePath) {
+    final Folder folder = provider.getFolder(filePath);
+    if (provider.pathContext.isWithin(root, folder.path)) {
+      _theOnlyPackage ??= new PubWorkspacePackage(root, this);
+      return _theOnlyPackage;
+    } else {
+      return null;
+    }
+  }
+
+  /// Find the pub workspace that contains the given [path].
+  static PubWorkspace find(
+      ResourceProvider provider, String filePath, ContextBuilder builder) {
+    Resource resource = provider.getResource(filePath);
+    if (resource is File) {
+      filePath = resource.parent.path;
+    }
+    Folder folder = provider.getFolder(filePath);
+    while (true) {
+      Folder parent = folder.parent;
+      if (parent == null) {
+        return null;
+      }
+
+      if (folder.getChildAssumingFile(_pubspecName).exists) {
+        // Found the pubspec.yaml file; this is our root.
+        String root = folder.path;
+        return new PubWorkspace._(provider, root, builder);
+      }
+
+      // Go up a folder.
+      folder = parent;
+    }
+  }
+}
+
+/// Information about a package defined in a [PubWorkspace].
+///
+/// Separate from [Packages] or package maps, this class is designed to simply
+/// understand whether arbitrary file paths represent libraries declared within
+/// a given package in a [PubWorkspace].
+class PubWorkspacePackage extends WorkspacePackage {
+  final String root;
+
+  final PubWorkspace workspace;
+
+  PubWorkspacePackage(this.root, this.workspace);
+
+  @override
+  bool contains(String path) {
+    // There is a 1-1 relationship between [PubWorkspace]s and
+    // [PubWorkspacePackage]s. If a file is in a package's workspace, then it
+    // is in the package as well.
+    return workspace.provider.pathContext.isWithin(root, path);
+  }
+}
diff --git a/pkg/analyzer/lib/src/workspace/simple.dart b/pkg/analyzer/lib/src/workspace/simple.dart
new file mode 100644
index 0000000..c2d0d8e
--- /dev/null
+++ b/pkg/analyzer/lib/src/workspace/simple.dart
@@ -0,0 +1,63 @@
+// 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:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/context/builder.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/source/package_map_resolver.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart';
+import 'package:analyzer/src/workspace/workspace.dart';
+import 'package:package_config/packages.dart';
+
+/// An abstract class for simple workspaces which do not feature any build
+/// artifacts or generated files.
+///
+/// The [packageMap] and [packageUrlResolver] are simple derivations from the
+/// [ContextBuilder] and [ResourceProvider] required for the class.
+abstract class SimpleWorkspace extends Workspace {
+  /// The [ResourceProvider] by which paths are converted into [Resource]s.
+  final ResourceProvider provider;
+
+  /// The absolute workspace root path.
+  final String root;
+
+  final ContextBuilder _builder;
+
+  Map<String, List<Folder>> _packageMap;
+
+  Packages _packages;
+
+  SimpleWorkspace(this.provider, this.root, this._builder);
+
+  @override
+  Map<String, List<Folder>> get packageMap {
+    _packageMap ??= _builder.convertPackagesToMap(packages);
+    return _packageMap;
+  }
+
+  Packages get packages {
+    _packages ??= _builder.createPackageMap(root);
+    return _packages;
+  }
+
+  @override
+  UriResolver get packageUriResolver =>
+      new PackageMapUriResolver(provider, packageMap);
+
+  @override
+  SourceFactory createSourceFactory(DartSdk sdk, SummaryDataStore summaryData) {
+    if (summaryData != null) {
+      throw new UnsupportedError(
+          'Summary files are not supported in a Pub workspace.');
+    }
+    List<UriResolver> resolvers = <UriResolver>[];
+    if (sdk != null) {
+      resolvers.add(new DartUriResolver(sdk));
+    }
+    resolvers.add(packageUriResolver);
+    resolvers.add(new ResourceUriResolver(provider));
+    return new SourceFactory(resolvers, packages, provider);
+  }
+}
diff --git a/pkg/analyzer/test/generated/hint_code_test.dart b/pkg/analyzer/test/generated/hint_code_test.dart
index 47bdb3a..e33ea7d 100644
--- a/pkg/analyzer/test/generated/hint_code_test.dart
+++ b/pkg/analyzer/test/generated/hint_code_test.dart
@@ -68,11 +68,8 @@
 
 @reflectiveTest
 class CrossPackageHintCodeTest extends ResolverTestCase {
-  /// Write a pubspec file at [root], so that BestPracticesVerifier can see that
-  /// [root] is the root of a BasicWorkspace, and a BasicWorkspacePackage.
-  void newBasicPackage(String root) {
-    newFile('$root/pubspec.yaml');
-  }
+  @override
+  bool get enableNewAnalysisDriver => true;
 
   test_subtypeOfSealedClass_extending() async {
     super.resetWith(packages: [
@@ -86,7 +83,7 @@
       ]
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:foo/foo.dart';
                     class Bar extends Foo {}
@@ -108,7 +105,7 @@
       ]
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:foo/foo.dart';
                     class Bar implements Foo {}
@@ -130,7 +127,7 @@
       ]
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:foo/foo.dart';
                     class Bar1 {}
@@ -153,7 +150,7 @@
       ]
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:foo/foo.dart';
                     mixin Bar implements Foo {}
@@ -175,7 +172,7 @@
       ]
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:foo/foo.dart';
                     mixin Bar on Foo {}
@@ -197,7 +194,7 @@
       ]
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:foo/foo.dart';
                     class Bar extends Object with Foo {}
@@ -212,7 +209,7 @@
       ['meta', metaLibraryStub],
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:meta/meta.dart';
                     @sealed class Foo {}
@@ -233,12 +230,13 @@
       ['meta', metaLibraryStub],
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source1 = addNamedSource('/pkg1/lib/lib1.dart', r'''
                      import 'package:meta/meta.dart';
                      @sealed class Foo {}
                      ''');
-    addNamedSource('/pkg1/lib/src/lib2.dart', r'''
+    Source source2 = addNamedSource('/pkg1/lib/src/lib2.dart', r'''
+                     import '../lib1.dart';
                      class Bar1 extends Foo {}
                      class Bar2 implements Foo {}
                      class Bar4 = Bar1 with Foo;
@@ -246,8 +244,10 @@
                      mixin Bar6 implements Foo {}
                      ''');
     await computeAnalysisResult(source1);
+    await computeAnalysisResult(source2);
     assertNoErrors(source1);
-    verify([source1]);
+    assertNoErrors(source2);
+    verify([source1, source2]);
   }
 
   test_subtypeOfSealedClass_withinPackageTestDirectory_OK() async {
@@ -255,12 +255,15 @@
       ['meta', metaLibraryStub],
     ]);
 
-    newBasicPackage('/pkg1');
+    newFolder('/pkg1');
+    _newPubPackageRoot('/pkg1');
+
     Source source1 = addNamedSource('/pkg1/lib/lib1.dart', r'''
                      import 'package:meta/meta.dart';
                      @sealed class Foo {}
                      ''');
-    addNamedSource('/pkg1/test/test.dart', r'''
+    Source source2 = addNamedSource('/pkg1/test/test.dart', r'''
+                     import '../lib/lib1.dart';
                      class Bar1 extends Foo {}
                      class Bar2 implements Foo {}
                      class Bar4 = Bar1 with Foo;
@@ -268,8 +271,10 @@
                      mixin Bar6 implements Foo {}
                      ''');
     await computeAnalysisResult(source1);
+    await computeAnalysisResult(source2);
     assertNoErrors(source1);
-    verify([source1]);
+    assertNoErrors(source2);
+    verify([source1, source2]);
   }
 
   test_subtypeOfSealedClass_withinPart_OK() async {
@@ -277,7 +282,7 @@
       ['meta', metaLibraryStub],
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source1 = addNamedSource('/pkg1/lib/lib1.dart', r'''
                      import 'package:meta/meta.dart';
                      part 'part1.dart';
@@ -308,7 +313,7 @@
       ]
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:foo/foo.dart';
                     class Bar1 {}
@@ -331,7 +336,7 @@
       ]
     ]);
 
-    newBasicPackage('/pkg1');
+    _newPubPackageRoot('/pkg1');
     Source source = addNamedSource('/pkg1/lib/lib1.dart', r'''
                     import 'package:foo/foo.dart';
                     class Bar extends Object with Foo {}
@@ -340,6 +345,12 @@
     assertErrors(source, [HintCode.SUBTYPE_OF_SEALED_CLASS]);
     verify([source]);
   }
+
+  /// Write a pubspec file at [root], so that BestPracticesVerifier can see
+  /// that [root] is the root of a PubWorkspace, and a PubWorkspacePackage.
+  void _newPubPackageRoot(String root) {
+    newFile('$root/pubspec.yaml');
+  }
 }
 
 @reflectiveTest
diff --git a/pkg/analyzer/test/src/context/builder_test.dart b/pkg/analyzer/test/src/context/builder_test.dart
index 3594512..23e564e 100644
--- a/pkg/analyzer/test/src/context/builder_test.dart
+++ b/pkg/analyzer/test/src/context/builder_test.dart
@@ -17,7 +17,12 @@
 import 'package:analyzer/src/source/package_map_resolver.dart';
 import 'package:analyzer/src/test_utilities/mock_sdk.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:analyzer/src/workspace/basic.dart';
 import 'package:analyzer/src/workspace/bazel.dart';
+import 'package:analyzer/src/workspace/gn.dart';
+import 'package:analyzer/src/workspace/package_build.dart';
+import 'package:analyzer/src/workspace/pub.dart';
+import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:args/args.dart';
 import 'package:package_config/packages.dart';
 import 'package:package_config/src/packages_impl.dart';
@@ -872,6 +877,68 @@
     expect(result.path, filePath);
   }
 
+  void test_createWorkspace_hasPackagesFile_hasDartToolAndPubspec() {
+    newFile('/workspace/.packages');
+    newFolder('/workspace/.dart_tool/build/generated/project/lib');
+    newFileWithBytes('/workspace/pubspec.yaml', 'name: project'.codeUnits);
+    Workspace workspace = ContextBuilder.createWorkspace(
+        resourceProvider, '/workspace/project/lib/lib.dart', builder);
+    expect(workspace, TypeMatcher<PackageBuildWorkspace>());
+  }
+
+  void test_createWorkspace_hasPackagesFile_hasPubspec() {
+    newFile('/workspace/.packages');
+    newFileWithBytes('/workspace/pubspec.yaml', 'name: project'.codeUnits);
+    Workspace workspace = ContextBuilder.createWorkspace(
+        resourceProvider, '/workspace/project/lib/lib.dart', builder);
+    expect(workspace, TypeMatcher<PubWorkspace>());
+  }
+
+  void test_createWorkspace_hasPackagesFile_noMarkerFiles() {
+    newFile('/workspace/.packages');
+    Workspace workspace = ContextBuilder.createWorkspace(
+        resourceProvider, '/workspace/project/lib/lib.dart', builder);
+    expect(workspace, TypeMatcher<BasicWorkspace>());
+  }
+
+  void test_createWorkspace_noPackagesFile_hasDartToolAndPubspec() {
+    newFolder('/workspace/.dart_tool/build/generated/project/lib');
+    newFileWithBytes('/workspace/pubspec.yaml', 'name: project'.codeUnits);
+    Workspace workspace = ContextBuilder.createWorkspace(
+        resourceProvider, '/workspace/project/lib/lib.dart', builder);
+    expect(workspace, TypeMatcher<PackageBuildWorkspace>());
+  }
+
+  void test_createWorkspace_noPackagesFile_hasPubspec() {
+    newFileWithBytes('/workspace/pubspec.yaml', 'name: project'.codeUnits);
+    Workspace workspace = ContextBuilder.createWorkspace(
+        resourceProvider, '/workspace/project/lib/lib.dart', builder);
+    expect(workspace, TypeMatcher<PubWorkspace>());
+  }
+
+  void test_createWorkspace_noPackagesFile_noMarkerFiles() {
+    Workspace workspace = ContextBuilder.createWorkspace(
+        resourceProvider, '/workspace/project/lib/lib.dart', builder);
+    expect(workspace, TypeMatcher<BasicWorkspace>());
+  }
+
+  void test_createWorkspace_noPackagesFile_hasGnMarkerFiles() {
+    newFolder('/workspace/.jiri_root');
+    newFile(
+        '/workspace/out/debug-x87_128/dartlang/gen/project/lib/lib.packages');
+    Workspace workspace = ContextBuilder.createWorkspace(
+        resourceProvider, '/workspace/project/lib/lib.dart', builder);
+    expect(workspace, TypeMatcher<GnWorkspace>());
+  }
+
+  void test_createWorkspace_noPackagesFile_hasBazelMarkerFiles() {
+    newFile('/workspace/WORKSPACE');
+    newFolder('/workspace/bazel-genfiles');
+    Workspace workspace = ContextBuilder.createWorkspace(
+        resourceProvider, '/workspace/project/lib/lib.dart', builder);
+    expect(workspace, TypeMatcher<BazelWorkspace>());
+  }
+
   _defineMockLintRules() {
     _mockLintRule = new _MockLintRule('mock_lint_rule');
     Registry.ruleRegistry.registerDefault(_mockLintRule);
diff --git a/pkg/analyzer/test/src/workspace/gn_test.dart b/pkg/analyzer/test/src/workspace/gn_test.dart
index bc75f28..8cd0a5b 100644
--- a/pkg/analyzer/test/src/workspace/gn_test.dart
+++ b/pkg/analyzer/test/src/workspace/gn_test.dart
@@ -182,6 +182,7 @@
     newFile('/ws/.config',
         content: 'FOO=foo\n' + 'FUCHSIA_BUILD_DIR="$buildDir"\n' + 'BAR=bar\n');
     newFile('/ws/out/debug-x87_128/dartlang/gen/some/code/foo.packages');
+    newFolder('/ws/some/code');
     return GnWorkspace.find(resourceProvider, convertPath('/ws/some/code'));
   }
 
diff --git a/pkg/analyzer/test/src/workspace/pub_test.dart b/pkg/analyzer/test/src/workspace/pub_test.dart
new file mode 100644
index 0000000..48d88d6
--- /dev/null
+++ b/pkg/analyzer/test/src/workspace/pub_test.dart
@@ -0,0 +1,119 @@
+// 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:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/context/builder.dart';
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:analyzer/src/workspace/pub.dart';
+import 'package:package_config/packages.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PubWorkspacePackageTest);
+    defineReflectiveTests(PubWorkspaceTest);
+  });
+}
+
+class MockContextBuilder implements ContextBuilder {
+  Map<String, Packages> packagesMapMap = <String, Packages>{};
+  Map<Packages, Map<String, List<Folder>>> packagesToMapMap =
+      <Packages, Map<String, List<Folder>>>{};
+
+  Map<String, List<Folder>> convertPackagesToMap(Packages packages) =>
+      packagesToMapMap[packages];
+
+  Packages createPackageMap(String rootDirectoryPath) =>
+      packagesMapMap[rootDirectoryPath];
+
+  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
+
+class MockPackages implements Packages {
+  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
+
+@reflectiveTest
+class PubWorkspacePackageTest with ResourceProviderMixin {
+  PubWorkspace workspace;
+
+  setUp() {
+    final contextBuilder = new MockContextBuilder();
+    final packages = new MockPackages();
+    final packageMap = <String, List<Folder>>{'project': []};
+    contextBuilder.packagesMapMap[convertPath('/workspace')] = packages;
+    contextBuilder.packagesToMapMap[packages] = packageMap;
+
+    newFileWithBytes('/workspace/pubspec.yaml', 'name: project'.codeUnits);
+    workspace = PubWorkspace.find(
+        resourceProvider, convertPath('/workspace'), contextBuilder);
+  }
+
+  void test_findPackageFor_unrelatedFile() {
+    newFile('/workspace/project/lib/file.dart');
+
+    var package = workspace
+        .findPackageFor(convertPath('/workspace2/project/lib/file.dart'));
+    expect(package, isNull);
+  }
+
+  void test_findPackageFor_includedFile() {
+    newFile('/workspace/project/lib/file.dart');
+
+    var package = workspace
+        .findPackageFor(convertPath('/workspace/project/lib/file.dart'));
+    expect(package, isNotNull);
+    expect(package.root, convertPath('/workspace'));
+    expect(package.workspace, equals(workspace));
+  }
+
+  void test_contains_differentWorkspace() {
+    newFile('/workspace2/project/lib/file.dart');
+
+    var package = workspace
+        .findPackageFor(convertPath('/workspace/project/lib/code.dart'));
+    expect(package.contains('/workspace2/project/lib/file.dart'), isFalse);
+  }
+
+  void test_contains_sameWorkspace() {
+    newFile('/workspace/project/lib/file2.dart');
+
+    var package = workspace
+        .findPackageFor(convertPath('/workspace/project/lib/code.dart'));
+    expect(package.contains('/workspace/project/lib/file2.dart'), isTrue);
+    expect(package.contains('/workspace/project/bin/bin.dart'), isTrue);
+    expect(package.contains('/workspace/project/test/test.dart'), isTrue);
+  }
+}
+
+@reflectiveTest
+class PubWorkspaceTest with ResourceProviderMixin {
+  void test_find_fail_notAbsolute() {
+    expect(
+        () => PubWorkspace.find(resourceProvider, convertPath('not_absolute'),
+            new MockContextBuilder()),
+        throwsA(TypeMatcher<ArgumentError>()));
+  }
+
+  void test_find_missingPubspec() {
+    PubWorkspace workspace = PubWorkspace.find(resourceProvider,
+        convertPath('/workspace/lib/lib1.dart'), new MockContextBuilder());
+    expect(workspace, isNull);
+  }
+
+  void test_find_directory() {
+    newFileWithBytes('/workspace/pubspec.yaml', 'name: project'.codeUnits);
+    PubWorkspace workspace = PubWorkspace.find(
+        resourceProvider, convertPath('/workspace'), new MockContextBuilder());
+    expect(workspace.root, convertPath('/workspace'));
+  }
+
+  void test_find_file() {
+    newFileWithBytes('/workspace/pubspec.yaml', 'name: project'.codeUnits);
+    PubWorkspace workspace = PubWorkspace.find(resourceProvider,
+        convertPath('/workspace/lib/lib1.dart'), new MockContextBuilder());
+    expect(workspace.root, convertPath('/workspace'));
+  }
+}