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 com.android.server;
18 
19 import android.annotation.Nullable;
20 import android.util.Log;
21 
22 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
23 import com.android.internal.util.Preconditions;
24 import com.android.modules.utils.testing.StaticMockFixture;
25 import com.android.modules.utils.testing.StaticMockFixtureRule;
26 
27 import org.mockito.Mockito;
28 import org.mockito.quality.Strictness;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Objects;
33 
34 /**
35  * Rule to make it easier to use Extended Mockito.
36  *
37  * <p>It's derived from {@link StaticMockFixtureRule}, with the additional features:
38  *
39  * <ul>
40  *   <li>Easier to define which classes must be statically mocked or spied
41  *   <li>Automatically starts mocking (so tests don't need a mockito runner or rule)
42  *   <li>Automatically clears the inlined mocks at the end (to avoid OOM)
43  *   <li>Allows other customization like strictness
44  * </ul>
45  */
46 public final class ExtendedMockitoRule extends StaticMockFixtureRule {
47 
48     private static final String TAG = ExtendedMockitoRule.class.getSimpleName();
49 
50     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
51 
52     private final Object mTestClassInstance;
53     private final Strictness mStrictness;
54 
ExtendedMockitoRule(Builder builder)55     private ExtendedMockitoRule(Builder builder) {
56         super(() -> new SimpleStatickMockFixture(builder.mMockedStaticClasses,
57                 builder.mSpiedStaticClasses, builder.mDynamicSessionBuilderConfigurator,
58                 builder.mAfterSessionFinishedCallback));
59         mTestClassInstance = builder.mTestClassInstance;
60         mStrictness = builder.mStrictness;
61         if (VERBOSE) {
62             Log.v(TAG, "strictness=" + mStrictness + ", testClassInstance" + mTestClassInstance
63                     + ", mockedStaticClasses=" + builder.mMockedStaticClasses
64                     + ", spiedStaticClasses=" + builder.mSpiedStaticClasses
65                     + ", dynamicSessionBuilderConfigurator="
66                     + builder.mDynamicSessionBuilderConfigurator
67                     + ", afterSessionFinishedCallback=" + builder.mAfterSessionFinishedCallback);
68         }
69     }
70 
71     @Override
getSessionBuilder()72     public StaticMockitoSessionBuilder getSessionBuilder() {
73         StaticMockitoSessionBuilder sessionBuilder = super.getSessionBuilder();
74         if (mStrictness != null) {
75             if (VERBOSE) {
76                 Log.v(TAG, "Setting strictness to " + mStrictness + " on " + sessionBuilder);
77             }
78             sessionBuilder.strictness(mStrictness);
79         }
80         return sessionBuilder.initMocks(mTestClassInstance);
81     }
82 
83     public static final class Builder {
84         private final Object mTestClassInstance;
85         private @Nullable Strictness mStrictness;
86         private final List<Class<?>> mMockedStaticClasses = new ArrayList<>();
87         private final List<Class<?>> mSpiedStaticClasses = new ArrayList<>();
88         private @Nullable Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
89         private @Nullable Runnable mAfterSessionFinishedCallback;
90 
Builder(Object testClassInstance)91         public Builder(Object testClassInstance) {
92             mTestClassInstance = Objects.requireNonNull(testClassInstance);
93         }
94 
setStrictness(Strictness strictness)95         public Builder setStrictness(Strictness strictness) {
96             mStrictness = Objects.requireNonNull(strictness);
97             return this;
98         }
99 
mockStatic(Class<?> clazz)100         public Builder mockStatic(Class<?> clazz) {
101             Objects.requireNonNull(clazz);
102             Preconditions.checkState(!mMockedStaticClasses.contains(clazz),
103                     "class %s already mocked", clazz);
104             mMockedStaticClasses.add(clazz);
105             return this;
106         }
107 
spyStatic(Class<?> clazz)108         public Builder spyStatic(Class<?> clazz) {
109             Objects.requireNonNull(clazz);
110             Preconditions.checkState(!mSpiedStaticClasses.contains(clazz),
111                     "class %s already spied", clazz);
112             mSpiedStaticClasses.add(clazz);
113             return this;
114         }
115 
dynamiclyConfigureSessionBuilder( Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator)116         public Builder dynamiclyConfigureSessionBuilder(
117                 Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator) {
118             mDynamicSessionBuilderConfigurator = Objects
119                     .requireNonNull(dynamicSessionBuilderConfigurator);
120             return this;
121         }
122 
afterSessionFinished(Runnable runnable)123         public Builder afterSessionFinished(Runnable runnable) {
124             mAfterSessionFinishedCallback = Objects.requireNonNull(runnable);
125             return this;
126         }
127 
build()128         public ExtendedMockitoRule build() {
129             return new ExtendedMockitoRule(this);
130         }
131     }
132 
133     private static final class SimpleStatickMockFixture implements StaticMockFixture {
134 
135         private final List<Class<?>> mMockedStaticClasses;
136         private final List<Class<?>> mSpiedStaticClasses;
137         @Nullable
138         private final Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
139         @Nullable
140         private final Runnable mAfterSessionFinishedCallback;
141 
SimpleStatickMockFixture(List<Class<?>> mockedStaticClasses, List<Class<?>> spiedStaticClasses, @Nullable Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator, @Nullable Runnable afterSessionFinishedCallback)142         private SimpleStatickMockFixture(List<Class<?>> mockedStaticClasses,
143                 List<Class<?>> spiedStaticClasses,
144                 @Nullable Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator,
145                 @Nullable Runnable afterSessionFinishedCallback) {
146             mMockedStaticClasses = mockedStaticClasses;
147             mSpiedStaticClasses = spiedStaticClasses;
148             mDynamicSessionBuilderConfigurator = dynamicSessionBuilderConfigurator;
149             mAfterSessionFinishedCallback = afterSessionFinishedCallback;
150         }
151 
152         @Override
setUpMockedClasses( StaticMockitoSessionBuilder sessionBuilder)153         public StaticMockitoSessionBuilder setUpMockedClasses(
154                 StaticMockitoSessionBuilder sessionBuilder) {
155             mMockedStaticClasses.forEach((c) -> sessionBuilder.mockStatic(c));
156             mSpiedStaticClasses.forEach((c) -> sessionBuilder.spyStatic(c));
157             if (mDynamicSessionBuilderConfigurator != null) {
158                 mDynamicSessionBuilderConfigurator.visit(sessionBuilder);
159             }
160             return sessionBuilder;
161         }
162 
163         @Override
setUpMockBehaviors()164         public void setUpMockBehaviors() {
165         }
166 
167         @Override
tearDown()168         public void tearDown() {
169             try {
170                 if (mAfterSessionFinishedCallback != null) {
171                     mAfterSessionFinishedCallback.run();
172                 }
173             } finally {
174                 if (VERBOSE) {
175                     Log.v(TAG, "calling Mockito.framework().clearInlineMocks()");
176                 }
177                 // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
178                 // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
179                 Mockito.framework().clearInlineMocks();
180             }
181         }
182     }
183 }
184