| // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| package com.android.tools.r8; | |
| import static com.google.common.base.Predicates.alwaysFalse; | |
| import com.android.tools.r8.TestRuntime.CfRuntime; | |
| import com.android.tools.r8.TestRuntime.CfVm; | |
| import com.android.tools.r8.TestRuntime.CollapsedDexRuntimes; | |
| import com.android.tools.r8.TestRuntime.DexRuntime; | |
| import com.android.tools.r8.TestRuntime.NoneRuntime; | |
| import com.android.tools.r8.ToolHelper.DexVm; | |
| import com.android.tools.r8.utils.AndroidApiLevel; | |
| import com.android.tools.r8.utils.internal.BooleanBox; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.LinkedHashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.TreeSet; | |
| import java.util.function.BiPredicate; | |
| import java.util.function.Function; | |
| import java.util.function.Predicate; | |
| import java.util.stream.Collectors; | |
| import java.util.stream.Stream; | |
| public class TestParametersBuilder { | |
| // Predicate describing which test parameters are applicable to the test. | |
| // Built via the methods found below. Defaults to no applicable parameters, i.e., the emtpy set. | |
| private Predicate<TestParameters> filter = alwaysFalse(); | |
| private Predicate<TestParameters> partialFilter = alwaysFalse(); | |
| private boolean hasDexRuntimeFilter = false; | |
| private boolean allowCollapsedDexRuntimes = true; | |
| TestParametersBuilder() {} | |
| private TestParametersBuilder withFilter(Predicate<TestParameters> predicate) { | |
| filter = filter.or(predicate); | |
| return this; | |
| } | |
| private TestParametersBuilder withCfRuntimeFilter(Predicate<CfVm> predicate) { | |
| return withFilter(p -> p.isCfRuntime() && predicate.test(p.getRuntime().asCf().getVm())); | |
| } | |
| private TestParametersBuilder withDexRuntimeFilter(Predicate<DexVm.Version> predicate) { | |
| hasDexRuntimeFilter = true; | |
| return withFilter( | |
| p -> | |
| p.isDexRuntime() | |
| && p.getDexRuntimeVersion().isOlderThan(DexVm.Version.MASTER) | |
| && predicate.test(p.getDexRuntimeVersion())); | |
| } | |
| private TestParametersBuilder withPartialCompilationFilter( | |
| Predicate<PartialCompilationTestParameters> predicate) { | |
| enablePartialCompilation = true; | |
| partialFilter = partialFilter.or(p -> predicate.test(p.getPartialCompilationTestParameters())); | |
| return this; | |
| } | |
| public TestParametersBuilder withNoneRuntime() { | |
| return withFilter(p -> p.getRuntime() == NoneRuntime.getInstance()); | |
| } | |
| public TestParametersBuilder withAllRuntimes() { | |
| return withCfRuntimes().withDexRuntimes(); | |
| } | |
| public TestParametersBuilder withAllRuntimesAndApiLevels() { | |
| return withCfRuntimes().withDexRuntimes().withAllApiLevels(); | |
| } | |
| /** Add specific runtime if available. */ | |
| public TestParametersBuilder withCfRuntime(CfVm runtime) { | |
| return withCfRuntimeFilter(vm -> vm == runtime); | |
| } | |
| /** | |
| * Test using the same runtime as the test is executing under. | |
| * | |
| * <p>This should only be used when there is an explicit dependency in the test that requires the | |
| * host and test to be the same. For example, it could be to fork a subprocess using the same | |
| * runtime. | |
| */ | |
| public TestParametersBuilder withSystemRuntime() { | |
| return withCfRuntimeFilter(TestParametersBuilder::isSystemJdk); | |
| } | |
| /** | |
| * Test using the default DEX VM. | |
| * | |
| * <p>Generally tests should rather use withDexRuntimes(), but if a test really only needs to be | |
| * tested on a single DEX runtime this can be used instead. The test should not have any | |
| * requirements as to which VM it is as the default will change and may not track latest. | |
| */ | |
| public TestParametersBuilder withDefaultDexRuntime() { | |
| return withDexRuntime(DexRuntime.getDefaultDexRuntime().getVersion()); | |
| } | |
| /** | |
| * Test using the default CF VM. | |
| * | |
| * <p>Generally tests should rather use withCfRuntimes(), but if a test really only needs to be | |
| * tested on a single CF runtime this can be used instead. The test should not have any | |
| * requirements as to which VM it is as the default will change and may not track latest. | |
| */ | |
| public TestParametersBuilder withDefaultCfRuntime() { | |
| return withCfRuntime(CfRuntime.getDefaultCfRuntime().getVm()); | |
| } | |
| public TestParametersBuilder withDefaultRuntimes() { | |
| return withDefaultDexRuntime().withDefaultCfRuntime(); | |
| } | |
| /** Add all available CF runtimes. */ | |
| public TestParametersBuilder withCfRuntimes() { | |
| return withCfRuntimeFilter(vm -> true); | |
| } | |
| /** Add all available CF runtimes between {@param startInclusive} and {@param endInclusive}. */ | |
| public TestParametersBuilder withCfRuntimes(CfVm startInclusive, CfVm endInclusive) { | |
| return withCfRuntimeFilter( | |
| vm -> startInclusive.lessThanOrEqual(vm) && vm.lessThanOrEqual(endInclusive)); | |
| } | |
| /** Add all available CF runtimes starting from and including {@param startInclusive}. */ | |
| public TestParametersBuilder withCfRuntimesStartingFromIncluding(CfVm startInclusive) { | |
| return withCfRuntimeFilter(startInclusive::lessThanOrEqual); | |
| } | |
| /** Add all available CF runtimes ending at and excluding {@param endExclusive}. */ | |
| public TestParametersBuilder withCfRuntimesEndingAtExcluding(CfVm endExclusive) { | |
| return withCfRuntimeFilter(vm -> vm.lessThan(endExclusive)); | |
| } | |
| /** Add all available DEX runtimes except master. */ | |
| public TestParametersBuilder withDexRuntimes() { | |
| return withDexRuntimeFilter(vm -> true); | |
| } | |
| public TestParametersBuilder withDexRuntimesAndAllApiLevels() { | |
| return withDexRuntimes().withAllApiLevels(); | |
| } | |
| /** Add specific runtime if available. */ | |
| public TestParametersBuilder withDexRuntime(DexVm.Version runtime) { | |
| return withDexRuntimeFilter(vm -> vm == runtime); | |
| } | |
| /** Add all available DEX runtimes that support native multidex. */ | |
| public TestParametersBuilder withNativeMultidexDexRuntimes() { | |
| return withDexRuntimesStartingFromIncluding(DexVm.Version.V5_1_1); | |
| } | |
| /** Add all available DEX runtimes starting from and including {@param startInclusive}. */ | |
| public TestParametersBuilder withDexRuntimesStartingFromIncluding(DexVm.Version startInclusive) { | |
| return withDexRuntimeFilter(startInclusive::isOlderThanOrEqual); | |
| } | |
| /** Add all available DEX runtimes starting from and excluding {@param startExcluding}. */ | |
| public TestParametersBuilder withDexRuntimesStartingFromExcluding(DexVm.Version startExcluding) { | |
| return withDexRuntimeFilter( | |
| vm -> vm != startExcluding && startExcluding.isOlderThanOrEqual(vm)); | |
| } | |
| /** Add all available DEX runtimes ending at and excluding {@param startExcluding}. */ | |
| public TestParametersBuilder withDexRuntimesEndingAtExcluding(DexVm.Version endExcluding) { | |
| return withDexRuntimeFilter(vm -> vm != endExcluding && vm.isOlderThanOrEqual(endExcluding)); | |
| } | |
| /** | |
| * Add all available DEX runtimes starting from {@param startInclusive} and ending at {@param | |
| * endInclusive}. Both inclusive. | |
| */ | |
| public TestParametersBuilder withDexRuntimesRangeIncluding( | |
| DexVm.Version startInclusive, DexVm.Version endInclusive) { | |
| return withDexRuntimeFilter( | |
| vm -> vm.isNewerThanOrEqual(startInclusive) && vm.isOlderThanOrEqual(endInclusive)); | |
| } | |
| public TestParametersBuilder withPartialCompilation() { | |
| return withIncludeAllPartialCompilation() | |
| .withExcludeAllPartialCompilation() | |
| .withRandomPartialCompilation(); | |
| } | |
| public TestParametersBuilder withIncludeAllPartialCompilation() { | |
| return withPartialCompilationFilter(PartialCompilationTestParameters::isIncludeAll); | |
| } | |
| public TestParametersBuilder withExcludeAllPartialCompilation() { | |
| return withPartialCompilationFilter(PartialCompilationTestParameters::isExcludeAll); | |
| } | |
| public TestParametersBuilder withRandomPartialCompilation() { | |
| return withPartialCompilationFilter(PartialCompilationTestParameters::isRandom); | |
| } | |
| /** | |
| * API level configuration. | |
| * | |
| * <p>Currently enabling API level config will by default configure each DEX VM to be configured | |
| * with two parameters, one running at the highest api-level supported by the VM and one at the | |
| * lowest supported by the compiler (i.e., B). | |
| */ | |
| private static final AndroidApiLevel lowestCompilerApiLevel = AndroidApiLevel.B; | |
| private boolean enableApiLevels = false; | |
| private boolean enableApiLevelsForCf = false; | |
| private boolean enablePartialCompilation = false; | |
| private BiPredicate<AndroidApiLevel, TestRuntime> apiLevelFilter = (api, runtime) -> false; | |
| private final List<AndroidApiLevel> explicitApiLevels = new ArrayList<>(); | |
| private TestParametersBuilder withApiFilter(BiPredicate<AndroidApiLevel, TestRuntime> filter) { | |
| enableApiLevels = true; | |
| apiLevelFilter = apiLevelFilter.or(filter); | |
| return this; | |
| } | |
| public TestParametersBuilder withAllApiLevels() { | |
| return withApiFilter((api, runtime) -> true); | |
| } | |
| public TestParametersBuilder withMinimumApiLevel() { | |
| return withApiFilter((api, runtime) -> api.equals(lowestCompilerApiLevel)); | |
| } | |
| public TestParametersBuilder withMaximumApiLevel() { | |
| return withApiFilter((api, runtime) -> api.equals(runtime.maxSupportedApiLevel())); | |
| } | |
| public TestParametersBuilder enableApiLevelsForCf() { | |
| enableApiLevelsForCf = true; | |
| return this; | |
| } | |
| public TestParametersBuilder withAllApiLevelsAlsoForCf() { | |
| return withAllApiLevels().enableApiLevelsForCf(); | |
| } | |
| public TestParametersBuilder withApiLevel(AndroidApiLevel api) { | |
| explicitApiLevels.add(api); | |
| return withApiFilter((otherApi, runtime) -> api.equals(otherApi)); | |
| } | |
| public TestParametersBuilder withApiLevelsStartingAtIncluding(AndroidApiLevel startInclusive) { | |
| return withApiFilter((api, runtime) -> startInclusive.isLessThanOrEqualTo(api)); | |
| } | |
| public TestParametersBuilder withApiLevelsEndingAtIncluding(AndroidApiLevel endInclusive) { | |
| return withApiFilter((api, runtime) -> api.isLessThanOrEqualTo(endInclusive)); | |
| } | |
| public TestParametersBuilder withApiLevelsEndingAtExcluding(AndroidApiLevel endExclusive) { | |
| return withApiFilter((api, runtime) -> api.isLessThan(endExclusive)); | |
| } | |
| public TestParametersBuilder apiLevelWithDefaultMethodsSupport() { | |
| return withApiLevelsStartingAtIncluding(TestBase.apiLevelWithDefaultInterfaceMethodsSupport()); | |
| } | |
| public TestParametersBuilder withCollapsedDexRuntimes(boolean allowCollapsedDexRuntimes) { | |
| this.allowCollapsedDexRuntimes = allowCollapsedDexRuntimes; | |
| return this; | |
| } | |
| public TestParametersBuilder withoutCollapsedDexRuntimes() { | |
| return withCollapsedDexRuntimes(false); | |
| } | |
| public TestParametersCollection build() { | |
| assert !enableApiLevels || enableApiLevelsForCf || hasDexRuntimeFilter; | |
| List<TestParameters> availableParameters = | |
| getAvailableRuntimes() | |
| .flatMap(this::createTestParameters) | |
| .filter(filter) | |
| .collect(Collectors.toList()); | |
| if (enableApiLevels && allowCollapsedDexRuntimes) { | |
| availableParameters = collapseDexRuntimes(availableParameters); | |
| } | |
| return new TestParametersCollection(availableParameters); | |
| } | |
| private List<TestParameters> collapseDexRuntimes(List<TestParameters> parameters) { | |
| Map<AndroidApiLevel, Map<PartialCompilationTestParameters, List<TestParameters>>> groups = | |
| new LinkedHashMap<>(); | |
| for (TestParameters parameter : parameters) { | |
| if (parameter.isDexRuntime() && parameter.hasApiLevel()) { | |
| groups | |
| .computeIfAbsent(parameter.getApiLevel(), k -> new LinkedHashMap<>()) | |
| .computeIfAbsent( | |
| parameter.getPartialCompilationTestParameters(), k -> new ArrayList<>()) | |
| .add(parameter); | |
| } | |
| } | |
| List<TestParameters> result = new ArrayList<>(); | |
| for (TestParameters parameter : parameters) { | |
| if (parameter.isDexRuntime() && parameter.hasApiLevel()) { | |
| List<TestParameters> group = | |
| groups | |
| .get(parameter.getApiLevel()) | |
| .remove(parameter.getPartialCompilationTestParameters()); | |
| if (group != null) { | |
| if (group.size() == 1) { | |
| result.add(group.get(0)); | |
| } else { | |
| List<DexRuntime> dexRuntimes = | |
| group.stream().map(TestParameters::asDexRuntime).collect(Collectors.toList()); | |
| result.add( | |
| new TestParameters( | |
| new CollapsedDexRuntimes(dexRuntimes), | |
| parameter.getApiLevel(), | |
| parameter.getPartialCompilationTestParameters())); | |
| } | |
| } | |
| } else { | |
| result.add(parameter); | |
| } | |
| } | |
| return result; | |
| } | |
| private Stream<TestParameters> createTestParameters(TestRuntime runtime) { | |
| Stream<TestParameters> parameters = createBasicTestParametersForRuntime(runtime); | |
| if (enablePartialCompilation) { | |
| parameters = createPartialCompilationTestParameters(parameters); | |
| } | |
| return parameters; | |
| } | |
| private Stream<TestParameters> createPartialCompilationTestParameters( | |
| Stream<TestParameters> parameters) { | |
| return parameters.flatMap(this::createPartialCompilationTestParameters); | |
| } | |
| private Stream<TestParameters> createPartialCompilationTestParameters(TestParameters parameters) { | |
| if (!parameters.canUseR8Partial()) { | |
| return Stream.of(parameters); | |
| } | |
| assert parameters.getPartialCompilationTestParameters().isNone(); | |
| Stream.Builder<TestParameters> builder = Stream.builder(); | |
| builder.add(parameters); | |
| for (PartialCompilationTestParameters partialCompilationTestParameters : | |
| PartialCompilationTestParameters.values()) { | |
| TestParameters newParameters = | |
| parameters.withPartialCompilationTestParameters(partialCompilationTestParameters); | |
| if (partialFilter.test(newParameters)) { | |
| builder.add(newParameters); | |
| } | |
| } | |
| return builder.build(); | |
| } | |
| private Stream<TestParameters> createBasicTestParametersForRuntime(TestRuntime runtime) { | |
| if (!enableApiLevels) { | |
| return Stream.of(new TestParameters(runtime)); | |
| } | |
| if (runtime.isCf() && !enableApiLevelsForCf) { | |
| return Stream.of(new TestParameters(runtime)); | |
| } | |
| AndroidApiLevel vmLevel = runtime.maxSupportedApiLevel(); | |
| List<AndroidApiLevel> sortedApiLevels = | |
| AndroidApiLevel.getAndroidApiLevelsSorted().stream() | |
| .filter(apiLevel -> apiLevelFilter.test(apiLevel, runtime)) | |
| .collect(Collectors.toList()); | |
| if (sortedApiLevels.isEmpty()) { | |
| return Stream.of(); | |
| } | |
| AndroidApiLevel lowestApplicable = sortedApiLevels.get(0); | |
| if (vmLevel.isLessThan(lowestApplicable)) { | |
| return Stream.of(); | |
| } | |
| if (sortedApiLevels.size() > 1) { | |
| for (int i = sortedApiLevels.size() - 1; i >= 0; i--) { | |
| AndroidApiLevel highestApplicable = sortedApiLevels.get(i); | |
| if (highestApplicable.isLessThanOrEqualTo(vmLevel) | |
| && lowestApplicable != highestApplicable) { | |
| Set<AndroidApiLevel> set = new TreeSet<>(); | |
| set.add(lowestApplicable); | |
| set.add(highestApplicable); | |
| for (AndroidApiLevel explicitApiLevel : explicitApiLevels) { | |
| if (explicitApiLevel.isLessThanOrEqualTo(vmLevel)) { | |
| set.add(explicitApiLevel); | |
| } | |
| } | |
| BooleanBox representativeApiLevelForRuntime = new BooleanBox(true); | |
| return set.stream() | |
| .map( | |
| api -> { | |
| TestParameters parameters = | |
| new TestParameters( | |
| runtime, | |
| api, | |
| PartialCompilationTestParameters.NONE, | |
| representativeApiLevelForRuntime.get()); | |
| representativeApiLevelForRuntime.unset(); | |
| return parameters; | |
| }); | |
| } | |
| } | |
| } | |
| return Stream.of( | |
| new TestParameters(runtime, lowestApplicable, PartialCompilationTestParameters.NONE, true)); | |
| } | |
| // Public method to check that the CF runtime coincides with the system runtime. | |
| public static boolean isSystemJdk(CfVm vm) { | |
| TestRuntime systemRuntime = TestRuntime.getSystemRuntime(); | |
| return systemRuntime.isCf() && systemRuntime.asCf().getVm().equals(vm); | |
| } | |
| public static boolean isRuntimesPropertySet() { | |
| return getRuntimesProperty() != null; | |
| } | |
| public static String getRuntimesProperty() { | |
| return System.getProperty("runtimes"); | |
| } | |
| /** Filters the items based on the build-configured runtime filter. */ | |
| public static <T> List<T> filterByDexVmVersion( | |
| List<T> items, Function<T, DexVm.Version> versionExtractor) { | |
| Set<DexVm.Version> allowedVersions = | |
| getAvailableRuntimes() | |
| .filter(TestRuntime::isDex) | |
| .map(r -> r.asDex().getVersion()) | |
| .collect(Collectors.toSet()); | |
| return items.stream() | |
| .filter(item -> allowedVersions.contains(versionExtractor.apply(item))) | |
| .collect(Collectors.toList()); | |
| } | |
| private static Stream<TestRuntime> getUnfilteredAvailableRuntimes() { | |
| // The runtimes are built in a linked hash map to ensure a deterministic order and avoid | |
| // duplicates. | |
| LinkedHashMap<TestRuntime, TestRuntime> runtimes = new LinkedHashMap<>(); | |
| // Place the none-runtime first. | |
| NoneRuntime noneRuntime = NoneRuntime.getInstance(); | |
| runtimes.putIfAbsent(noneRuntime, noneRuntime); | |
| // Then the checked in runtimes (CF and DEX). | |
| for (TestRuntime checkedInRuntime : TestRuntime.getCheckedInRuntimes()) { | |
| runtimes.putIfAbsent(checkedInRuntime, checkedInRuntime); | |
| } | |
| // Then finally the system runtime. It will likely be the same as a checked in and adding it | |
| // makes the overall order more stable. | |
| TestRuntime systemRuntime = TestRuntime.getSystemRuntime(); | |
| runtimes.putIfAbsent(systemRuntime, systemRuntime); | |
| return runtimes.values().stream(); | |
| } | |
| private static Stream<TestRuntime> getAvailableRuntimes() { | |
| if (isRuntimesPropertySet()) { | |
| String[] runtimeFilters = getRuntimesProperty().split(":"); | |
| return getUnfilteredAvailableRuntimes() | |
| .filter( | |
| runtime -> | |
| Arrays.stream(runtimeFilters).anyMatch(filter -> runtime.name().equals(filter))); | |
| } | |
| return getUnfilteredAvailableRuntimes(); | |
| } | |
| } |