• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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