1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.platform.test.flag.junit;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.TreeMap;
26 
27 import javax.annotation.Nonnull;
28 
29 /** An object which holds aconfig flags values, and can be used for parameterized testing. */
30 public final class FlagsParameterization {
31     public final Map<String, Boolean> mOverrides;
32 
33     /** Construct a values wrapper class */
FlagsParameterization(Map<String, Boolean> overrides)34     public FlagsParameterization(Map<String, Boolean> overrides) {
35         mOverrides = Map.copyOf(overrides);
36     }
37 
38     @Override
toString()39     public String toString() {
40         if (mOverrides.isEmpty()) {
41             return "EMPTY";
42         }
43         StringBuilder sb = new StringBuilder();
44         for (Map.Entry<String, Boolean> entry : new TreeMap<>(mOverrides).entrySet()) {
45             if (sb.length() != 0) {
46                 sb.append(',');
47             }
48             sb.append(entry.getKey()).append('=').append(entry.getValue());
49         }
50         return sb.toString();
51     }
52 
53     /**
54      * Determines whether the dependency <code>alpha dependsOn beta</code> is met for the defined
55      * values.
56      *
57      * @param alpha a flag which must be defined in this object
58      * @param beta a flag which must be defined in this object
59      * @return true in all cases except when alpha is enabled but beta is disabled.
60      */
isDependencyMet(String alpha, String beta)61     public boolean isDependencyMet(String alpha, String beta) {
62         boolean alphaEnabled = requireNonNull(mOverrides.get(alpha), alpha + " is not defined");
63         boolean betaEnabled = requireNonNull(mOverrides.get(beta), beta + " is not defined");
64         return betaEnabled || !alphaEnabled;
65     }
66 
67     @Override
equals(Object other)68     public boolean equals(Object other) {
69         if (other == null) return false;
70         if (other == this) return true;
71         if (!(other instanceof FlagsParameterization)) return false;
72         return mOverrides.equals(((FlagsParameterization) other).mOverrides);
73     }
74 
75     @Override
hashCode()76     public int hashCode() {
77         return mOverrides.hashCode();
78     }
79 
80     /**
81      * Produces a list containing every combination of boolean values for the given flags.
82      *
83      * @return a list of size 2^N for N provided flags.
84      */
85     @Nonnull
allCombinationsOf(@onnull String... flagNames)86     public static List<FlagsParameterization> allCombinationsOf(@Nonnull String... flagNames) {
87         List<Map<String, Boolean>> currentList = List.of(new HashMap<>());
88         for (String flagName : flagNames) {
89             List<Map<String, Boolean>> next = new ArrayList<>(currentList.size() * 2);
90             for (Map<String, Boolean> current : currentList) {
91                 // copy the current map and add this flag as disabled
92                 Map<String, Boolean> plusDisabled = new HashMap<>(current);
93                 plusDisabled.put(flagName, false);
94                 next.add(plusDisabled);
95                 // re-use the current map and add this flag as enabled
96                 current.put(flagName, true);
97                 next.add(current);
98             }
99             currentList = next;
100         }
101         List<FlagsParameterization> result = new ArrayList<>();
102         for (Map<String, Boolean> valuesMap : currentList) {
103             result.add(new FlagsParameterization(valuesMap));
104         }
105         return result;
106     }
107 
108     /**
109      * Produces a list containing the flag parameterizations where each flag is turned on in the
110      * given sequence.
111      *
112      * <p><code>progressionOf("a", "b", "c")</code> produces the following parameterizations:
113      *
114      * <ul>
115      *   <li><code>{"a": false, "b": false, "c": false}</code>
116      *   <li><code>{"a": true, "b": false, "c": false}</code>
117      *   <li><code>{"a": true, "b": true, "c": false}</code>
118      *   <li><code>{"a": true, "b": true, "c": true}</code>
119      * </ul>
120      *
121      * @return a list of size N+1 for N provided flags.
122      */
123     @Nonnull
progressionOf(@onnull String... flagNames)124     public static List<FlagsParameterization> progressionOf(@Nonnull String... flagNames) {
125         final List<FlagsParameterization> result = new ArrayList<>();
126         final Map<String, Boolean> currentMap = new HashMap<>();
127         for (String flagName : flagNames) {
128             currentMap.put(flagName, false);
129         }
130         result.add(new FlagsParameterization(currentMap));
131         for (String flagName : flagNames) {
132             currentMap.put(flagName, true);
133             result.add(new FlagsParameterization(currentMap));
134         }
135         return result;
136     }
137 }
138