blob: 375f6f8524e18bd4f73d6627774656cfc51864f5 [file] [log] [blame]
// 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 com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.TestRuntime.CfVm;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
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 = param -> false;
private boolean hasDexRuntimeFilter = false;
private TestParametersBuilder() {}
public static TestParametersBuilder builder() {
return new 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() && predicate.test(p.getRuntime().asDex().getVm().getVersion()));
}
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());
}
/** 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 starting from and excluding {@param startExcluding}. */
public TestParametersBuilder withCfRuntimesStartingFromExcluding(CfVm startExcluding) {
return withCfRuntimeFilter(startExcluding::lessThan);
}
/** Add all available CF runtimes ending at and including {@param endInclusive}. */
public TestParametersBuilder withCfRuntimesEndingAtIncluding(CfVm endInclusive) {
return withCfRuntimeFilter(vm -> vm.lessThanOrEqual(endInclusive));
}
/** 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. */
public TestParametersBuilder withDexRuntimes() {
return withDexRuntimeFilter(vm -> true);
}
/** Add specific runtime if available. */
public TestParametersBuilder withDexRuntime(DexVm.Version runtime) {
return withDexRuntimeFilter(vm -> vm == runtime);
}
/** Add all available CF runtimes between {@param startInclusive} and {@param endInclusive}. */
public TestParametersBuilder withDexRuntimes(
DexVm.Version startInclusive, DexVm.Version endInclusive) {
return withDexRuntimeFilter(
vm -> startInclusive.isOlderThanOrEqual(vm) && vm.isOlderThanOrEqual(endInclusive));
}
/** Add all available DEX runtimes that support native multidex. */
public TestParametersBuilder withNativeMultidexDexRuntimes() {
return withDexRuntimesStartingFromIncluding(DexVm.Version.V5_1_1);
}
/** Add all available DEX runtimes that do not support native multidex. */
public TestParametersBuilder withMainDexRuntimes() {
return withDexRuntimesEndingAtExcluding(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 including {@param endInclusive}. */
public TestParametersBuilder withDexRuntimesEndingAtIncluding(DexVm.Version endInclusive) {
return withDexRuntimeFilter(vm -> vm.isOlderThanOrEqual(endInclusive));
}
/** Add all available DEX runtimes ending at and excluding {@param endExclusive}. */
public TestParametersBuilder withDexRuntimesEndingAtExcluding(DexVm.Version endExclusive) {
return withDexRuntimeFilter(vm -> vm != endExclusive && vm.isOlderThanOrEqual(endExclusive));
}
/**
* 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 Predicate<AndroidApiLevel> apiLevelFilter = param -> false;
private List<AndroidApiLevel> explicitApiLevels = new ArrayList<>();
private List<TestRuntime> customRuntimes = new ArrayList<>();
private TestParametersBuilder withApiFilter(Predicate<AndroidApiLevel> filter) {
enableApiLevels = true;
apiLevelFilter = apiLevelFilter.or(filter);
return this;
}
public TestParametersBuilder withAllApiLevels() {
return withApiFilter(api -> true);
}
public TestParametersBuilder enableApiLevelsForCf() {
enableApiLevelsForCf = true;
return this;
}
public TestParametersBuilder withAllApiLevelsAlsoForCf() {
return withAllApiLevels().enableApiLevelsForCf();
}
public TestParametersBuilder withApiLevel(AndroidApiLevel api) {
explicitApiLevels.add(api);
return withApiFilter(api::equals);
}
public TestParametersBuilder withApiLevelsStartingAtIncluding(AndroidApiLevel startInclusive) {
return withApiFilter(api -> startInclusive.getLevel() <= api.getLevel());
}
public TestParametersBuilder withApiLevelsStartingAtExcluding(AndroidApiLevel startExclusive) {
return withApiFilter(api -> startExclusive.getLevel() < api.getLevel());
}
public TestParametersBuilder withApiLevelsEndingAtIncluding(AndroidApiLevel endInclusive) {
return withApiFilter(api -> api.getLevel() <= endInclusive.getLevel());
}
public TestParametersBuilder withApiLevelsEndingAtExcluding(AndroidApiLevel endExclusive) {
return withApiFilter(api -> api.getLevel() < endExclusive.getLevel());
}
public TestParametersBuilder withApiLevelsWithoutNativeMultiDex() {
return withApiLevelsEndingAtExcluding(AndroidApiLevel.L);
}
public TestParametersBuilder withCustomRuntime(TestRuntime runtime) {
assert getUnfilteredAvailableRuntimes().noneMatch(r -> r == runtime);
customRuntimes.add(runtime);
return this;
}
public TestParametersCollection build() {
assert !enableApiLevels || enableApiLevelsForCf || hasDexRuntimeFilter;
List<TestParameters> availableParameters =
getAvailableRuntimes()
.flatMap(this::createParameters)
.filter(filter)
.collect(Collectors.toList());
List<TestParameters> customParameters =
customRuntimes.stream().flatMap(this::createParameters).collect(Collectors.toList());
availableParameters.addAll(customParameters);
return new TestParametersCollection(availableParameters);
}
public Stream<TestParameters> createParameters(TestRuntime runtime) {
if (!enableApiLevels) {
return Stream.of(new TestParameters(runtime));
}
if (runtime.isCf() && !enableApiLevelsForCf) {
return Stream.of(new TestParameters(runtime));
}
List<AndroidApiLevel> sortedApiLevels =
AndroidApiLevel.getAndroidApiLevelsSorted().stream()
.filter(apiLevelFilter)
.collect(Collectors.toList());
if (sortedApiLevels.isEmpty()) {
return Stream.of();
}
AndroidApiLevel vmLevel = runtime.maxSupportedApiLevel();
AndroidApiLevel lowestApplicable = sortedApiLevels.get(0);
if (vmLevel.getLevel() < lowestApplicable.getLevel()) {
return Stream.of();
}
if (sortedApiLevels.size() > 1) {
for (int i = sortedApiLevels.size() - 1; i >= 0; i--) {
AndroidApiLevel highestApplicable = sortedApiLevels.get(i);
if (highestApplicable.getLevel() <= vmLevel.getLevel()
&& lowestApplicable != highestApplicable) {
Set<AndroidApiLevel> set = new TreeSet<>();
set.add(lowestApplicable);
set.add(highestApplicable);
for (AndroidApiLevel explicitApiLevel : explicitApiLevels) {
if (explicitApiLevel.getLevel() <= vmLevel.getLevel()) {
set.add(explicitApiLevel);
}
}
return set.stream().map(api -> new TestParameters(runtime, api));
}
}
}
return Stream.of(new TestParameters(runtime, lowestApplicable));
}
// 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");
}
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();
}
public static List<CfVm> getAvailableCfVms() {
return getAvailableRuntimes()
.filter(TestRuntime::isCf)
.map(runtime -> runtime.asCf().getVm())
.collect(Collectors.toList());
}
public static List<DexVm> getAvailableDexVms() {
return getAvailableRuntimes()
.filter(TestRuntime::isDex)
.map(runtime -> runtime.asDex().getVm())
.collect(Collectors.toList());
}
}