| // Copyright (c) 2018, 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.debug; |
| |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.ClassFileConsumer; |
| import com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.R8; |
| import com.android.tools.r8.R8Command; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.ProcessResult; |
| import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.FrameInspector; |
| import java.nio.file.Path; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import org.apache.harmony.jpda.tests.framework.jdwp.Value; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| /** Tests local variable information. */ |
| @RunWith(Parameterized.class) |
| public class BreakPointEventsTestRunner extends DebugTestBase { |
| |
| public static final Class<BreakPointEventsTest> CLASS = BreakPointEventsTest.class; |
| public static final String FILE = CLASS.getSimpleName() + ".java"; |
| |
| private static Path getClassFilePath() { |
| return ToolHelper.getClassFileForTestClass(CLASS); |
| } |
| |
| public static DebugTestConfig cfConfig() throws Exception { |
| return new CfDebugTestConfig(ToolHelper.getClassPathForTests()); |
| } |
| |
| public static DebugTestConfig d8Config() throws Exception { |
| return new D8DebugTestConfig().compileAndAdd(temp, getClassFilePath()); |
| } |
| |
| public static DebugTestConfig r8CfConfig() throws Exception { |
| Path outCf = temp.getRoot().toPath().resolve("cf.jar"); |
| R8.run( |
| R8Command.builder() |
| .setMode(CompilationMode.DEBUG) |
| .addProgramFiles(getClassFilePath()) |
| .addLibraryFiles(ToolHelper.getJava8RuntimeJar()) |
| .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outCf)) |
| .build()); |
| return new CfDebugTestConfig(ToolHelper.getClassPathForTests()).addPaths(outCf); |
| } |
| |
| public static DebugTestConfig dxConfig() throws Exception { |
| Path cwd = ToolHelper.getClassPathForTests().toAbsolutePath(); |
| Path test = cwd.relativize(getClassFilePath().toAbsolutePath()); |
| Path out = temp.newFolder().toPath().resolve("classes.dex").toAbsolutePath(); |
| ProcessResult result = ToolHelper.runDX(cwd, "--output=" + out, test.toString()); |
| assertTrue(result.stderr, 0 == result.exitCode); |
| DebugTestConfig config = new DexDebugTestConfig(); |
| config.addPaths(out); |
| return config; |
| } |
| |
| public void printConfig(String name, DebugTestConfig config) throws Exception { |
| new DebugStreamComparator() |
| .add(name, streamDebugTest(config, CLASS.getCanonicalName(), NO_FILTER)) |
| .setPrintStates(true) |
| .setPrintMethod(true) |
| .setPrintVariables(true) |
| .setFilter(s -> s.getSourceFile().equals(FILE)) |
| .run(); |
| } |
| |
| // The main method will single-step each configuration of the test. It cannot be made a test as |
| // the event streams are unequal due to ART vs JVM line change on return and DX incorrect locals. |
| public static void main(String[] args) throws Exception { |
| temp.create(); |
| try { |
| BreakPointEventsTestRunner runner = new BreakPointEventsTestRunner("unused name"); |
| System.out.println("\n============== CF single stepping: "); |
| runner.printConfig("CF", cfConfig()); |
| System.out.println("\n============== D8 single stepping: "); |
| runner.printConfig("D8", d8Config()); |
| System.out.println("\n============== DX single stepping: "); |
| runner.printConfig("DX", dxConfig()); |
| } finally { |
| temp.delete(); |
| } |
| } |
| |
| @Parameters(name = "{0}") |
| public static Collection<String> configs() { |
| return Arrays.asList("CF", "D8", "R8/CF"); |
| } |
| |
| private final DebugTestConfig config; |
| |
| public BreakPointEventsTestRunner(String name) throws Exception { |
| if (name.equals("CF")) { |
| config = cfConfig(); |
| } else if (name.equals("D8")) { |
| config = d8Config(); |
| } else { |
| assert name.equals("R8/CF"); |
| config = r8CfConfig(); |
| } |
| } |
| |
| @Test |
| public void testSingleLineDeclarations() throws Throwable { |
| int bLine = 11; |
| Value bValueOne = Value.createInt(1); |
| Value bValueTwo = Value.createInt(2); |
| runDebugTest( |
| config, |
| CLASS.getCanonicalName(), |
| // Install breakpoint on b which is hit 6 times during execution. |
| breakpoint(CLASS.getCanonicalName(), "b"), |
| // First set of declarations will have respectively {}, {x}, and {x,y} visible in the frame. |
| run(), |
| checkLine(FILE, bLine), |
| inspect( |
| i -> { |
| FrameInspector frame = i.getFrame(1); |
| frame.checkNoLocal("x"); |
| frame.checkNoLocal("y"); |
| frame.checkNoLocal("z"); |
| }), |
| run(), |
| checkLine(FILE, bLine), |
| inspect( |
| i -> { |
| FrameInspector frame = i.getFrame(1); |
| frame.checkLocal("x", bValueOne); |
| frame.checkNoLocal("y"); |
| frame.checkNoLocal("z"); |
| }), |
| run(), |
| checkLine(FILE, bLine), |
| inspect( |
| i -> { |
| FrameInspector frame = i.getFrame(1); |
| frame.checkLocal("x", bValueOne); |
| frame.checkLocal("y", bValueOne); |
| frame.checkNoLocal("z"); |
| }), |
| // Second set of declarations should appear the same, only with another value. |
| run(), |
| checkLine(FILE, bLine), |
| inspect( |
| i -> { |
| FrameInspector frame = i.getFrame(1); |
| // This fails on DX compiled output since x will appear visible here. |
| frame.checkNoLocal("x"); |
| frame.checkNoLocal("y"); |
| frame.checkNoLocal("z"); |
| }), |
| run(), |
| checkLine(FILE, bLine), |
| inspect( |
| i -> { |
| FrameInspector frame = i.getFrame(1); |
| frame.checkLocal("x", bValueTwo); |
| frame.checkNoLocal("y"); |
| frame.checkNoLocal("z"); |
| }), |
| run(), |
| checkLine(FILE, bLine), |
| inspect( |
| i -> { |
| FrameInspector frame = i.getFrame(1); |
| frame.checkLocal("x", bValueTwo); |
| frame.checkLocal("y", bValueTwo); |
| frame.checkNoLocal("z"); |
| }), |
| // Install additonal breakpoint on return to main so we identify program execution ended. |
| breakpoint(CLASS.getCanonicalName(), "main", 22), |
| run(), |
| checkLine(FILE, 22), |
| run()); |
| } |
| } |