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 
17 package com.android.adservices.common;
18 
19 import static com.android.adservices.common.AbstractAdServicesDeviceSupportedRule.REQUIRES_ANDROID_SERVICE_ASSUMPTION_FAILED_ERROR_MSG;
20 
21 import static org.junit.Assert.assertThrows;
22 import static org.mockito.Mockito.when;
23 
24 import com.android.adservices.common.annotations.RequiresAndroidServiceAvailable;
25 import com.android.adservices.shared.meta_testing.SimpleStatement;
26 import com.android.adservices.shared.meta_testing.TestAnnotations;
27 import com.android.adservices.shared.testing.DeviceConditionsViolatedException;
28 import com.android.adservices.shared.testing.Logger;
29 import com.android.adservices.shared.testing.ScreenSize;
30 import com.android.adservices.shared.testing.StandardStreamsLogger;
31 
32 import com.google.common.truth.Expect;
33 
34 import org.junit.Before;
35 import org.junit.Rule;
36 import org.junit.Test;
37 import org.junit.runner.Description;
38 import org.mockito.Mock;
39 import org.mockito.junit.MockitoJUnit;
40 import org.mockito.junit.MockitoRule;
41 
42 import java.lang.annotation.Annotation;
43 import java.util.Locale;
44 
45 // TODO(b/315542995): provide host-side implementation
46 /**
47  * Test case for {@link AbstractAdServicesDeviceSupportedRule} implementations.
48  *
49  * <p>By default, it uses a {@link
50  * AbstractAdServicesDeviceSupportedRuleTest.FakeAdServicesDeviceSupportedRule bogus rule} so it can
51  * be run by IDEs.
52  */
53 public class AbstractAdServicesDeviceSupportedRuleTest {
54     private static final String CLASS_SERVICE_NAME = "ClassService";
55     private final Logger.RealLogger mRealLogger = StandardStreamsLogger.getInstance();
56     private final SimpleStatement mBaseStatement = new SimpleStatement();
57 
58     private FakeAdServicesDeviceSupportedRule mAdServicesDeviceSupportedRule;
59 
60     @Mock private AbstractDeviceSupportHelper mAbstractDeviceSupportHelper;
61 
62     @Rule public final Expect expect = Expect.create();
63     @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule();
64 
65     @Before
setup()66     public void setup() {
67         mAdServicesDeviceSupportedRule =
68                 new FakeAdServicesDeviceSupportedRule(mRealLogger, mAbstractDeviceSupportHelper);
69         mockIsGoDevice(false);
70         mockIsLowRamDevice(false);
71         mockIsLargeScreenDevice(false);
72     }
73 
74     @Test
testIsAdServicesSupported_supported()75     public void testIsAdServicesSupported_supported() throws Exception {
76         when(mAbstractDeviceSupportHelper.isDeviceSupported()).thenReturn(true);
77 
78         expect.that(mAdServicesDeviceSupportedRule.isAdServicesSupportedOnDevice()).isTrue();
79     }
80 
81     @Test
testIsAdServicesSupportedOnDeviceTest_notSupported()82     public void testIsAdServicesSupportedOnDeviceTest_notSupported() throws Exception {
83         when(mAbstractDeviceSupportHelper.isDeviceSupported()).thenReturn(false);
84 
85         expect.that(mAdServicesDeviceSupportedRule.isAdServicesSupportedOnDevice()).isFalse();
86     }
87 
88     @Test
testIsLowRamDevice_returnsTrue()89     public void testIsLowRamDevice_returnsTrue() {
90         mockIsLowRamDevice(true);
91 
92         expect.that(mAdServicesDeviceSupportedRule.isLowRamDevice()).isTrue();
93     }
94 
95     @Test
testIsLowRamDevice_returnsFalse()96     public void testIsLowRamDevice_returnsFalse() {
97         mockIsLowRamDevice(false);
98 
99         expect.that(mAdServicesDeviceSupportedRule.isLowRamDevice()).isFalse();
100     }
101 
102     @Test
testIsLargeScreenevice_returnsTrue()103     public void testIsLargeScreenevice_returnsTrue() {
104         mockIsLargeScreenDevice(true);
105 
106         expect.that(mAdServicesDeviceSupportedRule.isLargeScreenDevice()).isTrue();
107     }
108 
109     @Test
testIsLargeScreenDevice_returnsFalse()110     public void testIsLargeScreenDevice_returnsFalse() {
111         mockIsLargeScreenDevice(false);
112 
113         expect.that(mAdServicesDeviceSupportedRule.isLargeScreenDevice()).isFalse();
114     }
115 
116     @Test
testIsGoDevice_returnsTrue()117     public void testIsGoDevice_returnsTrue() {
118         mockIsGoDevice(true);
119 
120         expect.that(mAdServicesDeviceSupportedRule.isGoDevice()).isTrue();
121     }
122 
123     @Test
testIsGoDevice_returnsFalse()124     public void testIsGoDevice_returnsFalse() {
125         mockIsGoDevice(false);
126 
127         expect.that(mAdServicesDeviceSupportedRule.isGoDevice()).isFalse();
128     }
129 
130     @Test
testAnnotatedWithLowRam_deviceNotLowRam()131     public void testAnnotatedWithLowRam_deviceNotLowRam() {
132         mockIsLowRamDevice(false);
133         Description description = createTestMethod(TestAnnotations.requiresLowRamDevice());
134 
135         assertTestThrowsAssumptionsViolatedException(
136                 description,
137                 AbstractAdServicesDeviceSupportedRule
138                         .REQUIRES_LOW_RAM_ASSUMPTION_FAILED_ERROR_MESSAGE);
139     }
140 
141     @Test
testAnnotatedWithLowRam_deviceLowRam()142     public void testAnnotatedWithLowRam_deviceLowRam() throws Throwable {
143         mockIsLowRamDevice(true);
144         Description description = createTestMethod(TestAnnotations.requiresLowRamDevice());
145 
146         mAdServicesDeviceSupportedRule.apply(mBaseStatement, description).evaluate();
147 
148         mBaseStatement.assertEvaluated();
149     }
150 
151     @Test
testAnnotatedWithLargeScreen_deviceSmallScreen()152     public void testAnnotatedWithLargeScreen_deviceSmallScreen() {
153         mockIsLargeScreenDevice(false);
154         Description description =
155                 createTestMethod(TestAnnotations.requiresScreenSizeDevice(ScreenSize.LARGE_SCREEN));
156 
157         assertTestThrowsAssumptionsViolatedException(
158                 description,
159                 String.format(
160                         AbstractAdServicesDeviceSupportedRule
161                                 .REQUIRES_SCREEN_SIZE_ASSUMPTION_FAILED_ERROR_MESSAGE,
162                         ScreenSize.LARGE_SCREEN));
163     }
164 
165     @Test
testAnnotatedWithLargeScreen_deviceLargeScreen()166     public void testAnnotatedWithLargeScreen_deviceLargeScreen() throws Throwable {
167         mockIsLargeScreenDevice(true);
168         Description description =
169                 createTestMethod(TestAnnotations.requiresScreenSizeDevice(ScreenSize.LARGE_SCREEN));
170 
171         mAdServicesDeviceSupportedRule.apply(mBaseStatement, description).evaluate();
172 
173         mBaseStatement.assertEvaluated();
174     }
175 
176     @Test
testAnnotatedWithSmallScreen_deviceLargeScreen()177     public void testAnnotatedWithSmallScreen_deviceLargeScreen() {
178         mockIsLargeScreenDevice(true);
179         Description description =
180                 createTestMethod(TestAnnotations.requiresScreenSizeDevice(ScreenSize.SMALL_SCREEN));
181 
182         assertTestThrowsAssumptionsViolatedException(
183                 description,
184                 String.format(
185                         AbstractAdServicesDeviceSupportedRule
186                                 .REQUIRES_SCREEN_SIZE_ASSUMPTION_FAILED_ERROR_MESSAGE,
187                         ScreenSize.SMALL_SCREEN));
188     }
189 
190     @Test
testAnnotatedWithSmallScreen_deviceSmallScreen()191     public void testAnnotatedWithSmallScreen_deviceSmallScreen() throws Throwable {
192         mockIsLargeScreenDevice(false);
193         Description description =
194                 createTestMethod(TestAnnotations.requiresScreenSizeDevice(ScreenSize.SMALL_SCREEN));
195 
196         mAdServicesDeviceSupportedRule.apply(mBaseStatement, description).evaluate();
197 
198         mBaseStatement.assertEvaluated();
199     }
200 
201     @Test
testAnnotatedWithRequiresGoDevice_deviceNotGoDevice()202     public void testAnnotatedWithRequiresGoDevice_deviceNotGoDevice() {
203         mockIsGoDevice(false);
204         Description description = createTestMethod(TestAnnotations.requiresGoDevice());
205 
206         assertTestThrowsAssumptionsViolatedException(
207                 description,
208                 AbstractAdServicesDeviceSupportedRule
209                         .REQUIRES_GO_DEVICE_ASSUMPTION_FAILED_ERROR_MESSAGE);
210     }
211 
212     @Test
testAnnotatedWithRequiresGoDevice_deviceGoDevice()213     public void testAnnotatedWithRequiresGoDevice_deviceGoDevice() throws Throwable {
214         mockIsGoDevice(true);
215         Description description = createTestMethod(TestAnnotations.requiresGoDevice());
216 
217         mAdServicesDeviceSupportedRule.apply(mBaseStatement, description).evaluate();
218 
219         mBaseStatement.assertEvaluated();
220     }
221 
222     @Test
testAnnotatedWithRequiresGoDeviceAndRequiresLowRamDevice_onlyGoDevice()223     public void testAnnotatedWithRequiresGoDeviceAndRequiresLowRamDevice_onlyGoDevice() {
224         mockIsGoDevice(true);
225         Description description =
226                 createTestMethod(
227                         TestAnnotations.requiresGoDevice(), TestAnnotations.requiresLowRamDevice());
228 
229         assertTestThrowsAssumptionsViolatedException(
230                 description,
231                 AbstractAdServicesDeviceSupportedRule
232                         .REQUIRES_LOW_RAM_ASSUMPTION_FAILED_ERROR_MESSAGE);
233     }
234 
235     @Test
testAnnotatedWithRequiresGoDeviceAndRequiresLowMemoryDevice_onlyLowRamDevice()236     public void testAnnotatedWithRequiresGoDeviceAndRequiresLowMemoryDevice_onlyLowRamDevice() {
237         mockIsLowRamDevice(true);
238         Description description =
239                 createTestMethod(
240                         TestAnnotations.requiresGoDevice(), TestAnnotations.requiresLowRamDevice());
241 
242         assertTestThrowsAssumptionsViolatedException(
243                 description,
244                 AbstractAdServicesDeviceSupportedRule
245                         .REQUIRES_GO_DEVICE_ASSUMPTION_FAILED_ERROR_MESSAGE);
246     }
247 
248     @Test
testAnnotatedWithRequiresGoDeviceAndRequiresLowMemoryDevice_deviceNone()249     public void testAnnotatedWithRequiresGoDeviceAndRequiresLowMemoryDevice_deviceNone() {
250         mockIsLowRamDevice(false);
251         mockIsGoDevice(false);
252         Description description =
253                 createTestMethod(
254                         TestAnnotations.requiresGoDevice(), TestAnnotations.requiresLowRamDevice());
255 
256         assertTestThrowsAssumptionsViolatedException(
257                 description,
258                 AbstractAdServicesDeviceSupportedRule
259                         .REQUIRES_GO_DEVICE_ASSUMPTION_FAILED_ERROR_MESSAGE,
260                 AbstractAdServicesDeviceSupportedRule
261                         .REQUIRES_LOW_RAM_ASSUMPTION_FAILED_ERROR_MESSAGE);
262     }
263 
264     @Test
testAnnotatedWithRequiresGoDeviceAndRequiresLowMemoryDevice_deviceBoth()265     public void testAnnotatedWithRequiresGoDeviceAndRequiresLowMemoryDevice_deviceBoth()
266             throws Throwable {
267         mockIsLowRamDevice(true);
268         mockIsGoDevice(true);
269         Description description =
270                 createTestMethod(
271                         TestAnnotations.requiresGoDevice(), TestAnnotations.requiresLowRamDevice());
272 
273         mAdServicesDeviceSupportedRule.apply(mBaseStatement, description).evaluate();
274 
275         mBaseStatement.assertEvaluated();
276     }
277 
278     @Test
testAnnotatedWithRequiresAndroidServiceAvailable_available()279     public void testAnnotatedWithRequiresAndroidServiceAvailable_available() throws Throwable {
280         String serviceName = "SomeService";
281         mockGetResolveInfos(serviceName, /* isAdIdAvailable= */ true);
282         Description description =
283                 createTestMethod(
284                         com.android.adservices.common.TestAnnotations
285                                 .requiresAndroidServiceAvailable(serviceName));
286 
287         mAdServicesDeviceSupportedRule.apply(mBaseStatement, description).evaluate();
288 
289         mBaseStatement.assertEvaluated();
290     }
291 
292     @Test
testAnnotatedWithRequiresAndroidServiceAvailable_unavailable()293     public void testAnnotatedWithRequiresAndroidServiceAvailable_unavailable() {
294         String serviceName = "SomeService";
295         // Package Manager returns null for resolveInfos.
296         mockGetResolveInfos(serviceName, /* isAdIdAvailable= */ false);
297         Description description =
298                 createTestMethod(
299                         com.android.adservices.common.TestAnnotations
300                                 .requiresAndroidServiceAvailable(serviceName));
301 
302         assertTestThrowsAssumptionsViolatedException(
303                 description, getRequiresAndroidServiceAssumptionFailureString(serviceName));
304     }
305 
306     @Test
testAnnotatedWithRequiresAndroidServiceAvailable_mismatchedServiceName()307     public void testAnnotatedWithRequiresAndroidServiceAvailable_mismatchedServiceName() {
308         // Package Manager returns null for resolveInfos.
309         mockGetResolveInfos("SomeService", /* isAdIdAvailable= */ false);
310         String mismatchedServiceName = "MismatchedServiceName";
311         Description description =
312                 createTestMethod(
313                         com.android.adservices.common.TestAnnotations
314                                 .requiresAndroidServiceAvailable(mismatchedServiceName));
315 
316         assertTestThrowsAssumptionsViolatedException(
317                 description,
318                 getRequiresAndroidServiceAssumptionFailureString(mismatchedServiceName));
319     }
320 
321     @Test
testAnnotatedWithRequiresAndroidServiceAvailable_classAnnotation()322     public void testAnnotatedWithRequiresAndroidServiceAvailable_classAnnotation() {
323         String serviceName = "SomeService";
324         // Package Manager returns null for resolveInfos.
325         mockGetResolveInfos(serviceName, /* isAdIdAvailable= */ false);
326         Description description =
327                 createTestMethodUsingDefinedClass(
328                         com.android.adservices.common.TestAnnotations
329                                 .requiresAndroidServiceAvailable(serviceName));
330 
331         assertTestThrowsAssumptionsViolatedException(
332                 description, getRequiresAndroidServiceAssumptionFailureString(serviceName));
333     }
334 
335     @Test
testAnnotatedWithRequiresAndroidServiceAvailable_methodOverridingClassAnnotation()336     public void testAnnotatedWithRequiresAndroidServiceAvailable_methodOverridingClassAnnotation()
337             throws Throwable {
338         String methodServiceName = "MethodService";
339         // Mock that the device doesn't have a service named as CLASS_SERVICE_NAME but has a service
340         // named as methodServiceName.
341         mockGetResolveInfos(CLASS_SERVICE_NAME, /* isAdIdAvailable= */ false);
342         mockGetResolveInfos(methodServiceName, /* isAdIdAvailable= */ true);
343 
344         // Create a description as a test method in the test class
345         // "TestRequiresAndroidServiceAvailable". This test method also has the annotation
346         // RequiresAndroidServiceAvailable but with a different intentAction.
347         Description methodDescription =
348                 createTestMethodUsingDefinedClass(
349                         com.android.adservices.common.TestAnnotations
350                                 .requiresAndroidServiceAvailable(methodServiceName));
351 
352         // It doesn't throw AssumptionsViolatedException though 1) class has the annotation 2) the
353         // corresponding intentAction for class is not available on the device. This is because the
354         // test method's annotation overrides the class's annotation.
355         mAdServicesDeviceSupportedRule.apply(mBaseStatement, methodDescription).evaluate();
356     }
357 
assertTestThrowsAssumptionsViolatedException( Description description, String... expectedReasons)358     private void assertTestThrowsAssumptionsViolatedException(
359             Description description, String... expectedReasons) {
360         DeviceConditionsViolatedException e =
361                 assertThrows(
362                         DeviceConditionsViolatedException.class,
363                         () ->
364                                 mAdServicesDeviceSupportedRule
365                                         .apply(mBaseStatement, description)
366                                         .evaluate());
367 
368         expect.withMessage("exception message")
369                 .that(e.getConditionsViolatedReasons())
370                 .containsExactlyElementsIn(expectedReasons);
371         mBaseStatement.assertNotEvaluated();
372     }
373 
374     /** Bogus implementation of {@link AbstractAdServicesDeviceSupportedRule}. */
375     private static final class FakeAdServicesDeviceSupportedRule
376             extends AbstractAdServicesDeviceSupportedRule {
377         /** Default constructor. */
FakeAdServicesDeviceSupportedRule( Logger.RealLogger logger, AbstractDeviceSupportHelper deviceSupportHelper)378         private FakeAdServicesDeviceSupportedRule(
379                 Logger.RealLogger logger, AbstractDeviceSupportHelper deviceSupportHelper) {
380             super(logger, deviceSupportHelper);
381         }
382     }
383 
createTestMethod(Annotation... annotations)384     private Description createTestMethod(Annotation... annotations) {
385         return Description.createTestDescription(
386                 AbstractAdServicesDeviceSupportedRuleTest.class, "method_name", annotations);
387     }
388 
createTestMethodUsingDefinedClass(Annotation... annotations)389     private Description createTestMethodUsingDefinedClass(Annotation... annotations) {
390         return Description.createTestDescription(
391                 TestRequiresAndroidServiceAvailable.class, "some_test", annotations);
392     }
393 
mockIsLowRamDevice(boolean isLowRam)394     private void mockIsLowRamDevice(boolean isLowRam) {
395         when(mAbstractDeviceSupportHelper.isLowRamDevice()).thenReturn(isLowRam);
396     }
397 
mockIsLargeScreenDevice(boolean isLargeScreen)398     private void mockIsLargeScreenDevice(boolean isLargeScreen) {
399         when(mAbstractDeviceSupportHelper.isLargeScreenDevice()).thenReturn(isLargeScreen);
400     }
401 
mockIsGoDevice(boolean isGoDevice)402     private void mockIsGoDevice(boolean isGoDevice) {
403         when(mAbstractDeviceSupportHelper.isGoDevice()).thenReturn(isGoDevice);
404     }
405 
mockGetResolveInfos(String serviceName, boolean isAdIdAvailable)406     private void mockGetResolveInfos(String serviceName, boolean isAdIdAvailable) {
407         // Bypass the device supported check
408         when(mAbstractDeviceSupportHelper.isDeviceSupported()).thenReturn(true);
409 
410         when(mAbstractDeviceSupportHelper.isAndroidServiceAvailable(serviceName))
411                 .thenReturn(isAdIdAvailable);
412     }
413 
getRequiresAndroidServiceAssumptionFailureString(String serviceName)414     private String getRequiresAndroidServiceAssumptionFailureString(String serviceName) {
415         return String.format(
416                 Locale.ENGLISH, REQUIRES_ANDROID_SERVICE_ASSUMPTION_FAILED_ERROR_MSG, serviceName);
417     }
418 
419     @RequiresAndroidServiceAvailable(intentAction = CLASS_SERVICE_NAME)
420     static final class TestRequiresAndroidServiceAvailable {}
421 }
422