1 // Copyright 2023 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import org.chromium.build.BuildConfig; 8 9 import java.util.ArrayList; 10 import java.util.Collections; 11 import java.util.LinkedHashSet; 12 13 /** 14 * ResettersForTesting provides functionality for reset values set for testing. This class is used 15 * directly by test runners, but lives in prod code to simplify usage. 16 * 17 * It is required to invoke {@link #register(Runnable)} whenever a method called `set*ForTesting`, 18 * such `setFooForTesting(Foo foo)` is invoked. Typical usage looks like this: 19 * 20 * <code> 21 * class MyClass { 22 * private static MyClass sInstance; 23 * 24 * public static MyClass getInstance() { 25 * if (sInstance == null) sInstance = new MyClass(); 26 * return sInstance; 27 * } 28 * 29 * public static void setMyClassForTesting(MyClass myClassObj) { 30 * var oldInstance = sInstance 31 * sInstance = myClassObj; 32 * ResettersForTesting.register(() -> sInstance = oldInstance); 33 * } 34 * } 35 * </code> 36 * 37 * This is not only used for singleton instances, but can also be used for resetting other static 38 * members. 39 * 40 * <code> 41 * class NeedsFoo { 42 * private static Foo sFooForTesting; 43 * 44 * public void doThing() { 45 * Foo foo = sFooForTesting != null ? sFooForTesting : new FooImpl(); 46 * foo.doItsThing(); 47 * } 48 * 49 * public static void setFooForTesting(Foo foo) { 50 * sFooForTesting = foo; 51 * ResettersForTesting.register(() -> sFooForTesting = null); 52 * } 53 * } 54 * </code> 55 * 56 * For cases where it is important that a particular resetter runs only once, even if the 57 * `set*ForTesting` method is invoked multiple times, there is another variation that can be used. 58 * In particular, since a lambda always ends up creating a new instance in Chromium builds, we can 59 * avoid this by having a single static instance of the resetter, like this: 60 * 61 * <code> 62 * private static class NeedsFooSingleDestroy { 63 * private static final class LazyHolder { 64 * private static Foo INSTANCE = new Foo(); 65 * } 66 * 67 * private static LazyHolder sFoo; 68 * 69 * private static Runnable sOneShotResetter = () -> { 70 * sFoo.INSTANCE.destroy(); 71 * sFoo = new Foo(); 72 * }; 73 * 74 * public static void setFooForTesting(Foo foo) { 75 * sFoo.INSTANCE = foo; 76 * ResettersForTesting.register(sResetter); 77 * } 78 * } 79 * </code> 80 */ 81 public class ResettersForTesting { 82 // LinkedHashSet is a set that provides ordering and enables one-shot resetters to only be 83 // invoked once. For example, the following `sResetter` will only be in the set a single time. 84 // <code> 85 // private static final Runnable sResetter = () -> { ... } 86 // ... 87 // ResettersForTesting.register(sResetter); 88 // </code> 89 private static final LinkedHashSet<Runnable> sClassResetters = 90 BuildConfig.IS_FOR_TEST ? new LinkedHashSet<>() : null; 91 private static final LinkedHashSet<Runnable> sMethodResetters = 92 BuildConfig.IS_FOR_TEST ? new LinkedHashSet<>() : null; 93 // Starts in "class mode", since @BeforeClass runs before @Before. 94 // Test runners toggle this via setMethodMode(), then reset it via onAfterClass(). 95 private static boolean sMethodMode; 96 97 /** 98 * Register a {@link Runnable} that will automatically execute during test tear down. 99 * @param runnable the {@link Runnable} to execute. 100 */ register(Runnable runnable)101 public static void register(Runnable runnable) { 102 // Allow calls from non-test code without callers needing to add a BuildConfig.IS_FOR_TEST 103 // check (enables R8 to optimize away the call). 104 if (!BuildConfig.IS_FOR_TEST) { 105 return; 106 } 107 if (sMethodMode) { 108 sMethodResetters.add(runnable); 109 } else { 110 sClassResetters.add(runnable); 111 } 112 } 113 114 /** 115 * Execute and clear all the currently registered resetters. 116 * 117 * This is not intended to be invoked manually, but is intended to be invoked by the test 118 * runners automatically during tear down. 119 */ flushResetters(LinkedHashSet activeSet)120 private static void flushResetters(LinkedHashSet activeSet) { 121 ArrayList<Runnable> resetters = new ArrayList<>(activeSet); 122 activeSet.clear(); 123 124 // Ensure that resetters are run in reverse order, enabling nesting of values as well as 125 // being more similar to C++ destruction order. 126 Collections.reverse(resetters); 127 for (Runnable resetter : resetters) { 128 resetter.run(); 129 } 130 } 131 132 /** Called by test runners after @After methods. */ onAfterMethod()133 public static void onAfterMethod() { 134 flushResetters(sMethodResetters); 135 } 136 137 /** Called by test runners after @AfterClass methods. */ onAfterClass()138 public static void onAfterClass() { 139 flushResetters(sClassResetters); 140 sMethodMode = false; 141 } 142 143 /** Called by test runners after @BeforeClass methods, but before @Before methods. */ setMethodMode()144 public static void setMethodMode() { 145 sMethodMode = true; 146 } 147 } 148