Add rudimentary PackageValidator functionality

Provides a hook for BlazeModules to provide loaded-package validation logic.
Packages which fail validation are treated as having failed to load.

RELNOTES: None
PiperOrigin-RevId: 297233863
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
index 84d2681..0532850 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
@@ -18,6 +18,8 @@
 import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
 import static com.google.devtools.build.skyframe.EvaluationResultSubjectFactory.assertThatEvaluationResult;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Predicates;
@@ -35,6 +37,8 @@
 import com.google.devtools.build.lib.packages.NoSuchPackageException;
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
 import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.PackageValidator;
+import com.google.devtools.build.lib.packages.PackageValidator.InvalidPackageException;
 import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
 import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
@@ -71,9 +75,13 @@
 import java.util.Set;
 import java.util.UUID;
 import javax.annotation.Nullable;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 /**
  * Unit tests of specific functionality of PackageFunction. Note that it's already tested indirectly
@@ -82,6 +90,10 @@
 @RunWith(JUnit4.class)
 public class PackageFunctionTest extends BuildViewTestCase {
 
+  @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+  @Mock private PackageValidator mockPackageValidator;
+
   private CustomInMemoryFs fs = new CustomInMemoryFs(new ManualClock());
 
   private void preparePackageLoading(Path... roots) {
@@ -114,6 +126,11 @@
     return fs;
   }
 
+  @Override
+  protected PackageValidator getPackageValidator() {
+    return mockPackageValidator;
+  }
+
   private Package validPackageWithoutErrors(SkyKey skyKey) throws InterruptedException {
     return validPackageInternal(skyKey, /*checkPackageError=*/ true);
   }
@@ -150,6 +167,40 @@
   }
 
   @Test
+  public void testInvalidPackage() throws Exception {
+    scratch.file("pkg/BUILD", "sh_library(name='foo', srcs=['foo.sh'])");
+    scratch.file("pkg/foo.sh");
+
+    doAnswer(
+            inv -> {
+              Package pkg = inv.getArgument(0, Package.class);
+              if (pkg.getName().equals("pkg")) {
+                throw new InvalidPackageException(pkg.getPackageIdentifier(), "no good");
+              }
+              return null;
+            })
+        .when(mockPackageValidator)
+        .validate(any(Package.class));
+
+    invalidatePackages();
+
+    SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("@//pkg"));
+    EvaluationResult<PackageValue> result =
+        SkyframeExecutorTestUtils.evaluate(
+            getSkyframeExecutor(), skyKey, /*keepGoing=*/ false, reporter);
+    assertThatEvaluationResult(result).hasError();
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(skyKey)
+        .hasExceptionThat()
+        .isInstanceOf(InvalidPackageException.class);
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(skyKey)
+        .hasExceptionThat()
+        .hasMessageThat()
+        .contains("no such package 'pkg': no good");
+  }
+
+  @Test
   public void testPropagatesFilesystemInconsistencies() throws Exception {
     reporter.removeHandler(failFastHandler);
     RecordingDifferencer differencer = getSkyframeExecutor().getDifferencerForTesting();