Ensure no service loader rewriting if it crosses a feature split

This CL ensures that we will not perform service-loader rewriting of
types, where the service type is defined in a feature, a
META-INF/services files is declared for a service in a feature or if
any of the implementations defined is in a feature.

Bug: 157426812
Change-Id: I086c569b88f48d9fc037e4d1ac5b8fb64c61a84a
diff --git a/src/main/java/com/android/tools/r8/FeatureSplit.java b/src/main/java/com/android/tools/r8/FeatureSplit.java
index 5947786..9bb637e 100644
--- a/src/main/java/com/android/tools/r8/FeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/FeatureSplit.java
@@ -29,6 +29,9 @@
  */
 @Keep
 public final class FeatureSplit {
+
+  public static final FeatureSplit BASE = new FeatureSplit(null, null);
+
   private final ProgramConsumer programConsumer;
   private final List<ProgramResourceProvider> programResourceProviders;
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index 5c1c7ad..f010b85 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.Origin;
@@ -21,7 +23,7 @@
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -36,9 +38,9 @@
   private final AppView<?> appView;
 
   // Mapping from service types to service implementation types.
-  private final Map<DexType, List<DexType>> services;
+  private final Map<DexType, Map<FeatureSplit, List<DexType>>> services;
 
-  private AppServices(AppView<?> appView, Map<DexType, List<DexType>> services) {
+  private AppServices(AppView<?> appView, Map<DexType, Map<FeatureSplit, List<DexType>>> services) {
     this.appView = appView;
     this.services = services;
   }
@@ -54,66 +56,124 @@
 
   public List<DexType> serviceImplementationsFor(DexType serviceType) {
     assert verifyRewrittenWithLens();
-    assert services.containsKey(serviceType);
-    List<DexType> serviceImplementationTypes = services.get(serviceType);
-    if (serviceImplementationTypes == null) {
+    Map<FeatureSplit, List<DexType>> featureSplitListMap = services.get(serviceType);
+    if (featureSplitListMap == null) {
       assert false
           : "Unexpected attempt to get service implementations for non-service type `"
               + serviceType.toSourceString()
               + "`";
       return ImmutableList.of();
     }
-    return serviceImplementationTypes;
+    ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+    for (List<DexType> implementations : featureSplitListMap.values()) {
+      builder.addAll(implementations);
+    }
+    return builder.build();
+  }
+
+  public boolean hasServiceImplementationsInFeature(DexType serviceType) {
+    if (appView.options().featureSplitConfiguration == null) {
+      return false;
+    }
+    Map<FeatureSplit, List<DexType>> featureImplementations = services.get(serviceType);
+    if (featureImplementations == null || featureImplementations.isEmpty()) {
+      assert false
+          : "Unexpected attempt to get service implementations for non-service type `"
+              + serviceType.toSourceString()
+              + "`";
+      return true;
+    }
+    if (featureImplementations.size() > 1
+        || !featureImplementations.containsKey(FeatureSplit.BASE)) {
+      return true;
+    }
+    // Check if service is defined feature
+    DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
+    if (appView.options().featureSplitConfiguration.isInFeature(serviceClass)) {
+      return true;
+    }
+    for (DexType dexType : featureImplementations.get(FeatureSplit.BASE)) {
+      DexProgramClass implementationClass = appView.definitionForProgramType(dexType);
+      if (appView.options().featureSplitConfiguration.isInFeature(implementationClass)) {
+        return true;
+      }
+    }
+    return false;
   }
 
   public AppServices rewrittenWithLens(GraphLense graphLens) {
-    ImmutableMap.Builder<DexType, List<DexType>> rewrittenServices = ImmutableMap.builder();
-    for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
+    ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenFeatureMappings =
+        ImmutableMap.builder();
+    for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
       DexType rewrittenServiceType = graphLens.lookupType(entry.getKey());
-      ImmutableList.Builder<DexType> rewrittenServiceImplementationTypes = ImmutableList.builder();
-      for (DexType serviceImplementationType : entry.getValue()) {
-        rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+      ImmutableMap.Builder<FeatureSplit, List<DexType>> rewrittenFeatureImplementations =
+          ImmutableMap.builder();
+      for (Entry<FeatureSplit, List<DexType>> featureSplitImpls : entry.getValue().entrySet()) {
+        ImmutableList.Builder<DexType> rewrittenServiceImplementationTypes =
+            ImmutableList.builder();
+        for (DexType serviceImplementationType : featureSplitImpls.getValue()) {
+          rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+        }
+        rewrittenFeatureImplementations.put(
+            featureSplitImpls.getKey(), rewrittenServiceImplementationTypes.build());
       }
-      rewrittenServices.put(rewrittenServiceType, rewrittenServiceImplementationTypes.build());
+      rewrittenFeatureMappings.put(rewrittenServiceType, rewrittenFeatureImplementations.build());
     }
-    return new AppServices(appView, rewrittenServices.build());
+    return new AppServices(appView, rewrittenFeatureMappings.build());
   }
 
   public AppServices prunedCopy(Collection<DexType> removedClasses) {
-    ImmutableMap.Builder<DexType, List<DexType>> rewrittenServicesBuilder = ImmutableMap.builder();
-    for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
-      if (!removedClasses.contains(entry.getKey())) {
-        DexType serviceType = entry.getKey();
+    ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenServicesBuilder =
+        ImmutableMap.builder();
+    for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
+      if (removedClasses.contains(entry.getKey())) {
+        continue;
+      }
+      ImmutableMap.Builder<FeatureSplit, List<DexType>> prunedFeatureSplitImpls =
+          ImmutableMap.builder();
+      for (Entry<FeatureSplit, List<DexType>> featureSplitEntry : entry.getValue().entrySet()) {
         ImmutableList.Builder<DexType> rewrittenServiceImplementationTypesBuilder =
             ImmutableList.builder();
-        for (DexType serviceImplementationType : entry.getValue()) {
+        for (DexType serviceImplementationType : featureSplitEntry.getValue()) {
           if (!removedClasses.contains(serviceImplementationType)) {
             rewrittenServiceImplementationTypesBuilder.add(serviceImplementationType);
           }
         }
-        List<DexType> rewrittenServiceImplementationTypes =
+        List<DexType> prunedFeatureSplitImplementations =
             rewrittenServiceImplementationTypesBuilder.build();
-        if (rewrittenServiceImplementationTypes.size() > 0) {
-          rewrittenServicesBuilder.put(
-              serviceType, rewrittenServiceImplementationTypesBuilder.build());
+        if (prunedFeatureSplitImplementations.size() > 0) {
+          prunedFeatureSplitImpls.put(
+              featureSplitEntry.getKey(), rewrittenServiceImplementationTypesBuilder.build());
         }
       }
+      ImmutableMap<FeatureSplit, List<DexType>> prunedServiceImplementations =
+          prunedFeatureSplitImpls.build();
+      if (prunedServiceImplementations.size() > 0) {
+        rewrittenServicesBuilder.put(entry.getKey(), prunedServiceImplementations);
+      }
     }
     return new AppServices(appView, rewrittenServicesBuilder.build());
   }
 
   private boolean verifyRewrittenWithLens() {
-    for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
+    for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
       assert entry.getKey() == appView.graphLense().lookupType(entry.getKey());
-      for (DexType type : entry.getValue()) {
-        assert type == appView.graphLense().lookupType(type);
+      for (Entry<FeatureSplit, List<DexType>> featureEntry : entry.getValue().entrySet()) {
+        for (DexType type : featureEntry.getValue()) {
+          assert type == appView.graphLense().lookupType(type);
+        }
       }
     }
     return true;
   }
 
   public void visit(BiConsumer<DexType, List<DexType>> consumer) {
-    services.forEach(consumer);
+    services.forEach(
+        (type, featureImpls) -> {
+          ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+          featureImpls.values().forEach(builder::addAll);
+          consumer.accept(type, builder.build());
+        });
   }
 
   public static Builder builder(AppView<?> appView) {
@@ -123,7 +183,7 @@
   public static class Builder {
 
     private final AppView<?> appView;
-    private final Map<DexType, List<DexType>> services = new IdentityHashMap<>();
+    private final Map<DexType, Map<FeatureSplit, List<DexType>>> services = new LinkedHashMap<>();
 
     private Builder(AppView<?> appView) {
       this.appView = appView;
@@ -131,14 +191,22 @@
 
     public AppServices build() {
       for (DataResourceProvider provider : appView.appInfo().app().dataResourceProviders) {
-        readServices(provider);
+        readServices(provider, FeatureSplit.BASE);
+      }
+      List<FeatureSplit> featureSplits =
+          appView.options().featureSplitConfiguration.getFeatureSplits();
+      for (FeatureSplit featureSplit : featureSplits) {
+        for (ProgramResourceProvider provider : featureSplit.getProgramResourceProviders()) {
+          readServices(provider.getDataResourceProvider(), featureSplit);
+        }
       }
       return new AppServices(appView, services);
     }
 
-    private void readServices(DataResourceProvider dataResourceProvider) {
+    private void readServices(
+        DataResourceProvider dataResourceProvider, FeatureSplit featureSplit) {
       try {
-        dataResourceProvider.accept(new DataResourceProviderVisitor());
+        dataResourceProvider.accept(new DataResourceProviderVisitor(featureSplit));
       } catch (ResourceException e) {
         throw new CompilationError(e.getMessage(), e);
       }
@@ -146,6 +214,12 @@
 
     private class DataResourceProviderVisitor implements Visitor {
 
+      private final FeatureSplit featureSplit;
+
+      public DataResourceProviderVisitor(FeatureSplit featureSplit) {
+        this.featureSplit = featureSplit;
+      }
+
       @Override
       public void visit(DataDirectoryResource directory) {
         // Ignore.
@@ -162,8 +236,10 @@
               DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
               byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
               String contents = new String(bytes, Charset.defaultCharset());
+              Map<FeatureSplit, List<DexType>> featureSplitImplementations =
+                  services.computeIfAbsent(serviceType, k -> new LinkedHashMap<>());
               List<DexType> serviceImplementations =
-                  services.computeIfAbsent(serviceType, (key) -> new ArrayList<>());
+                  featureSplitImplementations.computeIfAbsent(featureSplit, f -> new ArrayList<>());
               readServiceImplementationsForService(
                   contents, file.getOrigin(), serviceImplementations);
             }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 263cadd..71a2a18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -132,6 +132,11 @@
         continue;
       }
 
+      // Check that we are not service loading anything from a feature into base.
+      if (appView.appServices().hasServiceImplementationsInFeature(constClass.getValue())) {
+        continue;
+      }
+
       // Check that ClassLoader used is the ClassLoader defined for the the service configuration
       // that we are instantiating or NULL.
       InvokeVirtual classLoaderInvoke =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
index 39a1b03..c290f26 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
@@ -5,11 +5,14 @@
 package com.android.tools.r8.dexsplitter;
 
 import static com.android.tools.r8.rewrite.ServiceLoaderRewritingTest.getServiceLoaderLoads;
+import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
@@ -36,18 +39,15 @@
   }
 
   @Test
-  public void testR8AllServiceConfigurationInBase() throws Exception {
+  public void testR8AllServiceConfigurationInBaseAndNoTypesInFeatures() throws Exception {
     Path base = temp.newFile("base.zip").toPath();
     Path feature1Path = temp.newFile("feature1.zip").toPath();
-    Path feature2Path = temp.newFile("feature2.zip").toPath();
     testForR8(parameters.getBackend())
-        .addProgramClasses(Base.class, I.class)
+        .addProgramClasses(Base.class, I.class, Feature1I.class, Feature2I.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Base.class)
         .addFeatureSplit(
-            builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
-        .addFeatureSplit(
-            builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
+            builder -> simpleSplitProvider(builder, feature1Path, temp, Feature3Dummy.class))
         .addDataEntryResources(
             DataEntryResource.fromBytes(
                 StringUtils.lines(Feature1I.class.getTypeName(), Feature2I.class.getTypeName())
@@ -57,59 +57,99 @@
         .compile()
         .inspect(
             inspector -> {
-              // TODO(b/157426812): This should be 1.
               assertEquals(0, getServiceLoaderLoads(inspector, Base.class));
             })
         .writeToZip(base)
-        .addRunClasspathFiles(feature1Path, feature2Path)
         .run(parameters.getRuntime(), Base.class)
         .assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
   }
 
   @Test
+  public void testR8AllServiceConfigurationInBase() throws Exception {
+    Path base = temp.newFile("base.zip").toPath();
+    Path feature1Path = temp.newFile("feature1.zip").toPath();
+    Path feature2Path = temp.newFile("feature2.zip").toPath();
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Base.class, I.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Base.class)
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
+            .addDataEntryResources(
+                DataEntryResource.fromBytes(
+                    StringUtils.lines(Feature1I.class.getTypeName(), Feature2I.class.getTypeName())
+                        .getBytes(),
+                    "META-INF/services/" + I.class.getTypeName(),
+                    Origin.unknown()))
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+                })
+            .writeToZip(base)
+            .addRunClasspathFiles(feature1Path, feature2Path)
+            .run(parameters.getRuntime(), Base.class);
+    // TODO(b/160888348): This is failing on 7.0
+    if (parameters.getRuntime().isDex()
+        && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+      runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+    } else {
+      runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+    }
+  }
+
+  @Test
   public void testR8AllLoaded() throws Exception {
     Path base = temp.newFile("base.zip").toPath();
     Path feature1Path = temp.newFile("feature1.zip").toPath();
     Path feature2Path = temp.newFile("feature2.zip").toPath();
-    testForR8(parameters.getBackend())
-        .addProgramClasses(Base.class, I.class)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(Base.class)
-        .addFeatureSplit(
-            builder ->
-                splitWithNonJavaFile(
-                    builder,
-                    feature1Path,
-                    temp,
-                    ImmutableList.of(
-                        new Pair<>(
-                            "META-INF/services/" + I.class.getTypeName(),
-                            StringUtils.lines(Feature1I.class.getTypeName()))),
-                    true,
-                    Feature1I.class))
-        .addFeatureSplit(
-            builder ->
-                splitWithNonJavaFile(
-                    builder,
-                    feature2Path,
-                    temp,
-                    ImmutableList.of(
-                        new Pair<>(
-                            "META-INF/services/" + I.class.getTypeName(),
-                            StringUtils.lines(Feature2I.class.getTypeName()))),
-                    true,
-                    Feature2I.class))
-        .compile()
-        .inspect(
-            inspector -> {
-              assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
-            })
-        .writeToZip(base)
-        .addRunClasspathFiles(feature1Path, feature2Path)
-        .run(parameters.getRuntime(), Base.class)
-        // TODO(b/157426812): Should output
-        // .assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
-        .assertSuccessWithOutput("");
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Base.class, I.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Base.class)
+            .addFeatureSplit(
+                builder ->
+                    splitWithNonJavaFile(
+                        builder,
+                        feature1Path,
+                        temp,
+                        ImmutableList.of(
+                            new Pair<>(
+                                "META-INF/services/" + I.class.getTypeName(),
+                                StringUtils.lines(Feature1I.class.getTypeName()))),
+                        true,
+                        Feature1I.class))
+            .addFeatureSplit(
+                builder ->
+                    splitWithNonJavaFile(
+                        builder,
+                        feature2Path,
+                        temp,
+                        ImmutableList.of(
+                            new Pair<>(
+                                "META-INF/services/" + I.class.getTypeName(),
+                                StringUtils.lines(Feature2I.class.getTypeName()))),
+                        true,
+                        Feature2I.class))
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+                })
+            .writeToZip(base)
+            .addRunClasspathFiles(feature1Path, feature2Path)
+            .run(parameters.getRuntime(), Base.class);
+    // TODO(b/160888348): This is failing on 7.0
+    if (parameters.getRuntime().isDex()
+        && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+      runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+    } else {
+      runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+    }
   }
 
   @Test
@@ -118,42 +158,47 @@
     Path feature1Path = temp.newFile("feature1.zip").toPath();
     Path feature2Path = temp.newFile("feature2.zip").toPath();
     Path feature3Path = temp.newFile("feature3.zip").toPath();
-    testForR8(parameters.getBackend())
-        .addProgramClasses(Base.class, I.class)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(Base.class)
-        .addFeatureSplit(
-            builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
-        .addFeatureSplit(
-            builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
-        .addFeatureSplit(
-            builder ->
-                splitWithNonJavaFile(
-                    builder,
-                    feature3Path,
-                    temp,
-                    ImmutableList.of(
-                        new Pair<>(
-                            "META-INF/services/" + I.class.getTypeName(),
-                            StringUtils.lines(
-                                Feature1I.class.getTypeName(), Feature2I.class.getTypeName()))),
-                    true,
-                    Feature3Dummy.class))
-        .compile()
-        .inspect(
-            inspector -> {
-              assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
-            })
-        .writeToZip(base)
-        .addRunClasspathFiles(feature1Path, feature2Path, feature3Path)
-        .run(parameters.getRuntime(), Base.class)
-        // TODO(b/157426812): Should output
-        // .assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
-        .assertSuccessWithOutput("");
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Base.class, I.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Base.class)
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
+            .addFeatureSplit(
+                builder ->
+                    splitWithNonJavaFile(
+                        builder,
+                        feature3Path,
+                        temp,
+                        ImmutableList.of(
+                            new Pair<>(
+                                "META-INF/services/" + I.class.getTypeName(),
+                                StringUtils.lines(
+                                    Feature1I.class.getTypeName(), Feature2I.class.getTypeName()))),
+                        true,
+                        Feature3Dummy.class))
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+                })
+            .writeToZip(base)
+            .addRunClasspathFiles(feature1Path, feature2Path, feature3Path)
+            .run(parameters.getRuntime(), Base.class);
+    // TODO(b/160888348): This is failing on 7.0
+    if (parameters.getRuntime().isDex()
+        && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+      runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+    } else {
+      runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+    }
   }
 
   @Test
-  public void testR8NotFound() throws Exception {
+  public void testR8OnlyFeature2() throws Exception {
     Path base = temp.newFile("base.zip").toPath();
     Path feature1Path = temp.newFile("feature1.zip").toPath();
     Path feature2Path = temp.newFile("feature2.zip").toPath();
@@ -193,9 +238,8 @@
         .writeToZip(base)
         .addRunClasspathFiles(feature2Path)
         .run(parameters.getRuntime(), Base.class)
-        // TODO(b/157426812): Should output
-        // .assertSuccessWithOutputLines("Feature2I.foo()");
-        .assertSuccessWithOutput("");
+        // TODO(b/160889305): This should work.
+        .assertFailureWithErrorThatMatches(containsString("java.lang.ClassNotFoundException"));
   }
 
   public interface I {