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.flags; 18 19 import android.annotation.NonNull; 20 21 import java.util.HashMap; 22 import java.util.HashSet; 23 import java.util.Map; 24 import java.util.Set; 25 26 /** 27 * An implementation of {@link FeatureFlags} for testing. 28 * 29 * Before you read a flag from using this Fake, you must set that flag using 30 * {@link #setFlagValue(BooleanFlagBase, boolean)}. This ensures that your tests are deterministic. 31 * 32 * If you are relying on {@link FeatureFlags#getInstance()} to access FeatureFlags in your code 33 * under test, (instead of dependency injection), you can pass an instance of this fake to 34 * {@link FeatureFlags#setInstance(FeatureFlags)}. Be sure to call that method again, passing null, 35 * to ensure hermetic testing - you don't want static state persisting between your test methods. 36 * 37 * @hide 38 */ 39 public class FeatureFlagsFake extends FeatureFlags { 40 private final Map<BooleanFlagBase, Boolean> mFlagValues = new HashMap<>(); 41 private final Set<BooleanFlagBase> mReadFlags = new HashSet<>(); 42 FeatureFlagsFake(IFeatureFlags iFeatureFlags)43 public FeatureFlagsFake(IFeatureFlags iFeatureFlags) { 44 super(iFeatureFlags); 45 } 46 47 @Override isEnabled(@onNull BooleanFlag flag)48 public boolean isEnabled(@NonNull BooleanFlag flag) { 49 return requireFlag(flag); 50 } 51 52 @Override isEnabled(@onNull FusedOffFlag flag)53 public boolean isEnabled(@NonNull FusedOffFlag flag) { 54 return requireFlag(flag); 55 } 56 57 @Override isEnabled(@onNull FusedOnFlag flag)58 public boolean isEnabled(@NonNull FusedOnFlag flag) { 59 return requireFlag(flag); 60 } 61 62 @Override isCurrentlyEnabled(@onNull DynamicBooleanFlag flag)63 public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) { 64 return requireFlag(flag); 65 } 66 67 @Override syncInternal(Set<Flag<?>> dirtyFlags)68 protected void syncInternal(Set<Flag<?>> dirtyFlags) { 69 } 70 71 /** 72 * Explicitly set a flag's value for reading in tests. 73 * 74 * You _must_ call this for every flag your code-under-test will read. Otherwise, an 75 * {@link IllegalStateException} will be thrown. 76 * 77 * You are able to set values for {@link FusedOffFlag} and {@link FusedOnFlag}, despite those 78 * flags having a fixed value at compile time, since unit tests should still test the state of 79 * those flags as both true and false. I.e. a flag that is off might be turned on in a future 80 * build or vice versa. 81 * 82 * You can not call this method _after_ a non-dynamic flag has been read. Non-dynamic flags 83 * are held stable in the system, so changing a value after reading would not match 84 * real-implementation behavior. 85 * 86 * Calling this method will trigger any {@link android.flags.FeatureFlags.ChangeListener}s that 87 * are registered for the supplied flag if the flag is a {@link DynamicFlag}. 88 * 89 * @param flag The BooleanFlag that you want to set a value for. 90 * @param value The value that the flag should return when accessed. 91 */ setFlagValue(@onNull BooleanFlagBase flag, boolean value)92 public void setFlagValue(@NonNull BooleanFlagBase flag, boolean value) { 93 if (!(flag instanceof DynamicBooleanFlag) && mReadFlags.contains(flag)) { 94 throw new RuntimeException( 95 "You can not set the value of a flag after it has been read. Tried to set " 96 + flag + " to " + value + " but it already " + mFlagValues.get(flag)); 97 } 98 mFlagValues.put(flag, value); 99 if (flag instanceof DynamicBooleanFlag) { 100 onFlagChange((DynamicFlag<?>) flag); 101 } 102 } 103 requireFlag(BooleanFlagBase flag)104 private boolean requireFlag(BooleanFlagBase flag) { 105 if (!mFlagValues.containsKey(flag)) { 106 throw new IllegalStateException( 107 "Tried to access " + flag + " in test but no overrided specified. You must " 108 + "call #setFlagValue for each flag read in a test."); 109 } 110 mReadFlags.add(flag); 111 112 return mFlagValues.get(flag); 113 } 114 115 } 116