|  | // 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; | 
|  |  | 
|  | import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; | 
|  | import static com.android.tools.r8.TestBase.Backend.DEX; | 
|  | import static com.android.tools.r8.TestBase.testForD8; | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; | 
|  | import static org.hamcrest.CoreMatchers.not; | 
|  | import static org.hamcrest.MatcherAssert.assertThat; | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertTrue; | 
|  |  | 
|  | import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer; | 
|  | import com.android.tools.r8.TestBase.Backend; | 
|  | import com.android.tools.r8.TestRuntime.DexRuntime; | 
|  | import com.android.tools.r8.ToolHelper.ArtCommandBuilder; | 
|  | import com.android.tools.r8.ToolHelper.DexVm; | 
|  | import com.android.tools.r8.ToolHelper.ProcessResult; | 
|  | import com.android.tools.r8.benchmarks.BenchmarkResults; | 
|  | import com.android.tools.r8.debug.CfDebugTestConfig; | 
|  | import com.android.tools.r8.debug.DebugTestConfig; | 
|  | import com.android.tools.r8.debug.DexDebugTestConfig; | 
|  | import com.android.tools.r8.errors.Unreachable; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.utils.AndroidApiLevel; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.Box; | 
|  | import com.android.tools.r8.utils.DescriptorUtils; | 
|  | import com.android.tools.r8.utils.FileUtils; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.ThrowingConsumer; | 
|  | import com.android.tools.r8.utils.TriFunction; | 
|  | import com.android.tools.r8.utils.ZipUtils; | 
|  | import com.android.tools.r8.utils.codeinspector.ClassSubject; | 
|  | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ObjectArrays; | 
|  | import java.io.File; | 
|  | import java.io.IOException; | 
|  | import java.io.PrintStream; | 
|  | import java.nio.file.Path; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Arrays; | 
|  | import java.util.Collection; | 
|  | import java.util.Collections; | 
|  | import java.util.List; | 
|  | import java.util.Set; | 
|  | import java.util.concurrent.ExecutionException; | 
|  | import java.util.function.BiConsumer; | 
|  | import java.util.function.Consumer; | 
|  | import java.util.function.Function; | 
|  | import java.util.stream.Collectors; | 
|  | import org.hamcrest.Matcher; | 
|  |  | 
|  | public abstract class TestCompileResult< | 
|  | CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>> | 
|  | extends TestBaseResult<CR, RR> { | 
|  |  | 
|  | public final AndroidApp app; | 
|  | public final int minApiLevel; | 
|  | private final OutputMode outputMode; | 
|  | final List<Path> additionalRunClassPath = new ArrayList<>(); | 
|  | final List<Path> additionalBootClasspath = new ArrayList<>(); | 
|  | final List<String> vmArguments = new ArrayList<>(); | 
|  | private boolean withArt6Plus64BitsLib = false; | 
|  | private boolean withArtFrameworks = true; | 
|  | private LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration; | 
|  |  | 
|  | TestCompileResult(TestState state, AndroidApp app, int minApiLevel, OutputMode outputMode) { | 
|  | super(state); | 
|  | this.app = app; | 
|  | this.minApiLevel = minApiLevel; | 
|  | this.outputMode = outputMode; | 
|  | this.libraryDesugaringTestConfiguration = LibraryDesugaringTestConfiguration.DISABLED; | 
|  | } | 
|  |  | 
|  | TestCompileResult( | 
|  | TestState state, | 
|  | AndroidApp app, | 
|  | int minApiLevel, | 
|  | OutputMode outputMode, | 
|  | LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration) { | 
|  | super(state); | 
|  | this.app = app; | 
|  | this.minApiLevel = minApiLevel; | 
|  | this.outputMode = outputMode; | 
|  | this.libraryDesugaringTestConfiguration = libraryDesugaringTestConfiguration; | 
|  | } | 
|  |  | 
|  | public CR applyIf(boolean condition, ThrowableConsumer<CR> thenConsumer) { | 
|  | return applyIf(condition, thenConsumer, result -> {}); | 
|  | } | 
|  |  | 
|  | public CR applyIf( | 
|  | boolean condition, ThrowableConsumer<CR> thenConsumer, ThrowableConsumer<CR> elseConsumer) { | 
|  | if (condition) { | 
|  | thenConsumer.acceptWithRuntimeException(self()); | 
|  | } else { | 
|  | elseConsumer.acceptWithRuntimeException(self()); | 
|  | } | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public final CR withArt6Plus64BitsLib() { | 
|  | withArt6Plus64BitsLib = true; | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public final CR withArtFrameworks() { | 
|  | withArtFrameworks = true; | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public final AndroidApp getApp() { | 
|  | return app; | 
|  | } | 
|  |  | 
|  | public final Backend getBackend() { | 
|  | if (outputMode == OutputMode.ClassFile) { | 
|  | return Backend.CF; | 
|  | } | 
|  | assert outputMode == OutputMode.DexIndexed | 
|  | || outputMode == OutputMode.DexFilePerClass | 
|  | || outputMode == OutputMode.DexFilePerClassFile; | 
|  | return Backend.DEX; | 
|  | } | 
|  |  | 
|  | public abstract TestDiagnosticMessages getDiagnosticMessages(); | 
|  |  | 
|  | public CR inspectDiagnosticMessages(Consumer<TestDiagnosticMessages> consumer) { | 
|  | consumer.accept(getDiagnosticMessages()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public abstract Set<String> getMainDexClasses(); | 
|  |  | 
|  | public final CR inspectMainDexClasses(Consumer<Set<String>> consumer) { | 
|  | consumer.accept(getMainDexClasses()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public final CR inspectMainDexClasses(BiConsumer<CodeInspector, Set<String>> consumer) | 
|  | throws IOException { | 
|  | consumer.accept(inspector(), getMainDexClasses()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public abstract String getStdout(); | 
|  |  | 
|  | public abstract String getStderr(); | 
|  |  | 
|  | public OutputMode getOutputMode() { | 
|  | return outputMode; | 
|  | } | 
|  |  | 
|  | protected abstract RR createRunResult(TestRuntime runtime, ProcessResult result); | 
|  |  | 
|  | @Deprecated | 
|  | public RR run(Class<?> mainClass) throws ExecutionException, IOException { | 
|  | return run(mainClass.getTypeName()); | 
|  | } | 
|  |  | 
|  | @Deprecated | 
|  | public RR run(String mainClass) throws ExecutionException, IOException { | 
|  | assert !libraryDesugaringTestConfiguration.isEnabled(); | 
|  | ClassSubject mainClassSubject = inspector().clazz(mainClass); | 
|  | assertThat(mainClassSubject, isPresent()); | 
|  | switch (getBackend()) { | 
|  | case DEX: | 
|  | return runArt( | 
|  | new DexRuntime(ToolHelper.getDexVm()), | 
|  | mainClassSubject.getFinalName()); | 
|  | case CF: | 
|  | return runJava( | 
|  | TestRuntime.getDefaultJavaRuntime(), | 
|  | mainClassSubject.getFinalName()); | 
|  | default: | 
|  | throw new Unreachable(); | 
|  | } | 
|  | } | 
|  |  | 
|  | public RR run(TestRuntime runtime, Class<?> mainClass) throws ExecutionException, IOException { | 
|  | return run(runtime, mainClass.getTypeName()); | 
|  | } | 
|  |  | 
|  | public RR run(TestRuntime runtime, Class<?> mainClass, String... args) | 
|  | throws ExecutionException, IOException { | 
|  | return run(runtime, mainClass.getTypeName(), args); | 
|  | } | 
|  |  | 
|  | public RR run(TestRuntime runtime, String mainClass) throws ExecutionException, IOException { | 
|  | return run(runtime, mainClass, new String[] {}); | 
|  | } | 
|  |  | 
|  | public RR run(TestRuntime runtime, String mainClass, String... args) | 
|  | throws ExecutionException, IOException { | 
|  | assert getBackend() == runtime.getBackend(); | 
|  | if (libraryDesugaringTestConfiguration.isEnabled() | 
|  | && libraryDesugaringTestConfiguration.isAddRunClassPath()) { | 
|  | additionalRunClassPath.add(libraryDesugaringTestConfiguration.buildDesugaredLibrary(state)); | 
|  | } | 
|  | ClassSubject mainClassSubject = inspector().clazz(mainClass); | 
|  | if (!mainClassSubject.isPresent()) { | 
|  | for (Path classpathFile : additionalRunClassPath) { | 
|  | mainClassSubject = new CodeInspector(classpathFile).clazz(mainClass); | 
|  | if (mainClassSubject.isPresent()) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | assertThat("Did you forget a keep rule for the main method?", mainClassSubject, isPresent()); | 
|  | if (runtime.isDex()) { | 
|  | return runArt(runtime, mainClassSubject.getFinalName(), args); | 
|  | } | 
|  | assert runtime.isCf(); | 
|  | return runJava( | 
|  | runtime, | 
|  | ObjectArrays.concat(mainClassSubject.getFinalName(), args)); | 
|  | } | 
|  |  | 
|  | public RR runWithJaCoCo(Path output, TestRuntime runtime, String mainClass, String... args) | 
|  | throws IOException, ExecutionException { | 
|  | setSystemProperty("jacoco-agent.destfile", output.toString()); | 
|  | setSystemProperty("jacoco-agent.dumponexit", "true"); | 
|  | setSystemProperty("jacoco-agent.output", "file"); | 
|  | return run(runtime, mainClass, args); | 
|  | } | 
|  |  | 
|  | public CR addRunClasspathFiles(Path... classpath) { | 
|  | return addRunClasspathFiles(Arrays.asList(classpath)); | 
|  | } | 
|  |  | 
|  | public CR addRunClasspathFiles(List<Path> classpath) { | 
|  | additionalRunClassPath.addAll(classpath); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR addRunClasspathClasses(Class<?>... classpath) throws Exception { | 
|  | return addRunClasspathClasses(Arrays.asList(classpath)); | 
|  | } | 
|  |  | 
|  | public CR addRunClasspathClasses(List<Class<?>> classpath) throws Exception { | 
|  | if (getBackend() == Backend.DEX) { | 
|  | return addRunClasspathFiles( | 
|  | testForD8(state.getTempFolder()) | 
|  | .addProgramClasses(classpath) | 
|  | .setMinApi(minApiLevel) | 
|  | .compile() | 
|  | .writeToZip()); | 
|  | } | 
|  | assert getBackend() == Backend.CF; | 
|  | try { | 
|  | Path path = state.getNewTempFolder().resolve("runtime-classes.jar"); | 
|  | ArchiveConsumer consumer = new ArchiveConsumer(path); | 
|  | for (Class clazz : classpath) { | 
|  | consumer.accept( | 
|  | ByteDataView.of(ToolHelper.getClassAsBytes(clazz)), | 
|  | DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()), | 
|  | null); | 
|  | } | 
|  | consumer.finished(null); | 
|  | additionalRunClassPath.addAll(Collections.singletonList(path)); | 
|  | return self(); | 
|  | } catch (IOException e) { | 
|  | throw new RuntimeException(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | public CR addRunClasspathClassFileData(byte[]... classes) throws Exception { | 
|  | return addRunClasspathClassFileData(Arrays.asList(classes)); | 
|  | } | 
|  |  | 
|  | public CR addRunClasspathClassFileData(Collection<byte[]> classes) throws Exception { | 
|  | if (getBackend() == Backend.DEX) { | 
|  | additionalRunClassPath.add( | 
|  | testForD8(state.getTempFolder()) | 
|  | .addProgramClassFileData(classes) | 
|  | .setMinApi(minApiLevel) | 
|  | .compile() | 
|  | .writeToZip()); | 
|  | return self(); | 
|  | } | 
|  | assert getBackend() == Backend.CF; | 
|  | try { | 
|  | AndroidApp.Builder appBuilder = AndroidApp.builder(); | 
|  | for (byte[] clazz : classes) { | 
|  | appBuilder.addClassProgramData(clazz, Origin.unknown()); | 
|  | } | 
|  | Path path = state.getNewTempFolder().resolve("runtime-classes.jar"); | 
|  | appBuilder.build().writeToZip(path, OutputMode.ClassFile); | 
|  | additionalRunClassPath.add(path); | 
|  | return self(); | 
|  | } catch (IOException e) { | 
|  | throw new RuntimeException(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | public CR addBootClasspathClasses(Class<?>... classes) throws Exception { | 
|  | return addBootClasspathClasses(Arrays.asList(classes)); | 
|  | } | 
|  |  | 
|  | public CR addBootClasspathClasses(List<Class<?>> classes) throws Exception { | 
|  | if (getBackend() == Backend.DEX) { | 
|  | additionalBootClasspath.add( | 
|  | testForD8(state.getTempFolder()) | 
|  | .addProgramClasses(classes) | 
|  | .setMinApi(minApiLevel) | 
|  | .compile() | 
|  | .writeToZip()); | 
|  | return self(); | 
|  | } | 
|  | assert getBackend() == Backend.CF; | 
|  | try { | 
|  | Path path = state.getNewTempFolder().resolve("runtime-classes.jar"); | 
|  | ArchiveConsumer consumer = new ArchiveConsumer(path); | 
|  | for (Class<?> clazz : classes) { | 
|  | consumer.accept( | 
|  | ByteDataView.of(ToolHelper.getClassAsBytes(clazz)), | 
|  | DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()), | 
|  | null); | 
|  | } | 
|  | consumer.finished(null); | 
|  | additionalBootClasspath.add(path); | 
|  | return self(); | 
|  | } catch (IOException e) { | 
|  | throw new RuntimeException(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | public CR addDesugaredCoreLibraryRunClassPath( | 
|  | Function<AndroidApiLevel, Path> classPathSupplier, AndroidApiLevel minAPILevel) { | 
|  | addRunClasspathFiles(classPathSupplier.apply(minAPILevel)); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR addDesugaredCoreLibraryRunClassPath( | 
|  | TriFunction<AndroidApiLevel, String, Boolean, Path> classPathSupplier, | 
|  | AndroidApiLevel minAPILevel, | 
|  | String keepRules, | 
|  | boolean shrink) { | 
|  | addRunClasspathFiles(classPathSupplier.apply(minAPILevel, keepRules, shrink)); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR enableRuntimeAssertions() { | 
|  | assert getBackend() == Backend.CF; | 
|  | if (!vmArguments.contains("-ea")) { | 
|  | vmArguments.add("-ea"); | 
|  | } | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR enableJVMPreview() { | 
|  | assert getBackend() == Backend.CF; | 
|  | if (!vmArguments.contains("--enable-preview")) { | 
|  | vmArguments.add("--enable-preview"); | 
|  | } | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR enableRuntimeAssertions(boolean enable) { | 
|  | if (getBackend() == Backend.CF) { | 
|  | if (enable) { | 
|  | enableRuntimeAssertions(); | 
|  | } | 
|  | } else { | 
|  | // Assertions cannot be enabled on dex VMs. | 
|  | assert !enable; | 
|  | } | 
|  |  | 
|  | if (enable) { | 
|  | if (!this.vmArguments.contains("-ea")) { | 
|  | this.vmArguments.add("-ea"); | 
|  | } | 
|  | } | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR setSystemProperty(String name, String value) { | 
|  | vmArguments.add("-D" + name + "=" + value); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR writeSingleDexOutputToFile(Path file) throws IOException { | 
|  | assertTrue(getApp().getClassProgramResourcesForTesting().isEmpty()); | 
|  | try { | 
|  | List<ProgramResource> dexProgramSources = getApp().getDexProgramResourcesForTesting(); | 
|  | assertEquals(1, dexProgramSources.size()); | 
|  | FileUtils.writeToFile(file, null, dexProgramSources.get(0).getBytes()); | 
|  | return self(); | 
|  | } catch (ResourceException e) { | 
|  | throw new IOException(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | public Path writeToZip() throws IOException { | 
|  | Path file = state.getNewTempFolder().resolve("out.zip"); | 
|  | writeToZip(file); | 
|  | return file; | 
|  | } | 
|  |  | 
|  | public CR writeToZip(Consumer<Path> fn) throws IOException { | 
|  | fn.accept(writeToZip()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR writeToZip(Path file) throws IOException { | 
|  | app.writeToZip(file, getOutputMode()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public Path writeToDirectory() throws IOException { | 
|  | Path directory = state.getNewTempFolder(); | 
|  | writeToDirectory(directory); | 
|  | return directory; | 
|  | } | 
|  |  | 
|  | public CR writeToDirectory(Path directory) throws IOException { | 
|  | app.writeToDirectory(directory, getOutputMode()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CodeInspector inspector() throws IOException { | 
|  | return new CodeInspector(app); | 
|  | } | 
|  |  | 
|  | public CodeInspector inspector(Consumer<InternalOptions> debugOptionsConsumer) | 
|  | throws IOException { | 
|  | return new CodeInspector(app, debugOptionsConsumer); | 
|  | } | 
|  |  | 
|  | public <E extends Throwable> CR inspect(ThrowingConsumer<CodeInspector, E> consumer) | 
|  | throws IOException, E { | 
|  | consumer.accept(inspector()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public <E extends Throwable> CR inspectWithOptions( | 
|  | ThrowingConsumer<CodeInspector, E> consumer, Consumer<InternalOptions> debugOptionsConsumer) | 
|  | throws IOException, E { | 
|  | consumer.accept(inspector(debugOptionsConsumer)); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertNoMessages() { | 
|  | getDiagnosticMessages().assertNoMessages(); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertOnlyInfos() { | 
|  | getDiagnosticMessages().assertOnlyInfos(); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertOnlyWarnings() { | 
|  | getDiagnosticMessages().assertOnlyWarnings(); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertOnlyErrors() { | 
|  | getDiagnosticMessages().assertOnlyErrors(); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertDiagnosticThatMatches(Matcher<Diagnostic> matcher) { | 
|  | getDiagnosticMessages().assertDiagnosticThatMatches(matcher); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertInfoThatMatches(Matcher<Diagnostic> matcher) { | 
|  | getDiagnosticMessages().assertInfoThatMatches(matcher); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertInfoMessageThatMatches(Matcher<String> matcher) { | 
|  | getDiagnosticMessages().assertInfoThatMatches(diagnosticMessage(matcher)); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertAllInfosMatch(Matcher<Diagnostic> matcher) { | 
|  | getDiagnosticMessages().assertNoInfosMatch(not(matcher)); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertAllInfoMessagesMatch(Matcher<String> matcher) { | 
|  | return assertNoInfoMessageThatMatches(not(matcher)); | 
|  | } | 
|  |  | 
|  | public CR assertNoInfoThatMatches(Matcher<Diagnostic> matcher) { | 
|  | getDiagnosticMessages().assertNoInfosMatch(matcher); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertNoInfoMessageThatMatches(Matcher<String> matcher) { | 
|  | getDiagnosticMessages().assertNoInfosMatch(diagnosticMessage(matcher)); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertAtLeastOneInfoMessage() { | 
|  | assertTrue(getDiagnosticMessages().getInfos().size() >= 1); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertInfosCount(int count) { | 
|  | getDiagnosticMessages().assertInfosCount(count); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertNoInfoMessages() { | 
|  | getDiagnosticMessages().assertNoInfos(); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertWarningMessageThatMatches(Matcher<String> matcher) { | 
|  | getDiagnosticMessages().assertWarningMessageThatMatches(matcher); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertWarningThatMatches(Matcher<Diagnostic> matcher) { | 
|  | getDiagnosticMessages().assertWarningThatMatches(matcher); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertAllWarningMessagesMatch(Matcher<String> matcher) { | 
|  | getDiagnosticMessages().assertHasWarnings().assertAllWarningsMatch(diagnosticMessage(matcher)); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertNoWarningMessages() { | 
|  | getDiagnosticMessages().assertNoWarnings(); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertNoWarningMessageThatMatches(Matcher<String> matcher) { | 
|  | getDiagnosticMessages().assertNoWarningsMatch(diagnosticMessage(matcher)); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertErrorMessageThatMatches(Matcher<String> matcher) { | 
|  | getDiagnosticMessages().assertErrorMessageThatMatches(matcher); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertNoErrorMessages() { | 
|  | getDiagnosticMessages().assertNoErrors(); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertNoStdout() { | 
|  | assertEquals("", getStdout()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertStdoutThatMatches(Matcher<String> matcher) { | 
|  | assertThat(getStdout(), matcher); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertNoStderr() { | 
|  | assertEquals("", getStderr()); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR assertStderrThatMatches(Matcher<String> matcher) { | 
|  | assertThat(getStderr(), matcher); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR disassemble(PrintStream ps) throws IOException, ExecutionException { | 
|  | ToolHelper.disassemble(app, ps); | 
|  | return self(); | 
|  | } | 
|  |  | 
|  | public CR disassemble() throws IOException, ExecutionException { | 
|  | return disassemble(System.out); | 
|  | } | 
|  |  | 
|  | public DebugTestConfig debugConfig() { | 
|  | // Rethrow exceptions since debug config is usually used in a delayed wrapper which | 
|  | // does not declare exceptions. | 
|  | try { | 
|  | Path out = state.getNewTempFolder().resolve("out.zip"); | 
|  | app.writeToZip(out, getOutputMode()); | 
|  | switch (getBackend()) { | 
|  | case CF: | 
|  | return new CfDebugTestConfig().addPaths(out); | 
|  | case DEX: | 
|  | return new DexDebugTestConfig().addPaths(out); | 
|  | default: | 
|  | throw new Unreachable(); | 
|  | } | 
|  | } catch (IOException e) { | 
|  | throw new RuntimeException(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | private RR runJava(TestRuntime runtime, String... arguments) throws IOException { | 
|  | assert runtime != null; | 
|  | Path out = state.getNewTempFolder().resolve("out.zip"); | 
|  | app.writeToZip(out, OutputMode.ClassFile); | 
|  | List<Path> classPath = | 
|  | ImmutableList.<Path>builder().addAll(additionalRunClassPath).add(out).build(); | 
|  | ProcessResult result = | 
|  | ToolHelper.runJava( | 
|  | runtime.asCf(), vmArguments, additionalBootClasspath, classPath, arguments); | 
|  | return createRunResult(runtime, result); | 
|  | } | 
|  |  | 
|  | RR runArt(TestRuntime runtime, String mainClass, String... arguments) throws IOException { | 
|  | DexVm vm = runtime.asDex().getVm(); | 
|  | // TODO(b/127785410): Always assume a non-null runtime. | 
|  | Path out = state.getNewTempFolder().resolve("out.zip"); | 
|  | app.writeToZip(out, OutputMode.DexIndexed); | 
|  | List<String> classPath = | 
|  | ImmutableList.<String>builder() | 
|  | .addAll( | 
|  | additionalRunClassPath.stream().map(Path::toString).collect(Collectors.toList())) | 
|  | .add(out.toString()) | 
|  | .build(); | 
|  | Consumer<ArtCommandBuilder> commandConsumer = | 
|  | withArt6Plus64BitsLib && vm.getVersion().isNewerThanOrEqual(DexVm.Version.V6_0_1) | 
|  | ? builder -> builder.appendArtOption("--64") | 
|  | : builder -> {}; | 
|  | commandConsumer = | 
|  | commandConsumer.andThen( | 
|  | builder -> { | 
|  | if (!additionalBootClasspath.isEmpty()) { | 
|  | DexVm dexVm = runtime.asDex().getVm(); | 
|  | if (dexVm.isNewerThan(DexVm.ART_4_4_4_HOST)) { | 
|  | builder.appendArtOption("-Ximage:/system/non/existent/image.art"); | 
|  | builder.appendArtOption("-Xnoimage-dex2oat"); | 
|  | } | 
|  | try { | 
|  | for (String s : ToolHelper.getBootLibs(dexVm)) { | 
|  | builder.appendBootClasspath(new File(s).getCanonicalPath()); | 
|  | } | 
|  | } catch (Exception e) { | 
|  | throw new RuntimeException(); | 
|  | } | 
|  | additionalBootClasspath.forEach( | 
|  | path -> builder.appendBootClasspath(path.toString())); | 
|  | } | 
|  | for (String vmArgument : vmArguments) { | 
|  | builder.appendArtOption(vmArgument); | 
|  | } | 
|  | }); | 
|  | ProcessResult result = | 
|  | ToolHelper.runArtRaw( | 
|  | classPath, mainClass, commandConsumer, vm, withArtFrameworks, arguments); | 
|  | return createRunResult(runtime, result); | 
|  | } | 
|  |  | 
|  | public Dex2OatTestRunResult runDex2Oat(TestRuntime runtime) throws IOException { | 
|  | assert getBackend() == DEX; | 
|  | DexVm vm = runtime.asDex().getVm(); | 
|  | Path tmp = state.getNewTempFolder(); | 
|  | Path jarFile = tmp.resolve("out.jar"); | 
|  | Path oatFile = tmp.resolve("out.oat"); | 
|  | app.writeToZip(jarFile, OutputMode.DexIndexed); | 
|  | return new Dex2OatTestRunResult(app, runtime, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm)); | 
|  | } | 
|  |  | 
|  | public CR benchmarkCodeSize(BenchmarkResults results) throws IOException { | 
|  | Path out = writeToZip(); | 
|  | Box<Long> size = new Box<>(0L); | 
|  | ZipUtils.iter( | 
|  | out, | 
|  | (entry, stream) -> { | 
|  | if ((getBackend().isDex() && entry.getName().endsWith(".dex")) | 
|  | || getBackend().isCf() && entry.getName().endsWith(".class")) { | 
|  | size.set(size.get() + entry.getSize()); | 
|  | } | 
|  | }); | 
|  | results.addCodeSizeResult(size.get()); | 
|  | return self(); | 
|  | } | 
|  | } |