|  | // 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.CfVm; | 
|  | import com.android.tools.r8.TestRuntime.NoneRuntime; | 
|  | import com.android.tools.r8.ToolHelper.DexVm; | 
|  | import com.android.tools.r8.utils.AndroidApiLevel; | 
|  | import java.util.Arrays; | 
|  | import java.util.LinkedHashMap; | 
|  | import java.util.List; | 
|  | import java.util.function.Predicate; | 
|  | import java.util.stream.Collectors; | 
|  | import java.util.stream.Stream; | 
|  |  | 
|  | public class TestParametersBuilder { | 
|  |  | 
|  | // Static computation of VMs configured as available by the testing invocation. | 
|  | private static final List<TestRuntime> availableRuntimes = | 
|  | getAvailableRuntimes().collect(Collectors.toList()); | 
|  |  | 
|  | // 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); | 
|  | } | 
|  |  | 
|  | /** 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(vm -> startInclusive.lessThanOrEqual(vm)); | 
|  | } | 
|  |  | 
|  | /** Add all available CF runtimes starting from and excluding {@param startExcluding}. */ | 
|  | public TestParametersBuilder withCfRuntimesStartingFromExcluding(CfVm startExcluding) { | 
|  | return withCfRuntimeFilter(vm -> startExcluding.lessThan(vm)); | 
|  | } | 
|  |  | 
|  | /** 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 starting from and including {@param startInclusive}. */ | 
|  | public TestParametersBuilder withDexRuntimesStartingFromIncluding(DexVm.Version startInclusive) { | 
|  | return withDexRuntimeFilter(vm -> startInclusive.isOlderThanOrEqual(vm)); | 
|  | } | 
|  |  | 
|  | /** 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 Predicate<AndroidApiLevel> apiLevelFilter = param -> false; | 
|  |  | 
|  | private TestParametersBuilder withApiFilter(Predicate<AndroidApiLevel> filter) { | 
|  | enableApiLevels = true; | 
|  | apiLevelFilter = apiLevelFilter.or(filter); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public TestParametersBuilder withAllApiLevels() { | 
|  | return withApiFilter(api -> true); | 
|  | } | 
|  |  | 
|  | 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 TestParametersCollection build() { | 
|  | assert !enableApiLevels || hasDexRuntimeFilter; | 
|  | return new TestParametersCollection( | 
|  | getAvailableRuntimes() | 
|  | .flatMap(this::createParameters) | 
|  | .filter(filter) | 
|  | .collect(Collectors.toList())); | 
|  | } | 
|  |  | 
|  | public Stream<TestParameters> createParameters(TestRuntime runtime) { | 
|  | if (!enableApiLevels || !runtime.isDex()) { | 
|  | 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.asDex().getMinApiLevel(); | 
|  | 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) { | 
|  | return Stream.of( | 
|  | new TestParameters(runtime, lowestApplicable), | 
|  | new TestParameters(runtime, highestApplicable)); | 
|  | } | 
|  | } | 
|  | } | 
|  | 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()); | 
|  | } | 
|  | } |