| // 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))) |
| .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", |
| |
| // Fails due to non-matching Exception messages. |
| "201-built-in-except-detail-messages", |
| |
| // 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", |
| |
| // Fails due to non-matching Exception messages. |
| "201-built-in-except-detail-messages", |
| |
| // 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", |
| |
| // Fails due to non-matching Exception messages. |
| "201-built-in-except-detail-messages", |
| |
| // 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", |
| |
| // Fails due to non-matching Exception messages. |
| "201-built-in-except-detail-messages", |
| |
| // 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", |
| |
| // Fails due to non-matching Exception messages. |
| "201-built-in-except-detail-messages", |
| |
| // 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.D8_COMPILER, |
| 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()) |
| // 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("594-invoke-super", TestCondition.match(TestCondition.R8DEX_COMPILER)) |
| .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 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)) |
| // 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()) |
| // 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> requireUninstantiatedTypeOptimizationToBeDisabled = ImmutableList.of( |
| // This test inspects the message of the exception that is thrown when calling a virtual |
| // method with a null-receiver. This message changes when the invocation is rewritten to |
| // "throw null". |
| "201-built-in-except-detail-messages" |
| ); |
| |
| 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. |
| "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; |
| // Whether to disable the uninitialized type optimization. |
| private final boolean disableUninstantiatedTypeOptimization; |
| // 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 disableUninstantiatedTypeOptimization, |
| 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.disableUninstantiatedTypeOptimization = disableUninstantiatedTypeOptimization; |
| 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, |
| 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, |
| 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), |
| requireUninstantiatedTypeOptimizationToBeDisabled.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; |
| } |
| |
| protected void runArtTest(CompilerUnderTest compilerUnderTest) throws Throwable { |
| // Use the default dex VM specified. |
| runArtTest(ToolHelper.getDexVm(), compilerUnderTest); |
| } |
| |
| private static class CompilationOptions implements Consumer<InternalOptions> { |
| |
| private final boolean disableInlining; |
| private final boolean disableClassInlining; |
| private final boolean disableUninstantiatedTypeOptimization; |
| 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.disableUninstantiatedTypeOptimization = spec.disableUninstantiatedTypeOptimization; |
| 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; |
| } |
| if (disableUninstantiatedTypeOptimization) { |
| options.enableUninstantiatedTypeOptimization = 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 |
| && disableUninstantiatedTypeOptimization == options.disableUninstantiatedTypeOptimization |
| && hasMissingClasses == options.hasMissingClasses; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash( |
| disableInlining, |
| disableClassInlining, |
| disableUninstantiatedTypeOptimization, |
| 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())); |
| } |
| D8.run(builder.build()); |
| 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) { |
| expectException(CompilationError.class); |
| try { |
| executeCompilerUnderTest( |
| compilerUnderTest, |
| fileNames, |
| resultDir.getCanonicalPath(), |
| compilationMode, |
| new CompilationOptions(specification)); |
| } catch (CompilationFailedException e) { |
| 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(); |
| } |
| } |
| } |
| } |