|  | // Copyright (c) 2017, the R8 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. | 
|  | package com.android.tools.r8.bisect; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  |  | 
|  | import com.android.tools.r8.TestBase; | 
|  | import com.android.tools.r8.TestParameters; | 
|  | import com.android.tools.r8.TestParametersCollection; | 
|  | import com.android.tools.r8.bisect.BisectOptions.Result; | 
|  | import com.android.tools.r8.dex.ApplicationReader; | 
|  | import com.android.tools.r8.graph.DexApplication; | 
|  | import com.android.tools.r8.graph.DexProgramClass; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.smali.SmaliBuilder; | 
|  | import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.Timing; | 
|  | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import java.nio.file.Path; | 
|  | import java.util.concurrent.ExecutorService; | 
|  | import java.util.concurrent.Executors; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.Parameterized; | 
|  | import org.junit.runners.Parameterized.Parameters; | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class BisectTest extends TestBase { | 
|  |  | 
|  | @Parameters(name = "{0}") | 
|  | public static TestParametersCollection parameters() { | 
|  | return TestParameters.builder().withNoneRuntime().build(); | 
|  | } | 
|  |  | 
|  | public BisectTest(TestParameters parameters) { | 
|  | parameters.assertNoneRuntime(); | 
|  | } | 
|  |  | 
|  | private final String[] CLASSES = {"A", "B", "C", "D", "E", "F", "G", "H"}; | 
|  | private final String ERRONEOUS_CLASS = "F"; | 
|  | private final String ERRONEOUS_METHOD = "foo"; | 
|  | private final String VALID_METHOD = "bar"; | 
|  |  | 
|  | // Set during build to more easily inspect later. | 
|  | private MethodSignature erroneousMethodSignature = null; | 
|  |  | 
|  | // Build "good" application with no method in "F". | 
|  | AndroidApp buildGood() throws Exception { | 
|  | SmaliBuilder builderGood = new SmaliBuilder(); | 
|  | for (String clazz : CLASSES) { | 
|  | builderGood.addClass(clazz); | 
|  | builderGood.addStaticMethod( | 
|  | "void", VALID_METHOD, ImmutableList.of(), 0, "return-void"); | 
|  | } | 
|  | return AndroidApp.builder().addDexProgramData(builderGood.compile(), Origin.unknown()).build(); | 
|  | } | 
|  |  | 
|  | AndroidApp buildBad() throws Exception { | 
|  | // Build "bad" application with a method "foo" in "F". | 
|  | SmaliBuilder builderBad = new SmaliBuilder(); | 
|  | for (String clazz : CLASSES) { | 
|  | builderBad.addClass(clazz); | 
|  | if (clazz.equals(ERRONEOUS_CLASS)) { | 
|  | erroneousMethodSignature = builderBad.addStaticMethod( | 
|  | "void", ERRONEOUS_METHOD, ImmutableList.of(), 0, "return-void"); | 
|  | } else { | 
|  | builderBad.addStaticMethod( | 
|  | "void", VALID_METHOD, ImmutableList.of(), 0, "return-void"); | 
|  | } | 
|  | } | 
|  | return AndroidApp.builder().addDexProgramData(builderBad.compile(), Origin.unknown()).build(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void bisectWithExternalCommand() throws Exception { | 
|  | AndroidApp goodInput = buildGood(); | 
|  | AndroidApp badInput = buildBad(); | 
|  | ExecutorService executor = Executors.newWorkStealingPool(); | 
|  | DexProgramClass clazz = null; | 
|  | Path stateFile = temp.newFolder().toPath().resolve("state.txt"); | 
|  | try { | 
|  | Result lastResult = Result.UNKNOWN; | 
|  | while (clazz == null) { | 
|  | InternalOptions options = new InternalOptions(); | 
|  | Timing timing = Timing.empty(); | 
|  | DexApplication appGood = new ApplicationReader(goodInput, options, timing).read(); | 
|  | DexApplication appBad = new ApplicationReader(badInput, options, timing).read(); | 
|  | BisectState state = new BisectState(appGood, appBad, stateFile); | 
|  | state.read(); | 
|  | if (lastResult != Result.UNKNOWN) { | 
|  | state.setPreviousResult(lastResult); | 
|  | } | 
|  | Path output = temp.newFolder().toPath(); | 
|  | clazz = Bisect.run(state, null, output, executor); | 
|  | state.write(); | 
|  | if (clazz == null) { | 
|  | lastResult = command(new CodeInspector(output.resolve("classes.dex"))); | 
|  | } | 
|  | } | 
|  | } finally { | 
|  | executor.shutdown(); | 
|  | } | 
|  | assertEquals(clazz.type.toString(), ERRONEOUS_CLASS); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void bisectWithInternalCommand() throws Exception { | 
|  | InternalOptions options = new InternalOptions(); | 
|  | Timing timing = Timing.empty(); | 
|  | DexApplication appGood = new ApplicationReader(buildGood(), options, timing).read(); | 
|  | DexApplication appBad = new ApplicationReader(buildBad(), options, timing).read(); | 
|  | ExecutorService executor = Executors.newWorkStealingPool(); | 
|  | try { | 
|  | BisectState state = new BisectState(appGood, appBad, null); | 
|  | DexProgramClass clazz = | 
|  | Bisect.run( | 
|  | state, app -> command(new CodeInspector(app)), temp.newFolder().toPath(), executor); | 
|  | assertEquals(clazz.type.toString(), ERRONEOUS_CLASS); | 
|  | } finally { | 
|  | executor.shutdown(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private Result command(CodeInspector inspector) { | 
|  | if (inspector | 
|  | .clazz(ERRONEOUS_CLASS) | 
|  | .method(erroneousMethodSignature.returnType, | 
|  | erroneousMethodSignature.name, | 
|  | erroneousMethodSignature.parameterTypes) | 
|  | .isPresent()) { | 
|  | return Result.BAD; | 
|  | } | 
|  | return Result.GOOD; | 
|  | } | 
|  | } |