|  | // 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; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.fail; | 
|  |  | 
|  | import com.android.tools.r8.JctfTestSpecifications.Outcome; | 
|  | import com.android.tools.r8.TestCondition.Runtime; | 
|  | import com.android.tools.r8.TestCondition.RuntimeSet; | 
|  | import com.android.tools.r8.TestRuntime.CfVm; | 
|  | 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.DexVm.Kind; | 
|  | import com.android.tools.r8.ToolHelper.ProcessResult; | 
|  | import com.android.tools.r8.errors.CompilationError; | 
|  | 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.ArtErrorParser; | 
|  | import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo; | 
|  | import com.android.tools.r8.utils.FileUtils; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization; | 
|  | import com.android.tools.r8.utils.ListUtils; | 
|  | import com.android.tools.r8.utils.TestDescriptionWatcher; | 
|  | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
|  | import com.google.common.base.Charsets; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableListMultimap; | 
|  | import com.google.common.collect.ImmutableMap; | 
|  | import com.google.common.collect.Multimap; | 
|  | import com.google.common.collect.ObjectArrays; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.io.File; | 
|  | import java.io.FileNotFoundException; | 
|  | import java.io.FileOutputStream; | 
|  | import java.io.IOException; | 
|  | import java.nio.file.Files; | 
|  | import java.nio.file.Path; | 
|  | import java.nio.file.Paths; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Arrays; | 
|  | import java.util.Collection; | 
|  | import java.util.Collections; | 
|  | import java.util.HashMap; | 
|  | import java.util.HashSet; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Objects; | 
|  | import java.util.Set; | 
|  | import java.util.concurrent.ExecutionException; | 
|  | import java.util.function.BiFunction; | 
|  | import java.util.function.Consumer; | 
|  | import java.util.jar.JarEntry; | 
|  | import java.util.jar.JarOutputStream; | 
|  | import java.util.stream.Collectors; | 
|  | import org.junit.ComparisonFailure; | 
|  | import org.junit.Rule; | 
|  | import org.junit.rules.ExpectedException; | 
|  | import org.junit.rules.TemporaryFolder; | 
|  |  | 
|  | /** | 
|  | * This test class is not invoked directly. Instead, the gradle script generates one subclass per | 
|  | * actual art test. This allows us to run these in parallel. | 
|  | */ | 
|  | public abstract class R8RunArtTestsTest { | 
|  |  | 
|  | private static final boolean DEX_COMPARE_WITH_DEX_REFERENCE_ON_FAILURE = true; | 
|  |  | 
|  | private final String name; | 
|  | private final DexTool toolchain; | 
|  |  | 
|  | @Rule | 
|  | public ExpectedException thrown = ExpectedException.none(); | 
|  | private boolean expectedException = false; | 
|  |  | 
|  | public enum DexTool { | 
|  | DX, | 
|  | NONE // Working directly on .class files. | 
|  | } | 
|  |  | 
|  | public enum CompilerUnderTest { | 
|  | D8, | 
|  | R8, | 
|  | R8_AFTER_D8, // refers to the R8 (default: debug) step but implies a previous D8 step as well | 
|  | D8_AFTER_R8CF, | 
|  | R8CF | 
|  | } | 
|  |  | 
|  | public static final String ART_TESTS_DIR = "tests/2017-10-04/art"; | 
|  | private static final String ART_LEGACY_TESTS_DIR = "tests/2016-12-19/art/"; | 
|  | private static final String ART_TESTS_NATIVE_LIBRARY_DIR = "tests/2017-10-04/art/lib64"; | 
|  | private static final String ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR = "tests/2016-12-19/art/lib64"; | 
|  |  | 
|  | private static final RuntimeSet LEGACY_RUNTIME = TestCondition.runtimes( | 
|  | DexVm.Version.V4_0_4, | 
|  | DexVm.Version.V4_4_4, | 
|  | DexVm.Version.V5_1_1, | 
|  | DexVm.Version.V6_0_1, | 
|  | DexVm.Version.V7_0_0, | 
|  | DexVm.Version.V8_1_0, | 
|  | DexVm.Version.V9_0_0, | 
|  | DexVm.Version.V10_0_0); | 
|  |  | 
|  | // Input jar for jctf tests. | 
|  | private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar"; | 
|  |  | 
|  | // Parent dir for on-the-fly compiled jctf dex output. | 
|  | private static final String JCTF_TESTS_PREFIX = "build/classes/java/jctfTests"; | 
|  | private static final String JCTF_TESTS_LIB_PREFIX = | 
|  | JCTF_TESTS_PREFIX + "/com/google/jctf/test/lib"; | 
|  | private static final String JUNIT_TEST_RUNNER = "org.junit.runner.JUnitCore"; | 
|  | private static final String JUNIT_JAR = "third_party/junit/junit-4.13-beta-2.jar"; | 
|  | private static final String HAMCREST_JAR = | 
|  | "third_party/gradle/gradle/lib/plugins/hamcrest-core-1.3.jar"; | 
|  |  | 
|  | // Test that required to set min-api to a specific value. | 
|  | private static Map<String, AndroidApiLevel> needMinSdkVersion = | 
|  | new ImmutableMap.Builder<String, AndroidApiLevel>() | 
|  | .put("004-JniTest", AndroidApiLevel.N) | 
|  | // Has a non-abstract class with an abstract method. | 
|  | .put("800-smali", AndroidApiLevel.L) | 
|  | // Android O | 
|  | .put("952-invoke-custom", AndroidApiLevel.O) | 
|  | .put("952-invoke-custom-kinds", AndroidApiLevel.O) | 
|  | .put("953-invoke-polymorphic-compiler", AndroidApiLevel.O) | 
|  | .put("957-methodhandle-transforms", AndroidApiLevel.O) | 
|  | .put("958-methodhandle-stackframe", AndroidApiLevel.O) | 
|  | .put("959-invoke-polymorphic-accessors", AndroidApiLevel.O) | 
|  | .put("979-const-method-handle", AndroidApiLevel.P) | 
|  | .put("990-method-handle-and-mr", AndroidApiLevel.O) | 
|  | // Test intentionally asserts presence of bridge default methods desugar removes. | 
|  | .put("044-proxy", AndroidApiLevel.N) | 
|  | // Test intentionally asserts absence of default interface method in a class. | 
|  | .put("048-reflect-v8", AndroidApiLevel.N) | 
|  | // Uses default interface methods. | 
|  | .put("162-method-resolution", AndroidApiLevel.N) | 
|  | .put("616-cha-interface-default", AndroidApiLevel.N) | 
|  | .put("1910-transform-with-default", AndroidApiLevel.N) | 
|  | .put("960-default-smali", AndroidApiLevel.N) | 
|  | // Interface initializer is not triggered after desugaring. | 
|  | .put("962-iface-static", AndroidApiLevel.N) | 
|  | // Interface initializer is not triggered after desugaring. | 
|  | .put("964-default-iface-init-gen", AndroidApiLevel.N) | 
|  | .put("966-default-conflict", AndroidApiLevel.N) | 
|  | // AbstractMethodError (for method not implemented in class) instead of | 
|  | // IncompatibleClassChangeError (for conflict of default interface methods). | 
|  | .put("968-default-partial-compile-gen", AndroidApiLevel.N) | 
|  | // NoClassDefFoundError (for companion class) instead of NoSuchMethodError. | 
|  | .put("970-iface-super-resolution-gen", AndroidApiLevel.N) | 
|  | // NoClassDefFoundError (for companion class) instead of AbstractMethodError. | 
|  | .put("971-iface-super", AndroidApiLevel.N) | 
|  | // Test for miranda methods is not relevant for desugaring scenario. | 
|  | .put("972-default-imt-collision", AndroidApiLevel.N) | 
|  | // Uses default interface methods. | 
|  | .put("972-iface-super-multidex", AndroidApiLevel.N) | 
|  | // java.util.Objects is missing and test has default methods. | 
|  | .put("973-default-multidex", AndroidApiLevel.N) | 
|  | // a.klass.that.does.not.Exist is missing and test has default methods. | 
|  | .put("974-verify-interface-super", AndroidApiLevel.N) | 
|  | // Desugaring of interface private methods is not yet supported. | 
|  | .put("975-iface-private", AndroidApiLevel.N) | 
|  | .build(); | 
|  |  | 
|  | // Tests that timeout when run with Art. | 
|  | private static final Multimap<String, TestCondition> timeoutOrSkipRunWithArt = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | // Loops on art - timeout. | 
|  | .put( | 
|  | "109-suspend-check", | 
|  | TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1))) | 
|  | // Flaky loops on art. | 
|  | .put("129-ThreadGetId", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1))) | 
|  | // Takes ages to run on art 5.1.1 and behaves the same as on 6.0.1. Running this | 
|  | // tests on 5.1.1 makes our buildbot cycles time too long. | 
|  | .put("800-smali", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1))) | 
|  | // Hangs on dalvik. | 
|  | .put( | 
|  | "802-deoptimization", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // TODO(b/144975341): Triage | 
|  | .put( | 
|  | "134-reg-promotion", | 
|  | TestCondition.match(TestCondition.runtimes(DexVm.Version.V10_0_0))) | 
|  | .build(); | 
|  |  | 
|  | // Tests that are flaky with the Art version we currently use. | 
|  | // TODO(zerny): Amend flaky tests with an expected flaky result to track issues. | 
|  | private static Multimap<String, TestCondition> flakyRunWithArt = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | // Can crash but mostly passes | 
|  | // Crashes: | 
|  | // check_reference_map_visitor.h:44] At Main.f | 
|  | // Check failed: size() >= sizeof(T) (size()=0, sizeof(T)=1) | 
|  | // If art is passed -Xrelocate instead of -Xnorelocate the test passes. | 
|  | .put("004-ReferenceMap", TestCondition.any()) | 
|  | // Also marked flaky in the art repo, sometimes hangs, sometimes fails with a segfault: | 
|  | // line 105: 23283 Segmentation fault | 
|  | .put("004-ThreadStress", TestCondition.any()) | 
|  | // When it fails: | 
|  | // stack_walk_jni.cc:57] Check failed: 0xdU == GetDexPc() (0xdU=13, GetDexPc()=23) | 
|  | // The R8/D8 code does not produce code with the same instructions. | 
|  | .put("004-StackWalk", TestCondition.any()) | 
|  | // Nothing ensures the weak-ref is not cleared between makeRef and | 
|  | // printWeakReference on lines Main.java:78-79. An expected flaky | 
|  | // result contains: "but was:<wimp: [null]". | 
|  | .put("036-finalizer", TestCondition.any()) | 
|  | // The test waits for a maximum of 500 ms which is unreliable when running on buildbots: | 
|  | // Elapsed time was too long: elapsed=552 max=550 | 
|  | .put("053-wait-some", TestCondition.any()) | 
|  | // Failed on buildbot with: terminate called after throwing an instance | 
|  | // of '__gnu_cxx::recursive_init_error' | 
|  | .put("096-array-copy-concurrent-gc", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Failing with: expected "second > first =[tru]e" was "second > first =[fals]e" | 
|  | .put("098-ddmc", TestCondition.match(TestCondition.runtimes(DexVm.Version.V6_0_1))) | 
|  | // Sometimes fails with out of memory on Dalvik. | 
|  | .put("114-ParallelGC", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Seen crash: currently no more information | 
|  | .put("144-static-field-sigquit", TestCondition.any()) | 
|  | // Opens a lot of file descriptors and depending on the state of the machine this | 
|  | // can crash art or not. Skip the run on art. | 
|  | .put("151-OpenFileLimit", TestCondition.any()) | 
|  | // Can cause a segfault in the art vm 7.0.0 | 
|  | // tools/linux/art-7.0.0/bin/art: line 105: 14395 Segmentation fault | 
|  | .put("607-daemon-stress", TestCondition.any()) | 
|  | // Marked as flaky in the Art repository. | 
|  | .put("149-suspend-all-stress", TestCondition.any()) | 
|  | .build(); | 
|  |  | 
|  | // Tests that are never compiled or run. | 
|  | private static List<String> skipAltogether = ImmutableList.of( | 
|  | // Those tests contains an invalid type hierarchy, which we cannot currently handle. | 
|  | "065-mismatched-implements", | 
|  | "066-mismatched-super", | 
|  | // This test contains invalid dex code that reads uninitialized registers after an | 
|  | // an instruction that would in any case throw (implicit via aget null 0). | 
|  | "706-jit-skip-compilation", | 
|  | // This test uses a user defined class loader to validate correct execution order | 
|  | // between loadclass event and the execution of a static method. | 
|  | // This does not work when performing inlining across classes. | 
|  | "496-checker-inlining-class-loader", | 
|  | // These all test OOM behavior and segfault doing GC on some machines. We just ignore them. | 
|  | "080-oom-throw", | 
|  | "080-oom-fragmentation", | 
|  | "159-app-image-fields", | 
|  | "163-app-image-methods", | 
|  | "061-out-of-memory", | 
|  | "617-clinit-oome" | 
|  | ); | 
|  |  | 
|  | // Tests that may produce different output on consecutive runs or when processed or not. | 
|  | private static List<String> outputMayDiffer = ImmutableList.of( | 
|  | // One some versions of art, this will print the address of the boot classloader, which | 
|  | // may change between runs. | 
|  | "506-verify-aput", | 
|  | // Art does fail during validation of dex files if it encounters an ill-typed virtual-invoke | 
|  | // but allows interface-invokes to pass. As we rewrite one kind into the other, we remove | 
|  | // a verification error and thus change output. | 
|  | "135-MirandaDispatch", | 
|  | // We resolve a conflicting definition of default methods, thus removing an ICCE. | 
|  | "972-iface-super-multidex" | 
|  | ); | 
|  |  | 
|  | // Tests that make use of agents/native code. | 
|  | // Our test setup does not handle flags/linking of these. | 
|  | private static List<String> usesNativeAgentCode = ImmutableList.of( | 
|  | "497-inlining-and-class-loader", | 
|  | "626-const-class-linking", | 
|  | "642-fp-callees", | 
|  | "660-clinit", | 
|  | "909-attach-agent", | 
|  | "914-hello-obsolescence", | 
|  | "915-obsolete-2", | 
|  | "916-obsolete-jit", | 
|  | "917-fields-transformation", | 
|  | "918-fields", | 
|  | "919-obsolete-fields", | 
|  | "920-objects", | 
|  | "921-hello-failure", | 
|  | "922-properties", | 
|  | "923-monitors", | 
|  | "924-threads", | 
|  | "925-threadgroups", | 
|  | "926-multi-obsolescence", | 
|  | "927-timers", | 
|  | "928-jni-table", | 
|  | "929-search", | 
|  | "930-hello-retransform", | 
|  | "931-agent-thread", | 
|  | "932-transform-saves", | 
|  | "933-misc-events", | 
|  | "934-load-transform", | 
|  | "935-non-retransformable", | 
|  | "936-search-onload", | 
|  | "937-hello-retransform-package", | 
|  | "938-load-transform-bcp", | 
|  | "939-hello-transformation-bcp", | 
|  | "940-recursive-obsolete", | 
|  | "941-recurive-obsolete-jit", | 
|  | "942-private-recursive", | 
|  | "943-private-recursive-jit", | 
|  | "944-transform-classloaders", | 
|  | "945-obsolete-native", | 
|  | "946-obsolete-throw", | 
|  | "947-reflect-method", | 
|  | "948-change-annotations", | 
|  | "949-in-memory-transform", | 
|  | "950-redefine-intrinsic", | 
|  | "951-threaded-obsolete", | 
|  | "980-redefine-object", | 
|  | "1900-track-alloc", | 
|  | "1901-get-bytecodes", | 
|  | "1902-suspend", | 
|  | "1903-suspend-self", | 
|  | "1904-double-suspend", | 
|  | "1906-suspend-list-me-first", | 
|  | "1907-suspend-list-self-twice", | 
|  | "1909-per-agent-tls", | 
|  | "1910-transform-with-default", | 
|  | "1911-get-local-var-table", | 
|  | "1912-get-set-local-primitive", | 
|  | "1913-get-set-local-objects", | 
|  | "1914-get-local-instance", | 
|  | "1915-get-set-local-current-thread", | 
|  | "1916-get-set-current-frame", | 
|  | "1917-get-stack-frame", | 
|  | "1919-vminit-thread-start-timing", | 
|  | "1920-suspend-native-monitor", | 
|  | "1921-suspend-native-recursive-monitor", | 
|  | "1922-owned-monitors-info", | 
|  | "1923-frame-pop", | 
|  | "1924-frame-pop-toggle", | 
|  | "1925-self-frame-pop", | 
|  | "1926-missed-frame-pop", | 
|  | "1927-exception-event", | 
|  | "1928-exception-event-exception", | 
|  | "1929-exception-catch-exception", | 
|  | "1930-monitor-info", | 
|  | "1931-monitor-events", | 
|  | "1932-monitor-events-misc", | 
|  | "1933-monitor-current-contended", | 
|  | "1934-jvmti-signal-thread", | 
|  | "1935-get-set-current-frame-jit", | 
|  | "1936-thread-end-events", | 
|  | // These tests need a library name as parameter | 
|  | "164-resolution-trampoline-dex-cache", | 
|  | "597-deopt-invoke-stub", | 
|  | "597-deopt-busy-loop", | 
|  | "661-oat-writer-layout", | 
|  | "661-classloader-allocator", | 
|  | "664-aget-verifier" | 
|  | ); | 
|  |  | 
|  | // Tests with custom run. | 
|  | private static List<String> customRun = ImmutableList.of( | 
|  | "000-nop", | 
|  | "018-stack-overflow", | 
|  | "055-enum-performance", | 
|  | "071-dexfile-map-clean", | 
|  | "091-override-package-private-method", | 
|  | "103-string-append", | 
|  | "115-native-bridge", | 
|  | "116-nodex2oat", | 
|  | "117-nopatchoat", | 
|  | "118-noimage-dex2oat", | 
|  | "119-noimage-patchoat", | 
|  | "126-miranda-multidex", | 
|  | "127-checker-secondarydex", | 
|  | "131-structural-change", | 
|  | "133-static-invoke-super", | 
|  | "134-nodex2oat-nofallback", | 
|  | "137-cfi", | 
|  | "138-duplicate-classes-check2", | 
|  | "146-bad-interface", | 
|  | "147-stripped-dex-fallback", | 
|  | "304-method-tracing", | 
|  | "529-checker-unresolved", | 
|  | "555-checker-regression-x86const", | 
|  | "569-checker-pattern-replacement", | 
|  | "570-checker-osr", | 
|  | "574-irreducible-and-constant-area", | 
|  | "577-profile-foreign-dex", | 
|  | "595-profile-saving", | 
|  | "597-deopt-new-string", | 
|  | "608-checker-unresolved-lse", | 
|  | "613-inlining-dex-cache", | 
|  | "636-wrong-static-access", | 
|  | "900-hello-plugin", | 
|  | "901-hello-ti-agent", | 
|  | "902-hello-transformation", | 
|  | "910-methods", | 
|  | "911-get-stack-trace", | 
|  | "912-classes", | 
|  | "913-heaps" | 
|  | ); | 
|  |  | 
|  | // Tests with C++ code (JNI) | 
|  | private static List<String> useJNI = ImmutableList.of( | 
|  | "004-JniTest", | 
|  | "004-NativeAllocations", | 
|  | "004-ReferenceMap", | 
|  | "004-SignalTest", | 
|  | "004-StackWalk", | 
|  | "004-ThreadStress", | 
|  | "004-UnsafeTest", | 
|  | "044-proxy", | 
|  | "051-thread", | 
|  | "115-native-bridge", | 
|  | "117-nopatchoat", | 
|  | "136-daemon-jni-shutdown", | 
|  | "137-cfi", | 
|  | "139-register-natives", | 
|  | "141-class-unload", | 
|  | "148-multithread-gc-annotations", | 
|  | "149-suspend-all-stress", | 
|  | "154-gc-loop", | 
|  | "155-java-set-resolved-type", | 
|  | "157-void-class", | 
|  | "158-app-image-class-table", | 
|  | "454-get-vreg", | 
|  | "457-regs", | 
|  | "461-get-reference-vreg", | 
|  | "466-get-live-vreg", | 
|  | "497-inlining-and-class-loader", | 
|  | "543-env-long-ref", | 
|  | "566-polymorphic-inlining", | 
|  | "570-checker-osr", | 
|  | "595-profile-saving", | 
|  | "596-app-images", | 
|  | "597-deopt-new-string", | 
|  | "616-cha", | 
|  | "616-cha-abstract", | 
|  | "616-cha-interface", | 
|  | "616-cha-interface-default", | 
|  | "616-cha-miranda", | 
|  | "616-cha-regression-proxy-method", | 
|  | "616-cha-native", | 
|  | "616-cha-proxy-method-inline", | 
|  | "626-const-class-linking", | 
|  | "626-set-resolved-string", | 
|  | "900-hello-plugin", | 
|  | "901-hello-ti-agent", | 
|  | "1337-gc-coverage" | 
|  | ); | 
|  |  | 
|  | private static List<String> expectedToFailRunWithArtNonDefault = ImmutableList.of( | 
|  | // Fails due to missing symbol, jni tests, fails on non-R8/D8 run. | 
|  | "004-JniTest", | 
|  | "004-SignalTest", | 
|  | "004-ThreadStress", | 
|  | "004-UnsafeTest", | 
|  | "044-proxy", | 
|  | "051-thread", | 
|  | "136-daemon-jni-shutdown", | 
|  | "139-register-natives", | 
|  | "148-multithread-gc-annotations", | 
|  | "149-suspend-all-stress", | 
|  | "154-gc-loop", | 
|  | "155-java-set-resolved-type", | 
|  | "157-void-class", | 
|  | "158-app-image-class-table", | 
|  | "466-get-live-vreg", | 
|  | "497-inlining-and-class-loader", | 
|  | "566-polymorphic-inlining", | 
|  | "596-app-images", | 
|  | "616-cha", | 
|  | "616-cha-abstract", | 
|  | "616-cha-regression-proxy-method", | 
|  | "616-cha-native", | 
|  | "626-set-resolved-string", | 
|  | "629-vdex-speed", | 
|  | "1337-gc-coverage", | 
|  |  | 
|  | // Fails on non-R8/D8 run | 
|  | "031-class-attributes" | 
|  | ); | 
|  |  | 
|  | private static Map<DexVm.Version, List<String>> expectedToFailRunWithArtVersion; | 
|  |  | 
|  | static { | 
|  | ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder(); | 
|  | builder | 
|  | .put(DexVm.Version.V10_0_0, ImmutableList.of( | 
|  | // TODO(b/144975341): Triage, Verif error. | 
|  | "518-null-array-get", | 
|  | // TODO(b/144975341): Triage, Linking error. | 
|  | "457-regs", | 
|  | "543-env-long-ref", | 
|  | "454-get-vreg" | 
|  | )) | 
|  | .put(DexVm.Version.V9_0_0, ImmutableList.of( | 
|  | // TODO(120400625): Triage. | 
|  | "454-get-vreg", | 
|  | // TODO(120402198): Triage. | 
|  | "457-regs", | 
|  | // TODO(120401674): Triage. | 
|  | "543-env-long-ref", | 
|  | // TODO(120261858) Triage. | 
|  | "518-null-array-get" | 
|  | )) | 
|  | .put(DexVm.Version.V8_1_0, ImmutableList.of( | 
|  | // TODO(119938529): Triage. | 
|  | "709-checker-varhandles", | 
|  | "454-get-vreg", | 
|  | "457-regs" | 
|  | )) | 
|  | .put(DexVm.Version.V7_0_0, ImmutableList.of( | 
|  | // Addition of checks for super-class-initialization cause this to abort on non-ToT art. | 
|  | "008-exceptions", | 
|  |  | 
|  | // Generally fails on non-R8/D8 running. | 
|  | "156-register-dex-file-multi-loader", | 
|  | "412-new-array", | 
|  | "610-arraycopy", | 
|  | "625-checker-licm-regressions")) | 
|  | .put(DexVm.Version.V6_0_1, ImmutableList.of( | 
|  | // Addition of checks for super-class-initialization cause this to abort on non-ToT art. | 
|  | "008-exceptions", | 
|  |  | 
|  | // Generally fails on non-R8/D8 running. | 
|  | "004-checker-UnsafeTest18", | 
|  | "005-annotations", | 
|  | "008-exceptions", | 
|  | "082-inline-execute", | 
|  | "099-vmdebug", | 
|  | "156-register-dex-file-multi-loader", | 
|  | "412-new-array", | 
|  | "580-checker-round", | 
|  | "594-invoke-super", | 
|  | "625-checker-licm-regressions", | 
|  | "626-const-class-linking")) | 
|  | .put(DexVm.Version.V5_1_1, ImmutableList.of( | 
|  | // Addition of checks for super-class-initialization cause this to abort on non-ToT art. | 
|  | "008-exceptions", | 
|  |  | 
|  | // Generally fails on non R8/D8 running. | 
|  | "004-checker-UnsafeTest18", | 
|  | "004-NativeAllocations", | 
|  | "005-annotations", | 
|  | "008-exceptions", | 
|  | "082-inline-execute", | 
|  | "099-vmdebug", | 
|  | "143-string-value", | 
|  | "156-register-dex-file-multi-loader", | 
|  | "536-checker-intrinsic-optimization", | 
|  | "552-invoke-non-existent-super", | 
|  | "580-checker-round", | 
|  | "580-checker-string-fact-intrinsics", | 
|  | "594-invoke-super", | 
|  | "605-new-string-from-bytes", | 
|  | "626-const-class-linking")) | 
|  | .put(DexVm.Version.V4_4_4, ImmutableList.of( | 
|  | // Addition of checks for super-class-initialization cause this to abort on non-ToT art. | 
|  | "008-exceptions", | 
|  |  | 
|  | // Generally fails on non R8/D8 running. | 
|  | "004-checker-UnsafeTest18", | 
|  | "004-NativeAllocations", | 
|  | "005-annotations", | 
|  | "008-exceptions", | 
|  | "082-inline-execute", | 
|  | "099-vmdebug", | 
|  | "143-string-value", | 
|  | "156-register-dex-file-multi-loader", | 
|  | "536-checker-intrinsic-optimization", | 
|  | "552-invoke-non-existent-super", | 
|  | "580-checker-round", | 
|  | "580-checker-string-fact-intrinsics", | 
|  | "594-invoke-super", | 
|  | "605-new-string-from-bytes", | 
|  | "626-const-class-linking")) | 
|  | .put(DexVm.Version.V4_0_4, ImmutableList.of( | 
|  | // Addition of checks for super-class-initialization cause this to abort on non-ToT art. | 
|  | "008-exceptions", | 
|  |  | 
|  | // Generally fails on non R8/D8 running. | 
|  | "004-checker-UnsafeTest18", | 
|  | "004-NativeAllocations", | 
|  | "005-annotations", | 
|  | "008-exceptions", | 
|  | "082-inline-execute", | 
|  | "099-vmdebug", | 
|  | "143-string-value", | 
|  | "156-register-dex-file-multi-loader", | 
|  | "536-checker-intrinsic-optimization", | 
|  | "552-invoke-non-existent-super", | 
|  | "580-checker-round", | 
|  | "580-checker-string-fact-intrinsics", | 
|  | "594-invoke-super", | 
|  | "605-new-string-from-bytes", | 
|  | "626-const-class-linking")); | 
|  | expectedToFailRunWithArtVersion = builder.build(); | 
|  | } | 
|  |  | 
|  | // Tests where the R8/D8 output runs in Art but the original does not. | 
|  | private static Multimap<String, TestCondition> failingRunWithArtOriginalOnly = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | .put("095-switch-MAX_INT", | 
|  | TestCondition.match( | 
|  | TestCondition.runtimes(DexVm.Version.V4_0_4))) | 
|  | .build(); | 
|  |  | 
|  | // Tests where the output of R8 fails when run with Art. | 
|  | private static final Multimap<String, TestCondition> failingRunWithArt = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | // The growth limit test fails after processing by R8 because R8 will eliminate an | 
|  | // "unneeded" const store. The following reflective call to the VM's GC will then see the | 
|  | // large array as still live and the subsequent allocations will fail to reach the desired | 
|  | // size before an out-of-memory error occurs. See: | 
|  | // tests/art/dx/104-growth-limit/src/Main.java:40 | 
|  | .put( | 
|  | "104-growth-limit", | 
|  | TestCondition.match(TestCondition.R8_COMPILER, TestCondition.RELEASE_MODE)) | 
|  | .put( | 
|  | "461-get-reference-vreg", | 
|  | TestCondition.match( | 
|  | TestCondition.D8_COMPILER, | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1))) | 
|  | // Dalvik fails on reading an uninitialized local. | 
|  | .put( | 
|  | "471-uninitialized-locals", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Out of memory. | 
|  | .put( | 
|  | "152-dead-large-object", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Cannot resolve exception handler. Interestingly, D8 generates different code in | 
|  | // release mode (which is also the code generated by R8) which passes. | 
|  | .put( | 
|  | "111-unresolvable-exception", | 
|  | TestCondition.match( | 
|  | TestCondition.D8_COMPILER, TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Fails because the code has to be desugared to run on art <= 6.0.1 | 
|  | // When running from dx code we don't desugar. | 
|  | .put( | 
|  | "530-checker-lse2", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX), | 
|  | TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.D8), | 
|  | TestCondition.runtimesUpTo(DexVm.Version.V6_0_1))) | 
|  | .put( | 
|  | "534-checker-bce-deoptimization", | 
|  | TestCondition.match( | 
|  | TestCondition.D8_COMPILER, TestCondition.runtimes(DexVm.Version.V6_0_1))) | 
|  | // Type not present. | 
|  | .put( | 
|  | "124-missing-classes", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Failed creating vtable. | 
|  | .put( | 
|  | "587-inline-class-error", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Failed creating vtable. | 
|  | .put( | 
|  | "595-error-class", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // NoSuchFieldException: systemThreadGroup on Art 4.4.4. | 
|  | .put( | 
|  | "129-ThreadGetId", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Verifier says: can't modify final field LMain;.staticFinalField. | 
|  | .put( | 
|  | "600-verifier-fails", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // VFY: tried to get class from non-ref register. | 
|  | .put( | 
|  | "506-verify-aput", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // NoSuchMethod: startMethodTracing. | 
|  | .put( | 
|  | "545-tracing-and-jit", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // filled-new-array arg 0(1) not valid. | 
|  | .put( | 
|  | "412-new-array", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // TODO(ager): unclear what is failing here. | 
|  | .put("098-ddmc", TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Unsatisfiable link error: | 
|  | // libarttest.so: undefined symbol: _ZN3art6Thread18RunEmptyCheckpointEv | 
|  | .put( | 
|  | "543-env-long-ref", | 
|  | TestCondition.match( | 
|  | TestCondition.D8_COMPILER, | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V8_1_0, | 
|  | DexVm.Version.V7_0_0, | 
|  | DexVm.Version.V6_0_1, | 
|  | DexVm.Version.V5_1_1))) | 
|  | // lib64 libarttest.so: wrong ELF class ELFCLASS64. | 
|  | .put( | 
|  | "543-env-long-ref", | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // Leaving two static-get triggers LSE bug on 6.0.1 (b/25735083). | 
|  | // R8, with subtyping, knows the first sget is dead, and removing it avoids the bug. | 
|  | // Due to the lack of subtype hierarchy, D8 can't guarantee <clinit> side effects. | 
|  | .put( | 
|  | "550-new-instance-clinit", | 
|  | TestCondition.match( | 
|  | TestCondition.D8_COMPILER, TestCondition.runtimes(DexVm.Version.V6_0_1))) | 
|  | // Regression test for an issue that is not fixed on version 5.1.1. Throws an Exception | 
|  | // instance instead of the expected NullPointerException. This bug is only tickled when | 
|  | // running the R8 generated code when starting from jar or from dex code generated with | 
|  | // dx. However, the code that R8 generates is valid and there is nothing we can do for | 
|  | // this one. | 
|  | .put( | 
|  | "551-implicit-null-checks", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | TestCondition.R8DEX_COMPILER, | 
|  | TestCondition.runtimes(DexVm.Version.V5_1_1))) | 
|  | // Contains a method (B.<init>) which pass too few arguments to invoke. Also, contains an | 
|  | // iput on a static field. | 
|  | .put( | 
|  | "600-verifier-fails", | 
|  | TestCondition.match( | 
|  | TestCondition.D8_COMPILER, | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1))) | 
|  | // Dalvik 4.0.4 is missing ReflectiveOperationException class. | 
|  | .put( | 
|  | "140-field-packing", | 
|  | TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4))) | 
|  | // Dalvik 4.0.4 is missing theUnsafe field. | 
|  | .put("528-long-hint", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4))) | 
|  | // Cannot catch exception in Dalvik 4.0.4. | 
|  | .put("084-class-init", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4))) | 
|  | // Tested regression still exists in Dalvik 4.0.4. | 
|  | .put( | 
|  | "301-abstract-protected", | 
|  | TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4))) | 
|  | // Illegal class flags in Dalvik 4.0.4. | 
|  | .put("121-modifiers", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4))) | 
|  | // Switch regression still present in Dalvik 4.0.4. | 
|  | .put( | 
|  | "095-switch-MAX_INT", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX), | 
|  | TestCondition.D8_COMPILER, | 
|  | TestCondition.runtimes(DexVm.Version.V4_0_4))) | 
|  | .build(); | 
|  |  | 
|  | // Tests where the output of R8/D8 runs in Art but produces different output than the expected.txt | 
|  | // checked into the Art repo. | 
|  | private static final Multimap<String, TestCondition> failingRunWithArtOutput = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | // This test assumes that class-retention annotations are preserved by the compiler and | 
|  | // then checks for backwards compatibility with M where they could incorrectly be observed | 
|  | // by the program at runtime. | 
|  | .put("005-annotations", TestCondition.match(TestCondition.D8_COMPILER)) | 
|  | // On Art 4.4.4 we have fewer refs than expected (except for d8 when compiled with dx). | 
|  | .put("072-precise-gc", | 
|  | TestCondition.match( | 
|  | TestCondition.R8_COMPILER, | 
|  | TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | .put("072-precise-gc", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE), | 
|  | TestCondition.D8_COMPILER, | 
|  | TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) | 
|  | // This one is expected to have different output. It counts instances, but the list that | 
|  | // keeps the instances alive is dead and could be garbage collected. The compiler reuses | 
|  | // the register for the list and therefore there are no live instances. | 
|  | .put("099-vmdebug", TestCondition.any()) | 
|  | // This test relies on output on stderr, which we currently do not collect. | 
|  | .put("143-string-value", TestCondition.any()) | 
|  | .put("800-smali", | 
|  | TestCondition.match( | 
|  | TestCondition.D8_COMPILER, | 
|  | TestCondition.runtimes(DexVm.Version.V5_1_1, DexVm.Version.V6_0_1))) | 
|  | // Triggers regression test in 6.0.1 when using R8/D8 in debug mode. | 
|  | .put("474-fp-sub-neg", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE), | 
|  | TestCondition.D8_NOT_AFTER_R8CF_COMPILER, | 
|  | TestCondition.runtimes(DexVm.Version.V6_0_1))) | 
|  | .build(); | 
|  |  | 
|  | private static final TestCondition beforeAndroidN = | 
|  | TestCondition | 
|  | .match(TestCondition | 
|  | .runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, | 
|  | DexVm.Version.V6_0_1)); | 
|  | // TODO(herhut): Change to V8_0_0 once we have a new art VM. | 
|  | private static final TestCondition beforeAndroidO = | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V7_0_0)); | 
|  | // TODO(herhut): Change to V8_0_0 once we have a new art VM. | 
|  | private static final TestCondition beforeAndroidP = | 
|  | TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V7_0_0)); | 
|  |  | 
|  | // TODO(ager): Could we test that these fail in the way that we expect? | 
|  | private static final Multimap<String, TestCondition> expectedToFailRunWithArt = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | // Contains bad finalizer that times out. | 
|  | .put("030-bad-finalizer", TestCondition.any()) | 
|  | // Contains a direct method call with a null receiver. | 
|  | .put("034-call-null", TestCondition.any()) | 
|  | // Testing uncaught exceptions. | 
|  | .put("054-uncaught", TestCondition.any()) | 
|  | // Contains a null pointer exception. | 
|  | .put("038-inner-null", TestCondition.any()) | 
|  | // Null pointer exception. | 
|  | .put("071-dexfile", TestCondition.any()) | 
|  | // Array index out of bounds exception. | 
|  | .put("088-monitor-verification", TestCondition.any()) | 
|  | // Attempts to run hprof. | 
|  | .put("130-hprof", TestCondition.any()) | 
|  | // Null pointer exception. Test doesn't exist for dx. | 
|  | .put("138-duplicate-classes-check", TestCondition.any()) | 
|  | // Array index out of bounds exception. | 
|  | .put("150-loadlibrary", TestCondition.any()) | 
|  | // Fails due to non-matching Exception messages. | 
|  | .put( | 
|  | "201-built-in-except-detail-messages", | 
|  | TestCondition.or( | 
|  | TestCondition.match( | 
|  | TestCondition.compilers( | 
|  | CompilerUnderTest.D8, CompilerUnderTest.D8_AFTER_R8CF), | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V4_0_4, | 
|  | DexVm.Version.V4_4_4, | 
|  | DexVm.Version.V5_1_1, | 
|  | DexVm.Version.V6_0_1, | 
|  | DexVm.Version.V7_0_0)), | 
|  | TestCondition.match( | 
|  | TestCondition.compilers( | 
|  | CompilerUnderTest.R8, | 
|  | CompilerUnderTest.R8CF, | 
|  | CompilerUnderTest.R8_AFTER_D8)))) | 
|  | // Uses dex file version 37 and therefore only runs on Android N and above. | 
|  | .put( | 
|  | "370-dex-v37", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX), | 
|  | TestCondition.compilers(CompilerUnderTest.D8), | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V4_0_4, | 
|  | DexVm.Version.V4_4_4, | 
|  | DexVm.Version.V5_1_1, | 
|  | DexVm.Version.V6_0_1))) | 
|  | // Array index out of bounds exception. | 
|  | .put("449-checker-bce", TestCondition.any()) | 
|  | // Fails: get_vreg_jni.cc:46] Check failed: value == 42u (value=314630384, 42u=42) | 
|  | // The R8/D8 code does not produce values in the same registers as the tests expects in | 
|  | // Main.testPairVReg where Main.doNativeCall is called (v1 vs v0). | 
|  | .put( | 
|  | "454-get-vreg", | 
|  | TestCondition.match( | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V4_0_4, | 
|  | DexVm.Version.V4_4_4, | 
|  | DexVm.Version.V5_1_1, | 
|  | DexVm.Version.V6_0_1, | 
|  | DexVm.Version.V7_0_0))) | 
|  | .put("454-get-vreg", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Fails: regs_jni.cc:42] Check failed: GetVReg(m, 0, kIntVReg, &value) | 
|  | // The R8/D8 code does not put values in the same registers as the tests expects. | 
|  | .put( | 
|  | "457-regs", | 
|  | TestCondition.match( | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V4_0_4, | 
|  | DexVm.Version.V4_4_4, | 
|  | DexVm.Version.V5_1_1, | 
|  | DexVm.Version.V6_0_1, | 
|  | DexVm.Version.V7_0_0))) | 
|  | .put("457-regs", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Class not found. | 
|  | .put("529-checker-unresolved", TestCondition.any()) | 
|  | // Fails: env_long_ref.cc:44] Check failed: GetVReg(m, 1, kReferenceVReg, &value) | 
|  | // The R8/D8 code does not produce values in the same registers as the tests expects in | 
|  | // the stack frame for TestCase.testCase checked by the native Main.lookForMyRegisters | 
|  | // (v1 vs v0). | 
|  | .put("543-env-long-ref", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Array index out of bounds exception. | 
|  | .put("555-UnsafeGetLong-regression", TestCondition.any()) | 
|  | // Array index out of bounds exception. | 
|  | .put("563-checker-fakestring", TestCondition.any()) | 
|  | // Array index out of bounds exception. | 
|  | .put("575-checker-string-init-alias", TestCondition.any()) | 
|  | // Runtime exception. | 
|  | .put("577-profile-foreign-dex", TestCondition.any()) | 
|  | // Array index out of bounds exception. | 
|  | .put("602-deoptimizeable", TestCondition.any()) | 
|  | // Array index out of bounds exception. | 
|  | .put("604-hot-static-interface", TestCondition.any()) | 
|  | // Array index out of bounds exception. | 
|  | .put("612-jit-dex-cache", TestCondition.any()) | 
|  | // Fails: get_reference_vreg_jni.cc:43] Check failed: GetVReg(m, 1, kReferenceVReg, | 
|  | // &value) | 
|  | // The R8 code does not put values in the same registers as the tests expects in | 
|  | // Main.testThisWithInstanceCall checked by the native Main.doNativeCallRef (v0 vs. v1 and | 
|  | // only 1 register instead fof 2). | 
|  | .put("461-get-reference-vreg", TestCondition.match(TestCondition.R8_COMPILER)) | 
|  | // This test uses register r1 in method that is declared to only use 1 register (r0). | 
|  | // This is in dex code which D8 does not convert and which R8/CF does not process. | 
|  | // Therefore the error is a verification error at runtime and that is expected. | 
|  | .put("142-classloader2", TestCondition.match(TestCondition.D8_COMPILER)) | 
|  | // Invoke-custom is supported by D8 and R8, but it can only run on our newest version | 
|  | // of art. | 
|  | .put("952-invoke-custom", beforeAndroidO) | 
|  | .put("952-invoke-custom-kinds", beforeAndroidO) | 
|  | // Invoke-polymorphic is supported by D8 and R8, but it can only run on our newest version | 
|  | // of art. | 
|  | .put("953-invoke-polymorphic-compiler", beforeAndroidO) | 
|  | .put("957-methodhandle-transforms", beforeAndroidO) | 
|  | .put("958-methodhandle-stackframe", beforeAndroidO) | 
|  | .put("959-invoke-polymorphic-accessors", beforeAndroidO) | 
|  | .put("044-proxy", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("048-reflect-v8", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("962-iface-static", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("964-default-iface-init-gen", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("968-default-partial-compile-gen", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("970-iface-super-resolution-gen", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("971-iface-super", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("972-default-imt-collision", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("973-default-multidex", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("974-verify-interface-super", beforeAndroidN) // --min-sdk = 24 | 
|  | .put("975-iface-private", beforeAndroidN) // --min-sdk = 24 | 
|  | // Uses dex file version 37 and therefore only runs on Android N and above. | 
|  | .put( | 
|  | "972-iface-super-multidex", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX), | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V4_0_4, | 
|  | DexVm.Version.V4_4_4, | 
|  | DexVm.Version.V5_1_1, | 
|  | DexVm.Version.V6_0_1))) | 
|  | // Uses dex file version 37 and therefore only runs on Android N and above. | 
|  | .put( | 
|  | "978-virtual-interface", | 
|  | TestCondition.or( | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX), | 
|  | TestCondition.compilers(CompilerUnderTest.D8), | 
|  | TestCondition.runtimes( | 
|  | DexVm.Version.V4_0_4, | 
|  | DexVm.Version.V4_4_4, | 
|  | DexVm.Version.V5_1_1, | 
|  | DexVm.Version.V6_0_1)), | 
|  | // On V4_0_4 and V4_4_4 the test will throw a verification error. | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX), | 
|  | TestCondition.compilers( | 
|  | CompilerUnderTest.R8, | 
|  | CompilerUnderTest.R8_AFTER_D8, | 
|  | CompilerUnderTest.D8_AFTER_R8CF), | 
|  | TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4)))) | 
|  | .put("979-const-method-handle", beforeAndroidP) | 
|  | // Missing class junit.framework.Assert (see JunitAvailabilityInHostArtTest). | 
|  | // TODO(120884788): Add this again. | 
|  | /* | 
|  | .put( | 
|  | "021-string2", | 
|  | TestCondition.or( | 
|  | TestCondition.match( | 
|  | TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF), | 
|  | TestCondition.runtimesFrom(DexVm.Version.V7_0_0)), | 
|  | TestCondition.match( | 
|  | TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF), | 
|  | TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4)))) | 
|  | */ | 
|  | // Missing class junit.framework.Assert (see JunitAvailabilityInHostArtTest). | 
|  | // TODO(120884788): Add this again. | 
|  | /* | 
|  | .put( | 
|  | "082-inline-execute", | 
|  | TestCondition.or( | 
|  | TestCondition.match( | 
|  | TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF), | 
|  | TestCondition.runtimesFrom(DexVm.Version.V7_0_0)), | 
|  | TestCondition.match( | 
|  | TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF), | 
|  | TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4)))) | 
|  | */ | 
|  | .build(); | 
|  |  | 
|  | // Tests where code generation fails. | 
|  | private static final Multimap<String, TestCondition> failingWithCompiler = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | // Contains two methods with the same name and signature but different code. | 
|  | .put("097-duplicate-method", TestCondition.any()) | 
|  | // Dex code contains a method (B.<init>) which pass too few arguments to invoke, and it | 
|  | // also contains an iput on a static field. | 
|  | .put("600-verifier-fails", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Contains a method that falls off the end without a return. | 
|  | .put( | 
|  | "606-erroneous-class", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX), | 
|  | TestCondition.R8_NOT_AFTER_D8_COMPILER, | 
|  | LEGACY_RUNTIME)) | 
|  | // Dex input contains an illegal InvokeSuper in Z.foo() to Y.foo() | 
|  | // that R8 will fail to compile. | 
|  | .put("974-verify-interface-super", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // R8 generates too large code in Goto.bigGoto(). b/74327727 | 
|  | .put("003-omnibus-opcodes", TestCondition.match(TestCondition.D8_AFTER_R8CF_COMPILER)) | 
|  | .build(); | 
|  |  | 
|  | // Tests that are invalid dex files and on which R8/D8 fails and that is OK. | 
|  | private static final Multimap<String, TestCondition> expectedToFailWithCompiler = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | // When starting from the Jar frontend we see the A$B class both from the Java source | 
|  | // code and from the smali dex code. We reject that because there are then two definitions | 
|  | // of the same class in the application. When running from the final dex files there is | 
|  | // only one A$B class because of a custom build script that merges them. | 
|  | .put("121-modifiers", TestCondition.match(TestCondition.tools(DexTool.NONE))) | 
|  | // This test uses register r1 in method that is declared to only use 1 register (r0). | 
|  | .put("142-classloader2", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test with invalid register usage: invoke-static {v2,v2,v2} f(LIF)V | 
|  | .put("457-regs", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // This test uses an uninitialized register. | 
|  | .put("471-uninitialized-locals", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test which mixes int and float registers. | 
|  | .put("459-dead-phi", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test for verification error: contains an aput-object with an single-valued input. | 
|  | .put("506-verify-aput", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test with invalid register usage: returns a register of either long or double. | 
|  | .put("510-checker-try-catch", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test with backport method which is still present in DX input. | 
|  | .put("530-checker-lse2", TestCondition.match(TestCondition.tools(DexTool.DX))) | 
|  | // Test with invalid register usage: contains an int-to-byte on the result of aget-object. | 
|  | .put("518-null-array-get", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test with invalid register usage: phi of int and float. | 
|  | .put("535-regression-const-val", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test with invalid register usage: phi of int and float. | 
|  | .put("552-checker-primitive-typeprop", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test with invalid register usage: invoke-static {v0,v0}, foo(IL)V | 
|  | .put("557-checker-ref-equivalent", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Test with smali code that calls a method that needs to be desugared. | 
|  | // The smali code is only present in the non-legacy test distrubution, so this only fails | 
|  | // when running the "default" runtime. | 
|  | .put( | 
|  | "567-checker-compare", | 
|  | TestCondition.or( | 
|  | TestCondition.match(TestCondition.runtimes(Runtime.ART_DEFAULT)), | 
|  | TestCondition.match(TestCondition.tools(DexTool.DX)))) | 
|  | // This test is starting from invalid dex code. It splits up a double value and uses | 
|  | // the first register of a double with the second register of another double. | 
|  | .put("800-smali", TestCondition.match(TestCondition.R8DEX_COMPILER)) | 
|  | // Contains a loop in the class hierarchy. | 
|  | .put("804-class-extends-itself", TestCondition.any()) | 
|  | // Test with backport method which is still present in DX input. | 
|  | .put("912-classes", TestCondition.match(TestCondition.tools(DexTool.DX))) | 
|  | // These tests have illegal class flag combinations, so we reject them. | 
|  | .put("161-final-abstract-class", TestCondition.any()) | 
|  | .build(); | 
|  |  | 
|  | // Tests that does not have dex input for some toolchains. | 
|  | private static Multimap<String, TestCondition> noInputDex = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | .put("914-hello-obsolescence", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("915-obsolete-2", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("916-obsolete-jit", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("919-obsolete-fields", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("926-multi-obsolescence", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("941-recurive-obsolete-jit", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("940-recursive-obsolete", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("942-private-recursive", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("943-private-recursive-jit", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("945-obsolete-native", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("948-change-annotations", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("952-invoke-custom", TestCondition.match( | 
|  | TestCondition.tools(DexTool.NONE, DexTool.DX), | 
|  | LEGACY_RUNTIME)) | 
|  | .put("952-invoke-custom-kinds", | 
|  | TestCondition.match(TestCondition.tools(DexTool.DX))) | 
|  | .put("979-const-method-handle", | 
|  | TestCondition.match(TestCondition.tools(DexTool.DX))) | 
|  | .build(); | 
|  |  | 
|  | // Tests that does not have valid input for us to be compatible with dx running. | 
|  | private static List<String> noInputJar = ImmutableList.of( | 
|  | "097-duplicate-method", // No input class files. | 
|  | "630-safecast-array", // No input class files. | 
|  | "801-VoidCheckCast", // No input class files. | 
|  | "804-class-extends-itself", // No input class files. | 
|  | "972-iface-super-multidex", // Based on multiple smali files | 
|  | "973-default-multidex", // Based on multiple smali files. | 
|  | "974-verify-interface-super", // No input class files. | 
|  | "975-iface-private", // No input class files. | 
|  | "976-conflict-no-methods", // No input class files | 
|  | "978-virtual-interface", // No input class files | 
|  | "663-odd-dex-size", // No input class files | 
|  | "663-odd-dex-size2", // No input class files | 
|  | "663-odd-dex-size3", // No input class files | 
|  | "663-odd-dex-size4" // No input class files | 
|  | ); | 
|  |  | 
|  | // Some JCTF test cases require test classes from other tests. These are listed here. | 
|  | private static final Map<String, List<String>> jctfTestsExternalClassFiles = | 
|  | new ImmutableMap.Builder<String, List<String>>() | 
|  | .put("lang.RuntimePermission.Class.RuntimePermission_class_A13", | 
|  | new ImmutableList.Builder<String>() | 
|  | .add("lang/Thread/stop/Thread_stop_A02.class") | 
|  | .add("lang/Thread/stopLjava_lang_Throwable/Thread_stop_A02.class") | 
|  | .build()) | 
|  | .put("lang.RuntimePermission.Class.RuntimePermission_class_A02", | 
|  | new ImmutableList.Builder<String>() | 
|  | .add("lang/Class/getClassLoader/Class_getClassLoader_A03.class") | 
|  | .add("lang/ClassLoader/getParent/ClassLoader_getParent_A02.class") | 
|  | .add("lang/Thread/getContextClassLoader/Thread_getContextClassLoader_A02.class") | 
|  | .add("lang/Runtime/exitI/Runtime_exit_A02.class") | 
|  | .build()) | 
|  | .put("lang.Runtime.exitI.Runtime_exit_A03", | 
|  | new ImmutableList.Builder<String>() | 
|  | .add("lang/Runtime/exitI/Runtime_exit_A02.class") | 
|  | .build()) | 
|  | .build(); | 
|  |  | 
|  | // Tests to skip on some conditions | 
|  | private static final Multimap<String, TestCondition> testToSkip = | 
|  | new ImmutableListMultimap.Builder<String, TestCondition>() | 
|  | // When running R8 on dex input (D8, DX) this test non-deterministically fails | 
|  | // with a compiler exception, due to invoke-virtual on an interface, or it completes but | 
|  | // the output when run on Art is not as expected. b/65233869 | 
|  | .put( | 
|  | "162-method-resolution", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX, DexTool.NONE), TestCondition.R8_COMPILER)) | 
|  | // Produces wrong output when compiled in release mode, which we cannot express. | 
|  | .put("015-switch", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4))) | 
|  | // To prevent "Dex file with version '37' cannot be used with min sdk level '21'", which | 
|  | // would otherwise happen because D8 passes through the DEX code from DX. | 
|  | .put( | 
|  | "800-smali", | 
|  | TestCondition.match( | 
|  | TestCondition.tools(DexTool.DX), | 
|  | TestCondition.D8_COMPILER, | 
|  | TestCondition.runtimesFrom(DexVm.Version.V5_1_1))) | 
|  | .build(); | 
|  |  | 
|  | public static List<String> requireInliningToBeDisabled = | 
|  | ImmutableList.of( | 
|  | // Test for a specific stack trace that gets destroyed by inlining. | 
|  | "008-exceptions", | 
|  | "492-checker-inline-invoke-interface", | 
|  | "493-checker-inline-invoke-interface", | 
|  | "488-checker-inline-recursive-calls", | 
|  | "487-checker-inline-calls", | 
|  | "122-npe", | 
|  | "141-class-unload", | 
|  |  | 
|  | // Calls some internal art methods that cannot tolerate inlining. | 
|  | "466-get-live-vreg", | 
|  |  | 
|  | // Requires a certain call pattern to surface an Art bug. | 
|  | "534-checker-bce-deoptimization", | 
|  |  | 
|  | // Requires something to be allocated in a method so that it goes out of scope. | 
|  | "059-finalizer-throw", | 
|  |  | 
|  | // Has tests in submethods, which we should not inline. | 
|  | "625-checker-licm-regressions"); | 
|  |  | 
|  | private static List<String> requireClassInliningToBeDisabled = ImmutableList.of( | 
|  | // Test is registered to be failing (failingRunWithArt), it fails only on 4.0.4 | 
|  | // (because of a bug in this version of dalvik) but succeeds on later versions. | 
|  | // If class inliner us enabled it actually works fine on 4.0.4 since the class | 
|  | // instantiation is properly inlined. | 
|  | "301-abstract-protected", | 
|  | // Test depends on exception produced for missing method or similar cases, but | 
|  | // after class inlining removes class instantiations and references the exception | 
|  | // is not produced. | 
|  | "435-new-instance" | 
|  | ); | 
|  |  | 
|  | private static List<String> hasMissingClasses = ImmutableList.of( | 
|  | "091-override-package-private-method", | 
|  | "003-omnibus-opcodes", | 
|  | "608-checker-unresolved-lse", | 
|  | "529-checker-unresolved", | 
|  | "803-no-super", | 
|  | "127-checker-secondarydex", | 
|  | "952-invoke-custom-kinds", | 
|  | // Depends on java.lang.invoke.Transformers, which is hidden. | 
|  | "958-methodhandle-stackframe" | 
|  | ); | 
|  |  | 
|  | private static Map<String, List<String>> keepRules = | 
|  | ImmutableMap.of( | 
|  | "021-string2", ImmutableList.of("-dontwarn junit.framework.**"), | 
|  | "082-inline-execute", ImmutableList.of("-dontwarn junit.framework.**")); | 
|  |  | 
|  | private static Map<String, Consumer<InternalOptions>> configurations = | 
|  | ImmutableMap.of( | 
|  | // Has a new-instance instruction that attempts to instantiate an interface. | 
|  | "162-method-resolution", options -> options.testing.disableStackMapVerification = true, | 
|  | "435-new-instance", options -> options.testing.allowTypeErrors = true); | 
|  |  | 
|  | private static List<String> failuresToTriage = ImmutableList.of( | 
|  | // Dex file input into a jar file, not yet supported by the test framework. | 
|  | "663-odd-dex-size", | 
|  | "663-odd-dex-size2", | 
|  | "663-odd-dex-size3", | 
|  | "663-odd-dex-size4", | 
|  |  | 
|  | // This is flaky. | 
|  | "104-growth-limit", | 
|  |  | 
|  | // Various failures. | 
|  | "138-duplicate-classes-check", | 
|  | "461-get-reference-vreg", | 
|  | "629-vdex-speed", | 
|  | "638-no-line-number", | 
|  | "647-jni-get-field-id", | 
|  | "649-vdex-duplicate-method", | 
|  | "652-deopt-intrinsic", | 
|  | "655-jit-clinit", | 
|  | "656-annotation-lookup-generic-jni", | 
|  | "656-loop-deopt", | 
|  | "707-checker-invalid-profile", | 
|  | "708-jit-cache-churn", | 
|  |  | 
|  | // These use "native trace". | 
|  | "981-dedup-original-dex", | 
|  | "982-ok-no-retransform", | 
|  | "983-source-transform-verify", | 
|  | "984-obsolete-invoke", | 
|  | "985-re-obsolete", | 
|  | "986-native-method-bind", | 
|  | "987-agent-bind", | 
|  | "988-method-trace", | 
|  | "989-method-trace-throw", | 
|  | "990-field-trace", | 
|  | "991-field-trace-2", | 
|  | "992-source-data", | 
|  | "993-breakpoints", | 
|  | "994-breakpoint-line", | 
|  | "995-breakpoints-throw", | 
|  | "996-breakpoint-obsolete", | 
|  | "997-single-step", | 
|  |  | 
|  | // These two fail with missing *-hostdex.jar files. | 
|  | "648-inline-caches-unresolved", | 
|  | "998-redefine-use-after-free" | 
|  | ); | 
|  |  | 
|  | private static class TestSpecification { | 
|  |  | 
|  | // Name of the Art test | 
|  | private final String name; | 
|  | // Directory of the test files (containing prebuild dex/jar files and expected output). | 
|  | private final File directory; | 
|  | // Compiler that these expectations are for dx, or none if running on class files. | 
|  | private final DexTool dexTool; | 
|  | // Native library to use for running this test - if any. | 
|  | private final String nativeLibrary; | 
|  | // Skip running this test with Art or Java - most likely due to timeout. | 
|  | private final boolean skipRun; | 
|  | // Skip running this test altogether. For example, there might be no input in this configuration | 
|  | // (e.g. no class files). | 
|  | private final boolean skipTest; | 
|  | // Fails compilation - most likely throws an exception. | 
|  | private final boolean failsWithX8; | 
|  | // Expected to fail compilation with a CompilationError. | 
|  | private final boolean expectedToFailWithX8; | 
|  | // Fails running the output in Art or on Java with an assertion error. On Art it's typically due | 
|  | // to verification errors. | 
|  | private final boolean failsOnRun; | 
|  | // Runs in art but fails the run because it produces different results. | 
|  | private final boolean failsWithArtOutput; | 
|  | // Original fails in art but the R8/D8 version can run in art. | 
|  | private final boolean failsWithArtOriginalOnly; | 
|  | // Test might produce different outputs. | 
|  | private final boolean outputMayDiffer; | 
|  | // Whether to disable inlining | 
|  | private final boolean disableInlining; | 
|  | // Whether to disable class inlining | 
|  | private final boolean disableClassInlining; | 
|  | // Has missing classes. | 
|  | private final boolean hasMissingClasses; | 
|  | // Explicitly disable desugaring. | 
|  | private final boolean disableDesugaring; | 
|  | // Extra keep rules to use when running with R8. | 
|  | private final List<String> keepRules; | 
|  | private final Consumer<InternalOptions> configuration; | 
|  |  | 
|  | TestSpecification( | 
|  | String name, | 
|  | DexTool dexTool, | 
|  | File directory, | 
|  | boolean skipRun, | 
|  | boolean skipTest, | 
|  | boolean failsWithX8, | 
|  | boolean failsOnRun, | 
|  | boolean failsWithArtOutput, | 
|  | boolean failsWithArtOriginalOnly, | 
|  | String nativeLibrary, | 
|  | boolean expectedToFailWithX8, | 
|  | boolean outputMayDiffer, | 
|  | boolean disableInlining, | 
|  | boolean disableClassInlining, | 
|  | boolean hasMissingClasses, | 
|  | boolean disableDesugaring, | 
|  | List<String> keepRules, | 
|  | Consumer<InternalOptions> configuration) { | 
|  | this.name = name; | 
|  | this.dexTool = dexTool; | 
|  | this.nativeLibrary = nativeLibrary; | 
|  | this.directory = directory; | 
|  | this.skipRun = skipRun; | 
|  | this.skipTest = skipTest; | 
|  | this.failsWithX8 = failsWithX8; | 
|  | this.failsOnRun = failsOnRun; | 
|  | this.failsWithArtOutput = failsWithArtOutput; | 
|  | this.failsWithArtOriginalOnly = failsWithArtOriginalOnly; | 
|  | this.expectedToFailWithX8 = expectedToFailWithX8; | 
|  | this.outputMayDiffer = outputMayDiffer; | 
|  | this.disableInlining = disableInlining; | 
|  | this.disableClassInlining = disableClassInlining; | 
|  | this.hasMissingClasses = hasMissingClasses; | 
|  | this.disableDesugaring = disableDesugaring; | 
|  | this.keepRules = keepRules; | 
|  | this.configuration = configuration; | 
|  | } | 
|  |  | 
|  | TestSpecification( | 
|  | String name, | 
|  | DexTool dexTool, | 
|  | File directory, | 
|  | boolean skipRun, | 
|  | boolean failsOnRun, | 
|  | boolean disableInlining, | 
|  | DexVm dexVm) { | 
|  | this( | 
|  | name, | 
|  | dexTool, | 
|  | directory, | 
|  | skipRun, | 
|  | ToolHelper.isWindows() && dexVm.getKind() == Kind.HOST, | 
|  | false, | 
|  | failsOnRun, | 
|  | false, | 
|  | false, | 
|  | null, | 
|  | false, | 
|  | false, | 
|  | disableInlining, | 
|  | true, // Disable class inlining for JCTF tests. | 
|  | false, | 
|  | true, // Disable desugaring for JCTF tests. | 
|  | ImmutableList.of(), | 
|  | null); | 
|  | } | 
|  |  | 
|  | TestSpecification( | 
|  | String name, | 
|  | DexTool dexTool, | 
|  | File directory, | 
|  | boolean skipRun, | 
|  | boolean failsOnRun, | 
|  | boolean disableInlining) { | 
|  | this( | 
|  | name, | 
|  | dexTool, | 
|  | directory, | 
|  | skipRun, | 
|  | false, | 
|  | false, | 
|  | failsOnRun, | 
|  | false, | 
|  | false, | 
|  | null, | 
|  | false, | 
|  | false, | 
|  | disableInlining, | 
|  | true, // Disable class inlining for JCTF tests. | 
|  | false, | 
|  | true, // Disable desugaring for JCTF tests. | 
|  | ImmutableList.of(), | 
|  | null); | 
|  | } | 
|  |  | 
|  | public File resolveFile(String name) { | 
|  | return directory.toPath().resolve(name).toFile(); | 
|  | } | 
|  |  | 
|  | public String toString() { | 
|  | return name + " (" + dexTool + ")"; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class SpecificationKey { | 
|  |  | 
|  | private final String name; | 
|  | private final DexTool toolchain; | 
|  |  | 
|  | private SpecificationKey(String name, DexTool toolchain) { | 
|  | assert name != null; | 
|  | this.name = name; | 
|  | this.toolchain = toolchain; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object o) { | 
|  | if (!(o instanceof SpecificationKey)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | SpecificationKey that = (SpecificationKey) o; | 
|  | return name.equals(that.name) && toolchain == that.toolchain; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return 31 * name.hashCode() + toolchain.hashCode(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static Set<String> collectTestsMatchingConditions( | 
|  | DexTool dexTool, | 
|  | CompilerUnderTest compilerUnderTest, | 
|  | DexVm.Version dexVmVersion, | 
|  | CompilationMode mode, | 
|  | Multimap<String, TestCondition> testConditionsMap) { | 
|  | Set<String> set = Sets.newHashSet(); | 
|  | for (Map.Entry<String, TestCondition> kv : testConditionsMap.entries()) { | 
|  | if (kv.getValue().test(dexTool, compilerUnderTest, dexVmVersion, mode)) { | 
|  | set.add(kv.getKey()); | 
|  | } | 
|  | } | 
|  | return set; | 
|  | } | 
|  |  | 
|  | private static Map<SpecificationKey, TestSpecification> getTestsMap( | 
|  | CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm dexVm) { | 
|  | DexVm.Version version = dexVm.getVersion(); | 
|  | File defaultArtTestDir = new File(ART_TESTS_DIR); | 
|  | File legacyArtTestDir = new File(ART_LEGACY_TESTS_DIR); | 
|  | if (!defaultArtTestDir.exists() || !legacyArtTestDir.exists()) { | 
|  | // Don't run any tests if the directory does not exist. | 
|  | return Collections.emptyMap(); | 
|  | } | 
|  | Map<SpecificationKey, TestSpecification> data = new HashMap<>(); | 
|  |  | 
|  | // Collect tests where running Art is skipped (we still run R8/D8 on these). | 
|  | Set<String> skipArt = new HashSet<>(customRun); | 
|  |  | 
|  | // Collect the tests requiring the native library. | 
|  | Set<String> useNativeLibrary = Sets.newHashSet(useJNI); | 
|  | for (DexTool dexTool : DexTool.values()) { | 
|  | Set<String> skipTest = Sets.newHashSet(skipAltogether); | 
|  | skipTest.addAll(usesNativeAgentCode); | 
|  | skipTest.addAll(failuresToTriage); | 
|  |  | 
|  | File artTestDir = | 
|  | LEGACY_RUNTIME.contains(Runtime.fromDexVmVersion(version)) | 
|  | ? legacyArtTestDir | 
|  | : defaultArtTestDir; | 
|  | // Collect the tests failing code generation. | 
|  | Set<String> failsWithCompiler = | 
|  | collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, failingWithCompiler); | 
|  |  | 
|  | // Collect the tests that are flaky. | 
|  | skipArt.addAll(collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, flakyRunWithArt)); | 
|  |  | 
|  | // Collect tests that has no input: | 
|  | if (dexTool == DexTool.NONE) { | 
|  | skipTest.addAll(noInputJar); | 
|  | } | 
|  |  | 
|  | // Collect the test that we should skip in this configuration because there is no dex input | 
|  | skipTest.addAll(collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, noInputDex)); | 
|  |  | 
|  | // Collect the test that we should skip in this configuration | 
|  | skipTest.addAll(collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, testToSkip)); | 
|  |  | 
|  | // Collect the test that we should skip in this configuration. | 
|  | skipArt.addAll( | 
|  | collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, timeoutOrSkipRunWithArt)); | 
|  |  | 
|  | // Collect the tests failing to run in Art (we still run R8/D8 on these). | 
|  | Set<String> failsWithArt = | 
|  | collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, failingRunWithArt); | 
|  | { | 
|  | Set<String> tmpSet = | 
|  | collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, expectedToFailRunWithArt); | 
|  | failsWithArt.addAll(tmpSet); | 
|  | } | 
|  |  | 
|  | if (!ToolHelper.isDefaultDexVm(dexVm)) { | 
|  | // Generally failing when not TOT art. | 
|  | failsWithArt.addAll(expectedToFailRunWithArtNonDefault); | 
|  | // Version specific failures | 
|  | failsWithArt.addAll(expectedToFailRunWithArtVersion.get(version)); | 
|  | } | 
|  |  | 
|  | // Collect the tests failing with output differences in Art. | 
|  | Set<String> failsRunWithArtOutput = | 
|  | collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, failingRunWithArtOutput); | 
|  | Set<String> expectedToFailWithCompilerSet = | 
|  | collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, expectedToFailWithCompiler); | 
|  |  | 
|  | // Collect the tests where the original works in Art and the R8/D8 generated output does not. | 
|  | Set<String> failsRunWithArtOriginalOnly = | 
|  | collectTestsMatchingConditions( | 
|  | dexTool, compilerUnderTest, version, compilationMode, failingRunWithArtOriginalOnly); | 
|  |  | 
|  | File compilerTestDir = artTestDir.toPath().resolve(dexToolDirectory(dexTool)).toFile(); | 
|  | File[] testDirs = compilerTestDir.listFiles(); | 
|  | assert testDirs != null; | 
|  | for (File testDir : testDirs) { | 
|  | String name = testDir.getName(); | 
|  | boolean skip = skipTest.contains(name); | 
|  | // All the native code for all Art tests is currently linked into the | 
|  | // libarttest.so file. | 
|  | data.put( | 
|  | new SpecificationKey(name, dexTool), | 
|  | new TestSpecification( | 
|  | name, | 
|  | dexTool, | 
|  | testDir, | 
|  | skipArt.contains(name), | 
|  | skip || ToolHelper.isWindows() && dexVm.getKind() == Kind.HOST, | 
|  | failsWithCompiler.contains(name), | 
|  | failsWithArt.contains(name), | 
|  | failsRunWithArtOutput.contains(name), | 
|  | failsRunWithArtOriginalOnly.contains(name), | 
|  | useNativeLibrary.contains(name) ? "arttest" : null, | 
|  | expectedToFailWithCompilerSet.contains(name), | 
|  | outputMayDiffer.contains(name), | 
|  | requireInliningToBeDisabled.contains(name), | 
|  | requireClassInliningToBeDisabled.contains(name), | 
|  | hasMissingClasses.contains(name), | 
|  | false, | 
|  | keepRules.getOrDefault(name, ImmutableList.of()), | 
|  | configurations.get(name))); | 
|  | } | 
|  | } | 
|  | return data; | 
|  | } | 
|  |  | 
|  | private static CompilationMode defaultCompilationMode(CompilerUnderTest compilerUnderTest) { | 
|  | CompilationMode compilationMode = null; | 
|  | switch (compilerUnderTest) { | 
|  | case R8: | 
|  | case R8CF: | 
|  | compilationMode = CompilationMode.RELEASE; | 
|  | break; | 
|  | case D8: | 
|  | case R8_AFTER_D8: | 
|  | case D8_AFTER_R8CF: | 
|  | compilationMode = CompilationMode.DEBUG; | 
|  | break; | 
|  | default: | 
|  | throw new RuntimeException("Unreachable."); | 
|  | } | 
|  | return compilationMode; | 
|  | } | 
|  |  | 
|  | private static String dexToolDirectory(DexTool tool) { | 
|  | // DexTool.NONE uses class files in the dx directory. | 
|  | return "dx"; | 
|  | } | 
|  |  | 
|  | @Rule | 
|  | public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); | 
|  |  | 
|  | @Rule | 
|  | public TestDescriptionWatcher watcher = new TestDescriptionWatcher(); | 
|  |  | 
|  | public R8RunArtTestsTest(String name, DexTool toolchain) { | 
|  | this.name = name; | 
|  | this.toolchain = toolchain; | 
|  | } | 
|  |  | 
|  | private ArtCommandBuilder buildArtCommand( | 
|  | File dexFile, TestSpecification specification, DexVm artVersion) { | 
|  | ArtCommandBuilder builder = new ArtCommandBuilder(artVersion); | 
|  | builder.appendClasspath(dexFile.toString()); | 
|  | // All Art tests have the main class Main. | 
|  | builder.setMainClass("Main"); | 
|  | if (specification.nativeLibrary != null) { | 
|  | // All the native libraries for all Art tests is in the same directory. | 
|  | File artTestNativeLibraryDir = new File(ART_TESTS_NATIVE_LIBRARY_DIR); | 
|  | if (artVersion != DexVm.ART_DEFAULT) { | 
|  | artTestNativeLibraryDir = new File(ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR); | 
|  | } | 
|  | builder.addToJavaLibraryPath(artTestNativeLibraryDir); | 
|  | builder.appendProgramArgument(specification.nativeLibrary); | 
|  | } | 
|  | return builder; | 
|  | } | 
|  |  | 
|  | private static class CompilationOptions implements Consumer<InternalOptions> { | 
|  |  | 
|  | private final boolean disableInlining; | 
|  | private final boolean disableClassInlining; | 
|  | private final boolean hasMissingClasses; | 
|  | private final boolean disableDesugaring; | 
|  | private final List<String> keepRules; | 
|  | private final Consumer<InternalOptions> configuration; | 
|  |  | 
|  | private CompilationOptions(TestSpecification spec) { | 
|  | this.disableInlining = spec.disableInlining; | 
|  | this.disableClassInlining = spec.disableClassInlining; | 
|  | this.hasMissingClasses = spec.hasMissingClasses; | 
|  | this.disableDesugaring = spec.disableDesugaring; | 
|  | this.keepRules = spec.keepRules; | 
|  | this.configuration = spec.configuration; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void accept(InternalOptions options) { | 
|  | if (disableInlining) { | 
|  | options.enableInlining = false; | 
|  | } | 
|  | if (disableClassInlining) { | 
|  | options.enableClassInlining = false; | 
|  | } | 
|  | // Some tests actually rely on missing classes for what they test. | 
|  | options.ignoreMissingClasses = hasMissingClasses; | 
|  | if (configuration != null) { | 
|  | configuration.accept(options); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object o) { | 
|  | if (this == o) { | 
|  | return true; | 
|  | } | 
|  | if (o == null || getClass() != o.getClass()) { | 
|  | return false; | 
|  | } | 
|  | CompilationOptions options = (CompilationOptions) o; | 
|  | return disableInlining == options.disableInlining | 
|  | && disableClassInlining == options.disableClassInlining | 
|  | && hasMissingClasses == options.hasMissingClasses; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return Objects.hash( | 
|  | disableInlining, | 
|  | disableClassInlining, | 
|  | hasMissingClasses); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void executeCompilerUnderTest( | 
|  | CompilerUnderTest compilerUnderTest, | 
|  | Collection<String> fileNames, | 
|  | String resultPath, | 
|  | CompilationMode compilationMode, | 
|  | CompilationOptions compilationOptions) | 
|  | throws CompilationFailedException { | 
|  | executeCompilerUnderTest( | 
|  | compilerUnderTest, fileNames, resultPath, compilationMode, null, compilationOptions); | 
|  | } | 
|  |  | 
|  | private void executeCompilerUnderTest( | 
|  | CompilerUnderTest compilerUnderTest, | 
|  | Collection<String> fileNames, | 
|  | String resultPath, | 
|  | CompilationMode mode, | 
|  | String keepRulesFile, | 
|  | CompilationOptions compilationOptions) | 
|  | throws CompilationFailedException { | 
|  | assert mode != null; | 
|  | switch (compilerUnderTest) { | 
|  | case D8_AFTER_R8CF: | 
|  | { | 
|  | assert keepRulesFile == null : "Keep-rules file specified for D8."; | 
|  |  | 
|  | List<ProgramResource> dexInputs = new ArrayList<>(); | 
|  | List<ProgramResource> cfInputs = new ArrayList<>(); | 
|  | for (String f : fileNames) { | 
|  | Path p = Paths.get(f); | 
|  | if (FileUtils.isDexFile(p)) { | 
|  | dexInputs.add(ProgramResource.fromFile(ProgramResource.Kind.DEX, p)); | 
|  | } else if (FileUtils.isClassFile(p)) { | 
|  | cfInputs.add(ProgramResource.fromFile(ProgramResource.Kind.CF, p)); | 
|  | } else { | 
|  | assert FileUtils.isArchive(p); | 
|  | ArchiveProgramResourceProvider provider = | 
|  | ArchiveProgramResourceProvider.fromArchive(p); | 
|  |  | 
|  | try { | 
|  | for (ProgramResource pr : provider.getProgramResources()) { | 
|  | if (pr.getKind() == ProgramResource.Kind.DEX) { | 
|  | dexInputs.add(pr); | 
|  | } else { | 
|  | assert pr.getKind() == ProgramResource.Kind.CF; | 
|  | cfInputs.add(pr); | 
|  | } | 
|  | } | 
|  | } catch (ResourceException e) { | 
|  | throw new CompilationError("", e); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | D8Command.Builder builder = | 
|  | D8Command.builder() | 
|  | .setMode(mode) | 
|  | .addProgramResourceProvider( | 
|  | new ProgramResourceProvider() { | 
|  | @Override | 
|  | public Collection<ProgramResource> getProgramResources() { | 
|  | return dexInputs; | 
|  | } | 
|  | }) | 
|  | .setOutput(Paths.get(resultPath), OutputMode.DexIndexed); | 
|  |  | 
|  | Origin cfOrigin = | 
|  | new Origin(Origin.root()) { | 
|  | @Override | 
|  | public String part() { | 
|  | return "R8/CF"; | 
|  | } | 
|  | }; | 
|  |  | 
|  | R8Command.Builder r8builder = | 
|  | R8Command.builder() | 
|  | .setMode(mode) | 
|  | .setDisableTreeShaking(true) | 
|  | .setDisableMinification(true) | 
|  | .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()) | 
|  | .addProguardConfiguration(compilationOptions.keepRules, Origin.unknown()) | 
|  | .setProgramConsumer( | 
|  | new ClassFileConsumer() { | 
|  |  | 
|  | @Override | 
|  | public synchronized void accept( | 
|  | ByteDataView data, String descriptor, DiagnosticsHandler handler) { | 
|  | builder.addClassProgramData(data.copyByteData(), cfOrigin); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finished(DiagnosticsHandler handler) {} | 
|  | }) | 
|  | .addProgramResourceProvider( | 
|  | new ProgramResourceProvider() { | 
|  | @Override | 
|  | public Collection<ProgramResource> getProgramResources() | 
|  | throws ResourceException { | 
|  | return cfInputs; | 
|  | } | 
|  | }); | 
|  |  | 
|  | AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name); | 
|  | if (minSdkVersion != null) { | 
|  | builder.setMinApiLevel(minSdkVersion.getLevel()); | 
|  | builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion)); | 
|  | r8builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion)); | 
|  | } else { | 
|  | builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault())); | 
|  | r8builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault())); | 
|  | } | 
|  | ToolHelper.runR8(r8builder.build(), options -> options.ignoreMissingClasses = true); | 
|  | D8.run(builder.build()); | 
|  | break; | 
|  | } | 
|  | case D8: { | 
|  | assert keepRulesFile == null : "Keep-rules file specified for D8."; | 
|  | D8Command.Builder builder = | 
|  | D8Command.builder() | 
|  | .setMode(mode) | 
|  | .addProgramFiles(ListUtils.map(fileNames, Paths::get)) | 
|  | .setOutput(Paths.get(resultPath), OutputMode.DexIndexed) | 
|  | .setDisableDesugaring(compilationOptions.disableDesugaring); | 
|  | AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name); | 
|  | if (minSdkVersion != null) { | 
|  | builder.setMinApiLevel(minSdkVersion.getLevel()); | 
|  | builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion)); | 
|  | } else { | 
|  | builder | 
|  | .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault())); | 
|  | } | 
|  | ToolHelper.runD8(builder, compilationOptions::accept); | 
|  | break; | 
|  | } | 
|  | case R8: | 
|  | case R8CF: | 
|  | { | 
|  | boolean cfBackend = compilerUnderTest == CompilerUnderTest.R8CF; | 
|  | R8Command.Builder builder = | 
|  | R8Command.builder() | 
|  | .setMode(mode) | 
|  | .setDisableTreeShaking(true) | 
|  | .setDisableMinification(true) | 
|  | .setDisableDesugaring(compilationOptions.disableDesugaring) | 
|  | .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()) | 
|  | .addProguardConfiguration(compilationOptions.keepRules, Origin.unknown()) | 
|  | .setOutput( | 
|  | Paths.get(resultPath), | 
|  | cfBackend ? OutputMode.ClassFile : OutputMode.DexIndexed); | 
|  | ToolHelper.allowTestProguardOptions(builder); | 
|  | // Add program files directly to the underlying app to avoid errors on DEX inputs. | 
|  | ToolHelper.getAppBuilder(builder).addProgramFiles(ListUtils.map(fileNames, Paths::get)); | 
|  | if (cfBackend) { | 
|  | builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar()); | 
|  | } else { | 
|  | AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name); | 
|  | if (minSdkVersion != null) { | 
|  | builder.setMinApiLevel(minSdkVersion.getLevel()); | 
|  | ToolHelper.addFilteredAndroidJar(builder, minSdkVersion); | 
|  | } else { | 
|  | ToolHelper.addFilteredAndroidJar(builder, AndroidApiLevel.getDefault()); | 
|  | } | 
|  | } | 
|  | if (keepRulesFile != null) { | 
|  | builder.addProguardConfigurationFiles(Paths.get(keepRulesFile)); | 
|  | } | 
|  | // Add internal flags for testing purposes. | 
|  | ToolHelper.runR8( | 
|  | builder.build(), | 
|  | options -> { | 
|  | compilationOptions.accept(options); | 
|  | // Make sure we don't depend on this settings. | 
|  | options.classInliningInstructionAllowance = 10000; | 
|  | options.lineNumberOptimization = LineNumberOptimization.OFF; | 
|  | }); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | assert false : compilerUnderTest; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static boolean isAuxClassFile(String fileName, String auxClassFileBase) { | 
|  | return fileName.endsWith(".class") | 
|  | && (fileName.startsWith(auxClassFileBase + "$") | 
|  | || fileName.startsWith(auxClassFileBase + "_")); | 
|  | } | 
|  |  | 
|  |  | 
|  | private ArrayList<File> getJctfTestAuxClassFiles(File classFile) { | 
|  | // Collect additional files from the same directory with file names like | 
|  | // <dir>/<filename_wo_ext>$*.class and <dir>/<filename_wo_ext>_*.class | 
|  | String classFileString = classFile.toString(); | 
|  | assert classFileString.endsWith(".class"); | 
|  |  | 
|  | String auxClassFileBase = | 
|  | new File( | 
|  | classFileString.substring(0, classFileString.length() - ".class".length())) | 
|  | .getName(); | 
|  |  | 
|  | ArrayList<File> auxClassFiles = new ArrayList<>(); | 
|  |  | 
|  | File[] files = classFile.getParentFile() | 
|  | .listFiles( | 
|  | (File file) -> isAuxClassFile(file.getName(), auxClassFileBase)); | 
|  | if (files != null) { | 
|  | auxClassFiles.addAll(Arrays.asList(files)); | 
|  | } | 
|  |  | 
|  | if (auxClassFileBase.matches(".*[A-Z]\\d\\d")) { | 
|  | // Also collect all the files in this directory that doesn't match this pattern | 
|  | // They will be helper classes defined in one of the test class files but we don't know in | 
|  | // which one, so we just add them to all tests. | 
|  | final int SUFFIX_LENGTH_TO_STRIP = 3; // one letter (usually 'A' and two digits) | 
|  | String testClassFilePattern = | 
|  | auxClassFileBase.substring(0, auxClassFileBase.length() - SUFFIX_LENGTH_TO_STRIP) | 
|  | + "[A-Z]\\d\\d.*\\.class"; | 
|  | files = classFile.getParentFile() | 
|  | .listFiles( | 
|  | (File file) -> file.getName().matches(".*\\.class") && !file.getName() | 
|  | .matches(testClassFilePattern)); | 
|  | if (files != null) { | 
|  | auxClassFiles.addAll(Arrays.asList(files)); | 
|  | } | 
|  | } | 
|  |  | 
|  | return auxClassFiles; | 
|  | } | 
|  |  | 
|  | private static BiFunction<Outcome, Boolean, TestSpecification> jctfOutcomeToSpecification( | 
|  | String name, DexTool dexTool, File resultDir, DexVm dexVm) { | 
|  | return (outcome, noInlining) -> | 
|  | new TestSpecification( | 
|  | name, | 
|  | dexTool, | 
|  | resultDir, | 
|  | outcome == JctfTestSpecifications.Outcome.TIMEOUTS_WHEN_RUN | 
|  | || outcome == JctfTestSpecifications.Outcome.FLAKY_WHEN_RUN, | 
|  | outcome == JctfTestSpecifications.Outcome.FAILS_WHEN_RUN, | 
|  | noInlining, | 
|  | dexVm); | 
|  | } | 
|  |  | 
|  | private static BiFunction<Outcome, Boolean, TestSpecification> jctfOutcomeToSpecificationJava( | 
|  | String name, File resultDir) { | 
|  | return (outcome, noInlining) -> | 
|  | new TestSpecification( | 
|  | name, | 
|  | DexTool.NONE, | 
|  | resultDir, | 
|  | outcome == JctfTestSpecifications.Outcome.TIMEOUTS_WHEN_RUN | 
|  | || outcome == JctfTestSpecifications.Outcome.FLAKY_WHEN_RUN, | 
|  | outcome == JctfTestSpecifications.Outcome.FAILS_WHEN_RUN, | 
|  | noInlining); | 
|  | } | 
|  |  | 
|  | private static Runtime getRuntime(TestRuntime vm) { | 
|  | if (vm.isCf()) { | 
|  | return Runtime.JAVA; | 
|  | } else if (vm.isDex()) { | 
|  | return Runtime.fromDexVmVersion(vm.asDex().getVm().getVersion()); | 
|  | } else { | 
|  | throw new Unreachable(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class VmSpec { | 
|  | final TestRuntime vm; | 
|  | final TestSpecification spec; | 
|  |  | 
|  | private VmSpec(TestRuntime vm, TestSpecification testSpecification) { | 
|  | this.vm = vm; | 
|  | this.spec = testSpecification; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class VmErrors { | 
|  | private final Set<TestRuntime> failedVms = new HashSet<>(); | 
|  | private StringBuilder message; | 
|  |  | 
|  | private void addShouldHaveFailedError(CompilerUnderTest compilerUnderTest, TestRuntime vm) { | 
|  | addFailure(vm); | 
|  | message.append( | 
|  | "FAILURE: Test should have failed on " | 
|  | + vm | 
|  | + " after compiling with " | 
|  | + compilerUnderTest | 
|  | + ".\n"); | 
|  | } | 
|  |  | 
|  | private void addFailedOnRunError( | 
|  | CompilerUnderTest compilerUnderTest, TestRuntime vm, AssertionError error) { | 
|  | addFailure(vm); | 
|  | message.append( | 
|  | "FAILURE: Test failed on " | 
|  | + vm | 
|  | + " after compiling with " | 
|  | + compilerUnderTest | 
|  | + ", error:\n" | 
|  | + error.getMessage() | 
|  | + "\n"); | 
|  | } | 
|  |  | 
|  | private void addFailure(TestRuntime vm) { | 
|  | if (message == null) { | 
|  | message = new StringBuilder(); | 
|  | } | 
|  | failedVms.add(vm); | 
|  | } | 
|  | } | 
|  |  | 
|  | protected void runJctfTest( | 
|  | CompilerUnderTest compilerUnderTest, String classFilePath, String fullClassName) | 
|  | throws IOException, CompilationFailedException { | 
|  | VmErrors vmErrors = runJctfTestCore(compilerUnderTest, classFilePath, fullClassName); | 
|  | if (vmErrors.message != null) { | 
|  | throw new RuntimeException(vmErrors.message.toString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private VmErrors runJctfTestCore( | 
|  | CompilerUnderTest compilerUnderTest, String classFilePath, String fullClassName) | 
|  | throws IOException, CompilationFailedException { | 
|  | VmErrors vmErrors = new VmErrors(); | 
|  | List<TestRuntime> vms = new ArrayList<>(); | 
|  | if (compilerUnderTest == CompilerUnderTest.R8CF) { | 
|  | // TODO(b/135411839): Run on all java runtimes. | 
|  | vms.add(TestRuntime.getDefaultJavaRuntime()); | 
|  | } else { | 
|  | for (DexVm vm : TestParametersBuilder.getAvailableDexVms()) { | 
|  | // TODO(144966342): Disabled for triaging failures | 
|  | if (vm.getVersion() == DexVm.Version.V10_0_0) { | 
|  | System.out.println("Running on 10.0.0 is disabled, see b/144966342"); | 
|  | continue; | 
|  | } | 
|  | vms.add(new DexRuntime(vm)); | 
|  | } | 
|  | } | 
|  |  | 
|  | CompilerUnderTest firstCompilerUnderTest = | 
|  | compilerUnderTest == CompilerUnderTest.R8_AFTER_D8 | 
|  | ? CompilerUnderTest.D8 | 
|  | : compilerUnderTest; | 
|  | CompilationMode compilationMode = defaultCompilationMode(compilerUnderTest); | 
|  |  | 
|  | List<VmSpec> vmSpecs = new ArrayList<>(); | 
|  | for (TestRuntime vm : vms) { | 
|  | File resultDir = | 
|  | temp.newFolder( | 
|  | firstCompilerUnderTest.toString().toLowerCase() + "-output-" + vm.toString()); | 
|  |  | 
|  | TestSpecification specification = | 
|  | JctfTestSpecifications.getExpectedOutcome( | 
|  | name, | 
|  | firstCompilerUnderTest, | 
|  | getRuntime(vm), | 
|  | compilationMode, | 
|  | compilerUnderTest == CompilerUnderTest.R8CF | 
|  | ? jctfOutcomeToSpecificationJava(name, resultDir) | 
|  | : jctfOutcomeToSpecification(name, DexTool.NONE, resultDir, vm.asDex().getVm())); | 
|  |  | 
|  | if (!specification.skipTest) { | 
|  | vmSpecs.add(new VmSpec(vm, specification)); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (vmSpecs.isEmpty()) { | 
|  | return vmErrors; | 
|  | } | 
|  |  | 
|  | File classFile = new File(JCTF_TESTS_PREFIX + "/" + classFilePath); | 
|  | if (!classFile.exists()) { | 
|  | throw new FileNotFoundException( | 
|  | "Class file for Jctf test not found: \"" + classFile.toString() + "\"."); | 
|  | } | 
|  |  | 
|  | ArrayList<File> classFiles = new ArrayList<>(); | 
|  | classFiles.add(classFile); | 
|  |  | 
|  | // some tests need files from other tests | 
|  | int langIndex = fullClassName.indexOf(".java."); | 
|  | assert langIndex >= 0; | 
|  | List<String> externalClassFiles = jctfTestsExternalClassFiles | 
|  | .get(fullClassName.substring(langIndex + ".java.".length())); | 
|  |  | 
|  | if (externalClassFiles != null) { | 
|  | for (String s : externalClassFiles) { | 
|  | classFiles.add(new File(JCTF_TESTS_LIB_PREFIX + "/java/" + s)); | 
|  | } | 
|  | } | 
|  |  | 
|  | ArrayList<File> allClassFiles = new ArrayList<>(); | 
|  |  | 
|  | for (File f : classFiles) { | 
|  | allClassFiles.add(f); | 
|  | allClassFiles.addAll(getJctfTestAuxClassFiles(f)); | 
|  | } | 
|  |  | 
|  | File jctfCommonFile = new File(JCTF_COMMON_JAR); | 
|  | if (!jctfCommonFile.exists()) { | 
|  | throw new FileNotFoundException( | 
|  | "Jar file of Jctf tests common code not found: \"" + jctfCommonFile.toString() + "\"."); | 
|  | } | 
|  |  | 
|  | File junitFile = new File(JUNIT_JAR); | 
|  | if (!junitFile.exists()) { | 
|  | throw new FileNotFoundException( | 
|  | "Junit Jar not found: \"" + junitFile.toString() + "\"."); | 
|  | } | 
|  |  | 
|  | File hamcrestFile = new File(HAMCREST_JAR); | 
|  | if (!hamcrestFile.exists()) { | 
|  | throw new FileNotFoundException( | 
|  | "Hamcrest Jar not found: \"" + hamcrestFile.toString() + "\"."); | 
|  | } | 
|  |  | 
|  | // allClassFiles may contain duplicated files, that's why the HashSet | 
|  | Set<String> fileNames = new HashSet<>(); | 
|  |  | 
|  | fileNames.addAll(Arrays.asList( | 
|  | jctfCommonFile.getCanonicalPath(), | 
|  | junitFile.getCanonicalPath(), | 
|  | hamcrestFile.getCanonicalPath() | 
|  | )); | 
|  |  | 
|  | for (File f : allClassFiles) { | 
|  | fileNames.add(f.getCanonicalPath()); | 
|  | } | 
|  |  | 
|  | if (compilerUnderTest == CompilerUnderTest.R8CF) { | 
|  | assert vmSpecs.size() == 1 | 
|  | : "Running the same test on multiple JVMs should share the same build."; | 
|  | for (VmSpec vmSpec : vmSpecs) { | 
|  | runJctfTestDoRunOnJava( | 
|  | fileNames, vmSpec.spec, fullClassName, compilationMode, vmSpec.vm.asCf().getVm()); | 
|  | } | 
|  | return vmErrors; | 
|  | } | 
|  |  | 
|  | CompilationOptions compilationOptions = null; | 
|  | File compiledDir = temp.newFolder(); | 
|  | for (VmSpec vmSpec : vmSpecs) { | 
|  | CompilationOptions thisOptions = new CompilationOptions(vmSpec.spec); | 
|  | if (compilationOptions == null) { | 
|  | compilationOptions = thisOptions; | 
|  | executeCompilerUnderTest( | 
|  | firstCompilerUnderTest, | 
|  | fileNames, | 
|  | compiledDir.getAbsolutePath(), | 
|  | compilationMode, | 
|  | compilationOptions); | 
|  | } else { | 
|  | // For now compile options don't change across vms. | 
|  | assert compilationOptions.equals(thisOptions); | 
|  | } | 
|  | Files.copy( | 
|  | compiledDir.toPath().resolve("classes.dex"), | 
|  | vmSpec.spec.directory.toPath().resolve("classes.dex")); | 
|  |  | 
|  | AssertionError vmError = null; | 
|  | try { | 
|  | runJctfTestDoRunOnArt(fileNames, vmSpec.spec, fullClassName, vmSpec.vm.asDex().getVm()); | 
|  | } catch (AssertionError e) { | 
|  | vmError = e; | 
|  | } | 
|  | if (vmSpec.spec.failsOnRun && vmError == null) { | 
|  | vmErrors.addShouldHaveFailedError(firstCompilerUnderTest, vmSpec.vm); | 
|  | } else if (!vmSpec.spec.failsOnRun && vmError != null) { | 
|  | vmErrors.addFailedOnRunError(firstCompilerUnderTest, vmSpec.vm, vmError); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (compilerUnderTest != CompilerUnderTest.R8_AFTER_D8) { | 
|  | return vmErrors; | 
|  | } | 
|  |  | 
|  | // Second pass (R8), if R8_AFTER_D8. | 
|  | CompilationOptions r8CompilationOptions = null; | 
|  | File r8CompiledDir = temp.newFolder(); | 
|  | for (VmSpec vmSpec : vmSpecs) { | 
|  | if (vmSpec.spec.failsOnRun || vmErrors.failedVms.contains(vmSpec.vm)) { | 
|  | continue; | 
|  | } | 
|  | File r8ResultDir = temp.newFolder("r8-output-" + vmSpec.vm.toString()); | 
|  | TestSpecification specification = | 
|  | JctfTestSpecifications.getExpectedOutcome( | 
|  | name, | 
|  | CompilerUnderTest.R8_AFTER_D8, | 
|  | getRuntime(vmSpec.vm), | 
|  | CompilationMode.RELEASE, | 
|  | jctfOutcomeToSpecification(name, DexTool.DX, r8ResultDir, vmSpec.vm.asDex().getVm())); | 
|  | if (specification.skipTest) { | 
|  | continue; | 
|  | } | 
|  | CompilationOptions thisOptions = new CompilationOptions(specification); | 
|  | if (r8CompilationOptions == null) { | 
|  | r8CompilationOptions = thisOptions; | 
|  | executeCompilerUnderTest( | 
|  | CompilerUnderTest.R8, | 
|  | Collections.singletonList(compiledDir.toPath().resolve("classes.dex").toString()), | 
|  | r8CompiledDir.getAbsolutePath(), | 
|  | CompilationMode.RELEASE, | 
|  | r8CompilationOptions); | 
|  | } else { | 
|  | // For now compile options don't change across vms. | 
|  | assert r8CompilationOptions.equals(thisOptions); | 
|  | } | 
|  | Files.copy( | 
|  | r8CompiledDir.toPath().resolve("classes.dex"), | 
|  | specification.directory.toPath().resolve("classes.dex")); | 
|  | try { | 
|  | runJctfTestDoRunOnArt(fileNames, specification, fullClassName, vmSpec.vm.asDex().getVm()); | 
|  | } catch (AssertionError e) { | 
|  | if (!specification.failsOnRun) { | 
|  | vmErrors.addFailedOnRunError(CompilerUnderTest.R8, vmSpec.vm, e); | 
|  | } | 
|  | } | 
|  | } | 
|  | return vmErrors; | 
|  | } | 
|  |  | 
|  | private void runJctfTestDoRunOnArt( | 
|  | Collection<String> fileNames, | 
|  | TestSpecification specification, | 
|  | String fullClassName, | 
|  | DexVm dexVm) | 
|  | throws IOException { | 
|  | if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | File processedFile; | 
|  |  | 
|  | // Collect the generated dex files. | 
|  | File[] outputFiles = | 
|  | specification.directory.listFiles((File file) -> file.getName().endsWith(".dex")); | 
|  | assert outputFiles.length == 1; | 
|  | processedFile = outputFiles[0]; | 
|  |  | 
|  | boolean compileOnly = System.getProperty("jctf_compile_only", "0").equals("1"); | 
|  | if (compileOnly || specification.skipRun) { | 
|  | if (ToolHelper.isDex2OatSupported()) { | 
|  | // verify dex code instead of running it | 
|  | Path oatFile = temp.getRoot().toPath().resolve("all.oat"); | 
|  | ToolHelper.runDex2Oat(processedFile.toPath(), oatFile); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | ArtCommandBuilder builder = buildArtCommand(processedFile, specification, dexVm); | 
|  | if (dexVm.isNewerThan(DexVm.ART_4_4_4_HOST)) { | 
|  | builder.appendArtOption("-Ximage:/system/non/existent/image.art"); | 
|  | builder.appendArtOption("-Xnoimage-dex2oat"); | 
|  | } | 
|  | for (String s : ToolHelper.getBootLibs(dexVm)) { | 
|  | builder.appendBootClassPath(new File(s).getCanonicalPath()); | 
|  | } | 
|  | builder.setMainClass(JUNIT_TEST_RUNNER); | 
|  | builder.appendProgramArgument(fullClassName); | 
|  |  | 
|  | try { | 
|  | ToolHelper.runArt(builder); | 
|  | } catch (AssertionError e) { | 
|  | addDexInformationToVerificationError(fileNames, processedFile, | 
|  | specification.resolveFile("classes.dex"), e); | 
|  | throw e; | 
|  | } | 
|  | } | 
|  |  | 
|  | private void runJctfTestDoRunOnJava( | 
|  | Collection<String> fileNames, | 
|  | TestSpecification specification, | 
|  | String fullClassName, | 
|  | CompilationMode mode, | 
|  | CfVm vm) | 
|  | throws IOException, CompilationFailedException { | 
|  | assert TestParametersBuilder.isSystemJdk(vm); | 
|  | if (JctfTestSpecifications.compilationFailsWithAsmMethodTooLarge.contains(specification.name)) { | 
|  | expectException(org.objectweb.asm.MethodTooLargeException.class); | 
|  | } | 
|  | executeCompilerUnderTest( | 
|  | CompilerUnderTest.R8CF, | 
|  | fileNames, | 
|  | specification.directory.getAbsolutePath(), | 
|  | mode, | 
|  | new CompilationOptions(specification)); | 
|  |  | 
|  | boolean compileOnly = System.getProperty("jctf_compile_only", "0").equals("1"); | 
|  |  | 
|  | if (compileOnly || specification.skipRun) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (specification.failsOnRun) { | 
|  | expectException(AssertionError.class); | 
|  | } | 
|  |  | 
|  | // Some tests rely on an OutOfMemoryError being thrown (fx MemoryHog.getBigArray()). To ensure | 
|  | // compatible test results locally and externally, we need to synchronize the max heap size when | 
|  | // running the test. | 
|  | ProcessResult result = | 
|  | ToolHelper.runJava( | 
|  | specification.directory.toPath(), | 
|  | "-Xmx" + ToolHelper.BOT_MAX_HEAP_SIZE, | 
|  | JUNIT_TEST_RUNNER, | 
|  | fullClassName); | 
|  |  | 
|  | if (result.exitCode != 0) { | 
|  | throw new AssertionError( | 
|  | "Test failed on java.\nSTDOUT >>>\n" | 
|  | + result.stdout | 
|  | + "\n<<< STDOUT\nSTDERR >>>\n" | 
|  | + result.stderr | 
|  | + "\n<<< STDERR\n"); | 
|  | } | 
|  |  | 
|  | if (specification.failsOnRun) { | 
|  | System.err.println("Should have failed run with java."); | 
|  | } | 
|  | } | 
|  |  | 
|  | protected void runArtTest(DexVm dexVm, CompilerUnderTest compilerUnderTest) throws Throwable { | 
|  | CompilerUnderTest firstCompilerUnderTest = | 
|  | compilerUnderTest == CompilerUnderTest.R8_AFTER_D8 | 
|  | ? CompilerUnderTest.D8 | 
|  | : compilerUnderTest; | 
|  |  | 
|  | CompilationMode compilationMode = defaultCompilationMode(compilerUnderTest); | 
|  |  | 
|  | TestSpecification specification = | 
|  | getTestsMap(firstCompilerUnderTest, compilationMode, dexVm) | 
|  | .get(new SpecificationKey(name, toolchain)); | 
|  |  | 
|  | if (specification == null) { | 
|  | if (dexVm.getVersion() == DexVm.Version.DEFAULT) { | 
|  | throw new RuntimeException("Test " + name + " has no specification for toolchain" | 
|  | + toolchain + "."); | 
|  | } else { | 
|  | // For older VMs the test might not exist, as the tests are currently generates from the | 
|  | // directories present in the art test directory for AOSP master. | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (specification.skipTest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (specification.nativeLibrary != null && dexVm.getKind() == Kind.TARGET) { | 
|  | // JNI tests not yet supported for devices | 
|  | return; | 
|  | } | 
|  |  | 
|  | File[] inputFiles; | 
|  | if (toolchain == DexTool.NONE) { | 
|  | inputFiles = addFileTree(new File[0], new File(specification.directory, "classes")); | 
|  | inputFiles = addFileTree(inputFiles, new File(specification.directory, "jasmin_classes")); | 
|  | File smali = new File(specification.directory, "smali"); | 
|  | if (smali.exists()) { | 
|  | File smaliDex = new File(smali, "out.dex"); | 
|  | assert smaliDex.exists(); | 
|  | inputFiles = ObjectArrays.concat(inputFiles, smaliDex); | 
|  | } | 
|  | inputFiles = addFileTree(inputFiles, new File(specification.directory, "classes2")); | 
|  | inputFiles = addFileTree(inputFiles, new File(specification.directory, "jasmin_classes2")); | 
|  | } else { | 
|  | inputFiles = | 
|  | specification.directory.listFiles((File file) -> | 
|  | file.getName().endsWith(".dex") && !file.getName().startsWith("jasmin")); | 
|  | } | 
|  |  | 
|  | List<String> fileNames = new ArrayList<>(); | 
|  | for (File file : inputFiles) { | 
|  | fileNames.add(file.getCanonicalPath()); | 
|  | } | 
|  |  | 
|  | File resultDir = temp.newFolder(firstCompilerUnderTest.toString().toLowerCase() + "-output"); | 
|  |  | 
|  | runArtTestDoRunOnArt( | 
|  | dexVm, firstCompilerUnderTest, specification, fileNames, resultDir, compilationMode); | 
|  |  | 
|  | if (compilerUnderTest == CompilerUnderTest.R8_AFTER_D8) { | 
|  | if (expectedException) { | 
|  | // The expected exception was not thrown while running D8. | 
|  | return; | 
|  | } | 
|  |  | 
|  | compilationMode = CompilationMode.DEBUG; | 
|  | specification = | 
|  | getTestsMap(CompilerUnderTest.R8_AFTER_D8, compilationMode, dexVm) | 
|  | .get(new SpecificationKey(name, DexTool.DX)); | 
|  |  | 
|  | if (specification == null) { | 
|  | throw new RuntimeException( | 
|  | "Test " + name + " has no specification for toolchain" + toolchain + "."); | 
|  | } | 
|  |  | 
|  | if (specification.skipTest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | fileNames.clear(); | 
|  | for (File file : resultDir.listFiles((File file) -> file.getName().endsWith(".dex"))) { | 
|  | fileNames.add(file.getCanonicalPath()); | 
|  | } | 
|  |  | 
|  | resultDir = temp.newFolder("r8-output"); | 
|  |  | 
|  | runArtTestDoRunOnArt( | 
|  | dexVm, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode); | 
|  | } | 
|  | } | 
|  |  | 
|  | private File[] addFileTree(File[] files, File directory) { | 
|  | if (!directory.exists()) { | 
|  | return files; | 
|  | } | 
|  | return ObjectArrays.concat( | 
|  | files, | 
|  | com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(directory) | 
|  | .filter(f -> !f.isDirectory()) | 
|  | .toArray(File.class), | 
|  | File.class); | 
|  | } | 
|  |  | 
|  | private void runArtTestDoRunOnArt( | 
|  | DexVm version, | 
|  | CompilerUnderTest compilerUnderTest, | 
|  | TestSpecification specification, | 
|  | List<String> fileNames, | 
|  | File resultDir, | 
|  | CompilationMode compilationMode) | 
|  | throws Throwable { | 
|  | if (specification.expectedToFailWithX8) { | 
|  | expectedException = true; | 
|  | try { | 
|  | executeCompilerUnderTest( | 
|  | compilerUnderTest, | 
|  | fileNames, | 
|  | resultDir.getCanonicalPath(), | 
|  | compilationMode, | 
|  | new CompilationOptions(specification)); | 
|  | } catch (CompilationFailedException | CompilationError e) { | 
|  | expectException(CompilationError.class); | 
|  | throw new CompilationError(e.getMessage(), e); | 
|  | } | 
|  | System.err.println("Should have failed R8/D8 compilation with a CompilationError."); | 
|  | return; | 
|  | } else if (specification.failsWithX8) { | 
|  | expectException(Throwable.class); | 
|  | executeCompilerUnderTest( | 
|  | compilerUnderTest, | 
|  | fileNames, | 
|  | resultDir.getCanonicalPath(), | 
|  | compilationMode, | 
|  | new CompilationOptions(specification)); | 
|  | System.err.println("Should have failed R8/D8 compilation with an exception."); | 
|  | return; | 
|  | } else { | 
|  | executeCompilerUnderTest( | 
|  | compilerUnderTest, | 
|  | fileNames, | 
|  | resultDir.getCanonicalPath(), | 
|  | compilationMode, | 
|  | new CompilationOptions(specification)); | 
|  | } | 
|  |  | 
|  | if (!specification.skipRun | 
|  | && (ToolHelper.artSupported() || ToolHelper.dealsWithGoldenFiles())) { | 
|  | File originalFile; | 
|  | File processedFile; | 
|  |  | 
|  | // Collect the generated dex files. | 
|  | File[] outputFiles = | 
|  | resultDir.listFiles((File file) -> file.getName().endsWith(".dex")); | 
|  | if (outputFiles.length == 1) { | 
|  | // Just run Art on classes.dex. | 
|  | processedFile = outputFiles[0]; | 
|  | } else { | 
|  | // Run Art on JAR file with multiple dex files. | 
|  | processedFile | 
|  | = temp.getRoot().toPath().resolve(specification.name + ".jar").toFile(); | 
|  | buildJar(outputFiles, processedFile); | 
|  | } | 
|  |  | 
|  | File expectedFile = specification.resolveFile("expected.txt"); | 
|  | String expected = | 
|  | com.google.common.io.Files.asCharSource(expectedFile, Charsets.UTF_8).read(); | 
|  | if (specification.failsOnRun) { | 
|  | expectException(AssertionError.class); | 
|  | } | 
|  |  | 
|  | ArtCommandBuilder builder = buildArtCommand(processedFile, specification, version); | 
|  | String output; | 
|  | try { | 
|  | output = ToolHelper.runArt(builder); | 
|  | } catch (AssertionError e) { | 
|  | addDexInformationToVerificationError(fileNames, processedFile, | 
|  | specification.resolveFile("classes.dex"), e); | 
|  | throw e; | 
|  | } | 
|  | if (specification.failsOnRun) { | 
|  | System.err.println("Should have failed run with art"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | File checkCommand = specification.resolveFile("check"); | 
|  | if (checkCommand.exists() && !ToolHelper.isWindows()) { | 
|  | // Run the Art test custom check command. | 
|  | File actualFile = temp.newFile(); | 
|  | com.google.common.io.Files.asByteSink(actualFile).write(output.getBytes(Charsets.UTF_8)); | 
|  | ProcessBuilder processBuilder = new ProcessBuilder(); | 
|  | processBuilder.command( | 
|  | specification.resolveFile("check").toString(), expectedFile.toString(), | 
|  | actualFile.toString()); | 
|  | ProcessResult result = ToolHelper.runProcess(processBuilder); | 
|  | if (result.exitCode != 0 && !specification.failsWithArtOutput) { | 
|  | System.err.println("ERROR: check script failed. Building comparison of dex files"); | 
|  | failWithDexDiff(specification.resolveFile("classes.dex"), processedFile); | 
|  | } | 
|  | } else { | 
|  | if (!expected.equals(output)) { | 
|  | // The expected.txt in the Android repository might not match what our version of Art | 
|  | // produces. | 
|  | originalFile = specification.resolveFile(specification.name + ".jar"); | 
|  | if (specification.failsWithArtOriginalOnly) { | 
|  | expectException(AssertionError.class); | 
|  | } | 
|  | builder = buildArtCommand(originalFile, specification, version); | 
|  | expected = ToolHelper.runArt(builder); | 
|  | if (specification.failsWithArtOriginalOnly) { | 
|  | System.err.println("Original file should have failed run with art"); | 
|  | return; | 
|  | } | 
|  | } | 
|  | if (specification.failsWithArtOutput) { | 
|  | expectException(ComparisonFailure.class); | 
|  | } | 
|  | if (!specification.outputMayDiffer) { | 
|  | assertEquals(expected, output); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void expectException(Class<? extends Throwable> exception) { | 
|  | thrown.expect(exception); | 
|  | expectedException = true; | 
|  | } | 
|  |  | 
|  | private void failWithDexDiff(File originalFile, File processedFile) | 
|  | throws IOException, ExecutionException { | 
|  | CodeInspector inspectOriginal = | 
|  | new CodeInspector(originalFile.toPath().toAbsolutePath()); | 
|  | CodeInspector inspectProcessed = | 
|  | new CodeInspector(processedFile.toPath().toAbsolutePath()); | 
|  | StringBuilder builderOriginal = new StringBuilder(); | 
|  | StringBuilder builderProcessed = new StringBuilder(); | 
|  | inspectOriginal.forAllClasses((clazz) -> builderOriginal.append(clazz.dumpMethods())); | 
|  | inspectProcessed.forAllClasses((clazz) -> builderProcessed.append(clazz.dumpMethods())); | 
|  | assertEquals(builderOriginal.toString(), builderProcessed.toString()); | 
|  | fail(); | 
|  | } | 
|  |  | 
|  | private void addDexInformationToVerificationError( | 
|  | Collection<String> inputFiles, File processedFile, File referenceFile, | 
|  | AssertionError verificationError) { | 
|  | List<ComparisonFailure> errors; | 
|  | try { | 
|  | // Parse all the verification errors. | 
|  | CodeInspector processed = new CodeInspector(processedFile.toPath()); | 
|  | CodeInspector original = DEX_COMPARE_WITH_DEX_REFERENCE_ON_FAILURE | 
|  | ? new CodeInspector(referenceFile.toPath()) | 
|  | : new CodeInspector(inputFiles.stream().map(Paths::get).collect(Collectors.toList())); | 
|  | List<ArtErrorInfo> errorInfo = ArtErrorParser.parse(verificationError.getMessage()); | 
|  | errors = ListUtils.map(errorInfo, (error) -> | 
|  | new ComparisonFailure( | 
|  | error.getMessage(), | 
|  | "ORIGINAL\n" + error.dump(original, false) + "\nEND ORIGINAL", | 
|  | "PROCESSED\n" + error.dump(processed, true) + "\nEND PROCESSED")); | 
|  | } catch (Throwable e) { | 
|  | System.err.println("Failed to add extra dex information to the verification error:"); | 
|  | e.printStackTrace(); | 
|  | throw verificationError; | 
|  | } | 
|  |  | 
|  | // If we failed to annotate anything, rethrow the original exception. | 
|  | if (errors.isEmpty()) { | 
|  | throw verificationError; | 
|  | } | 
|  |  | 
|  | // Otherwise, we print each error and throw the last one, since Intellij only supports nice | 
|  | // comparison-diff if thrown and we can only throw one :-( | 
|  | System.err.println(verificationError.getMessage()); | 
|  | for (ComparisonFailure error : errors.subList(0, errors.size() - 1)) { | 
|  | System.err.println(error.toString()); | 
|  | } | 
|  | throw errors.get(errors.size() - 1); | 
|  | } | 
|  |  | 
|  | // TODO(zerny): Refactor tests to output jar files directly and eliminate this method. | 
|  | private static void buildJar(File[] files, File jarFile) throws IOException { | 
|  | try (JarOutputStream target = new JarOutputStream(new FileOutputStream(jarFile))) { | 
|  | for (File file : files) { | 
|  | // Only use the file name in the JAR entry (classes.dex, classes2.dex, ...) | 
|  | JarEntry entry = new JarEntry(file.getName()); | 
|  | entry.setTime(file.lastModified()); | 
|  | target.putNextEntry(entry); | 
|  | Files.copy(file.toPath(), target); | 
|  | target.closeEntry(); | 
|  | } | 
|  | } | 
|  | } | 
|  | } |