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 package com.android.adservices.mockito;
17 
18 import static com.android.adservices.shared.testing.TestHelper.getAnnotation;
19 import static com.android.adservices.shared.testing.TestHelper.getTestName;
20 
21 import android.util.Log;
22 
23 import org.junit.rules.TestRule;
24 import org.junit.runner.Description;
25 import org.junit.runners.model.Statement;
26 import org.mockito.Mockito;
27 
28 import java.lang.annotation.ElementType;
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 import java.lang.annotation.Target;
32 import java.util.Objects;
33 
34 // TODO(b/315339283): move to shared infra or module-utils (and in the case of the latter,
35 // refactor the ExtendedMockito.Builder.dontClearInlineMocks() to something like
36 // setClearInlineMocks(Mode))
37 // TODO(b/315339283): add unit tests
38 /**
39  * Rule used to clear Mockito inline mocks after a test is run.
40  *
41  * <p>Typically used as a {@code ClassRule} in together with {@link AdServicesExtendedMockitoRule}.
42  */
43 public final class ExtendedMockitoInlineCleanerRule implements TestRule {
44 
45     private static final String TAG = ExtendedMockitoInlineCleanerRule.class.getSimpleName();
46 
47     private final Mode mDefaultMode;
48 
ExtendedMockitoInlineCleanerRule()49     public ExtendedMockitoInlineCleanerRule() {
50         this(Mode.DONT_CLEAR_AT_ALL);
51     }
52 
ExtendedMockitoInlineCleanerRule(Mode defaultMode)53     public ExtendedMockitoInlineCleanerRule(Mode defaultMode) {
54         mDefaultMode = Objects.requireNonNull(defaultMode);
55     }
56 
57     @Override
apply(Statement base, Description description)58     public Statement apply(Statement base, Description description) {
59         return new Statement() {
60 
61             @Override
62             public void evaluate() throws Throwable {
63                 try {
64                     base.evaluate();
65                 } finally {
66                     clearInlineMocksAfterTest(description);
67                 }
68             }
69         };
70     }
71 
72     private Mode getMode(Description description) {
73         Mode mode = mDefaultMode;
74         String testName = getTestName(description);
75         ClearInlineMocksMode annotation = getAnnotation(description, ClearInlineMocksMode.class);
76         if (annotation != null) {
77             mode = annotation.value();
78             Log.v(TAG, "getMode(" + testName + "): returning mode from annotation (" + mode + ")");
79         }
80         Log.v(TAG, "getMode(" + testName + "): returning default mode (" + mode + ")");
81         return mode;
82     }
83 
84     private void clearInlineMocksAfterTest(Description description) {
85         Mode mode = getMode(description);
86         boolean shouldClear = shouldClearInlineMocksAfterTest(description, mode);
87         Log.d(
88                 TAG,
89                 "clearInlineMocksAfterTest("
90                         + getTestName(description)
91                         + "): mode="
92                         + mode
93                         + ", shouldClear="
94                         + shouldClear);
95         if (shouldClear) {
96             clearInlineMocks();
97         }
98     }
99 
100     private void clearInlineMocks() {
101         Log.i(TAG, "Calling Mockito.framework().clearInlineMocks()");
102         Mockito.framework().clearInlineMocks();
103     }
104 
105     /**
106      * Gets whether the inline mocks should be cleared after the given test, based on whether the
107      * test represents a method or class and the given mode.
108      */
109     public static boolean shouldClearInlineMocksAfterTest(Description description, Mode mode) {
110         switch (mode) {
111             case DONT_CLEAR_AT_ALL:
112                 return false;
113             case CLEAR_AFTER_TEST_CLASS:
114                 return description.isSuite();
115             case CLEAR_AFTER_TEST_METHOD:
116                 return !description.isSuite();
117             default:
118                 throw new IllegalArgumentException("unsupported mode: " + mode);
119         }
120     }
121 
122     /** Defines when the inline mocks should be cleared. */
123     public enum Mode {
124         DONT_CLEAR_AT_ALL,
125         CLEAR_AFTER_TEST_METHOD,
126         CLEAR_AFTER_TEST_CLASS
127     }
128 
129     /** Annotation used to defines when the inline mocks should be cleared. */
130     @Retention(RetentionPolicy.RUNTIME)
131     @Target({ElementType.METHOD, ElementType.TYPE})
132     public @interface ClearInlineMocksMode {
133         Mode value();
134     }
135 }
136