1 /*
2  * Copyright (C) 2024 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.shared.testing;
17 
18 import android.annotation.CallSuper;
19 import android.content.Context;
20 import android.platform.test.ravenwood.RavenwoodRule;
21 
22 import androidx.test.platform.app.InstrumentationRegistry;
23 
24 import com.android.adservices.shared.testing.Logger.LogLevel;
25 
26 import org.junit.Before;
27 import org.junit.BeforeClass;
28 import org.junit.ClassRule;
29 import org.junit.Rule;
30 import org.junit.Test;
31 
32 /**
33  * Superclass for all device-side tests, it contains just the bare minimum features used by all
34  * tests.
35  */
36 public abstract class DeviceSideTestCase extends SidelessTestCase {
37 
38     private static final String TAG = DeviceSideTestCase.class.getSimpleName();
39 
40     private static final String REASON_SESSION_MANAGED_BY_RULE =
41             "mockito session is automatically managed by a @Rule";
42     private static final String REASON_NO_TARGET_CONTEXT =
43             "tests should use mContext instead - if it needs the target context, please add to"
44                     + " DeviceSideTestCase instead";
45 
46     // TODO(b/335935200): This (and RavenwoodConfig) should be removed once Ravenwood starts
47     // using the package name from the build file.
48     private static final String RAVENWOOD_PACKAGE_NAME = "com.android.adservices.shared.tests";
49 
50     /** {@code logcat} tag. */
51     protected final String mTag = getClass().getSimpleName();
52 
53     // NOTE: references below CANNOT be set when declared as the call to InstrumentationRegistry
54     // would fail when running on host / under Ravenwood
55 
56     /**
57      * @deprecated use {@link #mContext}
58      */
59     @Deprecated protected static Context sContext;
60 
61     /**
62      * Package name of the app being instrumented.
63      *
64      * @deprecated use {@link #mPackageName} instead.
65      */
66     @Deprecated protected static String sPackageName;
67 
68     /**
69      * Reference to the context of this test's instrumentation package (as defined by {@link
70      * android.app.Instrumentation#getContext()})
71      */
72     protected Context mContext;
73 
74     /**
75      * Package name of this test's instrumentation package (as defined by {@link
76      * android.app.Instrumentation#getContext()})
77      */
78     protected String mPackageName;
79 
80     @ClassRule
81     public static final RavenwoodRule sRavenwood =
82             new RavenwoodRule.Builder()
83                     .setProvideMainThread(true)
84                     .setPackageName(RAVENWOOD_PACKAGE_NAME)
85                     .build();
86 
87     // TODO(b/342639109): make sure it's the right order
88     @Rule(order = 0)
89     public final SdkLevelSupportRule sdkLevel = SdkLevelSupportRule.forAnyLevel();
90 
91     // TODO(b/342639109): set order
92     @Rule
93     public final ProcessLifeguardRule processLifeguard =
94             new ProcessLifeguardRule(ProcessLifeguardRule.Mode.IGNORE);
95 
96     @BeforeClass
setStaticFixtures()97     public static void setStaticFixtures() {
98         if (sContext != null) {
99             // TODO(b/335935200): remove this check once the static initialization is gone
100             return;
101         }
102         try {
103             sContext = InstrumentationRegistry.getInstrumentation().getContext();
104             sPackageName = sContext.getPackageName();
105         } catch (Exception e) {
106             DynamicLogger.getInstance()
107                     .log(
108                             LogLevel.ERROR,
109                             TAG,
110                             e,
111                             "setStaticFixtures() failed (usually happens under Ravenwood). Setting"
112                                     + " sContext=%s, sPackageName=%s",
113                             sContext,
114                             sPackageName);
115         }
116     }
117 
118     @Before
setInstanceFixtures()119     public final void setInstanceFixtures() {
120         mContext = sContext;
121         mPackageName = sPackageName;
122     }
123 
124     // TODO(b/361555631): merge 2 classes below into testDeviceSideTestCaseFixtures() and annotate
125     // it with @MetaTest
126     @Test
127     @Override
testValidTestCaseFixtures()128     public final void testValidTestCaseFixtures() throws Exception {
129         assertValidTestCaseFixtures();
130     }
131 
132     @CallSuper
133     @Override
assertValidTestCaseFixtures()134     protected void assertValidTestCaseFixtures() throws Exception {
135         super.assertValidTestCaseFixtures();
136 
137         assertTestClassHasNoFieldsFromSuperclass(
138                 DeviceSideTestCase.class,
139                 "mContext",
140                 "mPackageName",
141                 "mTag",
142                 "ravenwood",
143                 "sdkLevel",
144                 "processLifeGuard",
145                 "sContext",
146                 "sPackageName",
147                 "sRavenWood",
148                 "RAVENWOOD_PACKAGE_NAME");
149         assertTestClassHasNoSuchField(
150                 "CONTEXT",
151                 "should use existing mContext (or sContext when that's not possible) instead");
152         assertTestClassHasNoSuchField(
153                 "APPLICATION_CONTEXT",
154                 "should use existing mContext (or sContext when that's not possible) instead");
155         assertTestClassHasNoSuchField("context", "should use existing mContext instead");
156         assertTestClassHasNoSuchField("mTargetContext", REASON_NO_TARGET_CONTEXT);
157         assertTestClassHasNoSuchField("mTargetPackageName", REASON_NO_TARGET_CONTEXT);
158         assertTestClassHasNoSuchField("sTargetContext", REASON_NO_TARGET_CONTEXT);
159         assertTestClassHasNoSuchField("sTargetPackageName", REASON_NO_TARGET_CONTEXT);
160     }
161 
162     // NOTE: it's static so it can be used by other mockito-related superclasses, as often test
163     // cases are converted to use AdServicesMockitoTestCase and still defined the ExtendedMockito
164     // session - they should migrate to AdServicesExtendedMockitoTestCase instead.
checkProhibitedMockitoFields( Class<T> superclass, T testInstance)165     protected static <T extends DeviceSideTestCase> void checkProhibitedMockitoFields(
166             Class<T> superclass, T testInstance) throws Exception {
167         // NOTE: same fields below are not defined (yet?) SharedExtendedMockitoTestCase or
168         // SharedMockitoTestCase, but they might; and even if they don't, this method is also used
169         // by the classes on AdServices (AdServicesMockitoTestCase /
170         // AdServicesExtendedMockitoTestCase)
171         testInstance.assertTestClassHasNoFieldsFromSuperclass(
172                 superclass,
173                 "mMockContext",
174                 "mSpyContext",
175                 "extendedMockito",
176                 "errorLogUtilUsageRule",
177                 "mocker",
178                 "sInlineCleaner",
179                 "sSpyContext",
180                 "mMockFlags",
181                 "mMockDebugFlags");
182         testInstance.assertTestClassHasNoSuchField(
183                 "mContextMock", "should use existing mMockContext instead");
184         testInstance.assertTestClassHasNoSuchField(
185                 "mContextSpy", "should use existing mSpyContext instead");
186         testInstance.assertTestClassHasNoSuchField("mockito", "already taken care by @Rule");
187         testInstance.assertTestClassHasNoSuchField(
188                 "mFlagsMock", "should use existing mMockFlags instead");
189         testInstance.assertTestClassHasNoSuchField(
190                 "sMockFlags", "should use existing mMockFlags instead");
191         testInstance.assertTestClassHasNoSuchField(
192                 "mFlags",
193                 superclass.getSimpleName()
194                         + " already define a mMockFlags, and often subclasses define a @Mock"
195                         + " mFlags; to avoid confusion, either use the existing mMockFlags, or"
196                         + " create a non-mock instance like mFakeFlags");
197 
198         // Listed below are existing names for the extended mockito session on test classes that
199         // don't use the rule / superclass:
200         // TODO(b/368153625): should check for type instead
201         testInstance.assertTestClassHasNoSuchField(
202                 "mStaticMockSession", REASON_SESSION_MANAGED_BY_RULE);
203         testInstance.assertTestClassHasNoSuchField(
204                 "mMockitoSession", REASON_SESSION_MANAGED_BY_RULE);
205         testInstance.assertTestClassHasNoSuchField(
206                 "mockitoSession", REASON_SESSION_MANAGED_BY_RULE);
207         testInstance.assertTestClassHasNoSuchField("session", REASON_SESSION_MANAGED_BY_RULE);
208         testInstance.assertTestClassHasNoSuchField(
209                 "sStaticMockitoSession", REASON_SESSION_MANAGED_BY_RULE);
210         testInstance.assertTestClassHasNoSuchField(
211                 "staticMockitoSession", REASON_SESSION_MANAGED_BY_RULE);
212         testInstance.assertTestClassHasNoSuchField(
213                 "staticMockSession", REASON_SESSION_MANAGED_BY_RULE);
214     }
215 
216     // TODO(b/335935200): temporary hac^H^H^Hworkaround to set context references before subclasses
217     // when the class or instance is initialized.
218     // In the long term, these tests must be refactored to use them "inside" the test (otherwise
219     // it would not work when running on host-side / ravenwood), then they can be removed.
220     static {
setStaticFixtures()221         setStaticFixtures();
222     }
223 
DeviceSideTestCase()224     protected DeviceSideTestCase() {
225         setInstanceFixtures();
226     }
227 }
228