11package frc .robot .lib ;
22
3+ import java .lang .ref .WeakReference ;
34import java .lang .reflect .InvocationTargetException ;
45import java .lang .reflect .Method ;
56import java .lang .reflect .Modifier ;
67import java .util .ArrayList ;
78import java .util .Arrays ;
9+ import java .util .Collections ;
810import java .util .HashMap ;
11+ import java .util .List ;
12+ import java .util .function .Consumer ;
13+ import java .util .function .Predicate ;
914import java .util .stream .Collectors ;
10- import org .mockito .MockSettings ;
1115
16+ import org .mockito .MockSettings ;
1217import org .mockito .Mockito ;
1318import org .mockito .internal .stubbing .defaultanswers .ReturnsSmartNulls ;
1419import org .mockito .invocation .InvocationOnMock ;
1520import org .mockito .stubbing .Answer ;
1621
1722public final class Mocks {
23+
24+ private static final List <WeakReference <Object >> MOCKS = Collections .synchronizedList (new ArrayList <>());
25+ private static final Predicate <WeakReference <?>> IS_REFERENCE_CLEARED = reference -> reference .get () == null ;
26+ private static final Consumer <WeakReference <Object >> CLEAR_INVOCATIONS_ON_REFERENCED_MOCK = reference -> Mockito .clearInvocations (reference .get ());
27+ private static final Predicate <WeakReference <Object >> CLEAR_INVOCATIONS_ON_REFERENCED_MOCK_IF_REFERNCE_NOT_CLEARED = reference -> {
28+ if (IS_REFERENCE_CLEARED .test (reference )) return true ;
29+ CLEAR_INVOCATIONS_ON_REFERENCED_MOCK .accept (reference );
30+ return false ;
31+ };
32+
33+ static {
34+ // Use a single predicate so that clearing references and invocations is an atomic operation
35+ // Otherwise, we could (rarely) run into:
36+ // 1) Mock is added
37+ // 2) Garbage collected references are removed
38+ // 3) Mock is garbage collected
39+ // 4) Mock invocations are cleared -> throws NullPointerException
40+ Lib199Subsystem .registerPeriodic (() -> MOCKS .removeIf (CLEAR_INVOCATIONS_ON_REFERENCED_MOCK_IF_REFERNCE_NOT_CLEARED ));
41+ }
1842
1943 /**
2044 * Attempts to create an instance of a class in which some or all of the classes methods are replaced with a mocked implementation
@@ -77,7 +101,7 @@ public static <T, U> T createMock(Class<T> classToMock, U implClass, Answer<Obje
77101 settings = Mockito .withSettings ().extraInterfaces (interfaces );
78102 }
79103 settings = settings .defaultAnswer (new MockAnswer <>(methods , implClass , defaultAnswer ));
80- T mock = Mockito . mock (classToMock , settings );
104+ T mock = mock (classToMock , settings );
81105 return mock ;
82106 }
83107
@@ -88,6 +112,55 @@ public static Method[] listMethods(Class<?> base, Class<?>... interfaces) {
88112 return out .toArray (Method []::new );
89113 }
90114
115+ /**
116+ * A wrapper for the underlying Mockito method which automatically calls {@link Mockito#clearInvocations(Object...)} to prevent memory leaks
117+ *
118+ * @see Mockito#mock(Class)
119+ */
120+ public static <T > T mock (Class <T > classToMock ) {
121+ return reportMock (Mockito .mock (classToMock ));
122+ }
123+
124+ /**
125+ * A wrapper for the underlying Mockito method which automatically calls {@link Mockito#clearInvocations(Object...)} to prevent memory leaks
126+ *
127+ * @see Mockito#mock(Class, String)
128+ */
129+ public static <T > T mock (Class <T > classToMock , String name ) {
130+ return reportMock (Mockito .mock (classToMock , name ));
131+ }
132+
133+ /**
134+ * A wrapper for the underlying Mockito method which automatically calls {@link Mockito#clearInvocations(Object...)} to prevent memory leaks
135+ *
136+ * @see Mockito#mock(Class, Answer)
137+ */
138+ public static <T > T mock (Class <T > classToMock , Answer <?> defaultAnswer ) {
139+ return reportMock (Mockito .mock (classToMock , defaultAnswer ));
140+ }
141+
142+ /**
143+ * A wrapper for the underlying Mockito method which automatically calls {@link Mockito#clearInvocations(Object...)} to prevent memory leaks
144+ *
145+ * @see Mockito#mock(Class, MockSettings)
146+ */
147+ public static <T > T mock (Class <T > classToMock , MockSettings mockSettings ) {
148+ return reportMock (Mockito .mock (classToMock , mockSettings ));
149+ }
150+
151+ /**
152+ * Registers a Mockito mock and periodically calls {@link Mockito#clearInvocations(Object...)} on it to prevent memory leaks
153+ *
154+ * @param <T> The type of the mock
155+ * @param t The mock
156+ * @return The mock
157+ */
158+ public static <T > T reportMock (T t ) {
159+ // Wrap in a WeakReference to prevent memory leaks on objects with no more references
160+ if (Mockito .mockingDetails (t ).isMock ()) MOCKS .add (new WeakReference <Object >(t ));
161+ return t ;
162+ }
163+
91164 private Mocks () {}
92165
93166 private static final class MockAnswer <U > implements Answer <Object > {
0 commit comments