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