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.logging; 18 19 import static com.android.adservices.common.logging.annotations.ExpectErrorLogUtilCall.ANNOTATION_NAME; 20 21 import android.util.Log; 22 23 import com.android.adservices.common.logging.annotations.ExpectErrorLogUtilCall; 24 import com.android.adservices.common.logging.annotations.ExpectErrorLogUtilCalls; 25 import com.android.adservices.common.logging.annotations.SetErrorLogUtilDefaultParams; 26 import com.android.adservices.shared.testing.AbstractLogVerifier; 27 import com.android.adservices.shared.testing.TestHelper; 28 29 import com.google.common.collect.ImmutableList; 30 import com.google.common.collect.ImmutableSet; 31 32 import org.junit.runner.Description; 33 34 import java.util.Arrays; 35 import java.util.List; 36 import java.util.Set; 37 import java.util.stream.Collectors; 38 39 /** 40 * Log verifier for scanning usage of {@code ErrorLogUtil.e(int, int)} invocations. Uses {@link 41 * ErrorLogUtilSyncCallback} internally to capture log calls and waits for expected number of calls 42 * to be made before verifying or failing the test if expected number of calls are not made (default 43 * 5s timeout). 44 * 45 * <p>Use {@link ExpectErrorLogUtilCall} to verify both background and non-background logging calls. 46 */ 47 public final class AdServicesErrorLogUtilVerifier extends AbstractLogVerifier<ErrorLogUtilCall> { 48 private ErrorLogUtilSyncCallback mErrorLogUtilSyncCallback; 49 private Set<ErrorLogUtilCall> mExpectedLogCallsCache; 50 51 @Override mockLogCalls(Description description)52 protected void mockLogCalls(Description description) { 53 // Configure the sync callback to await for expected number of calls to be made before 54 // verification. Note: default timeout is set to 5 seconds before the test fails. 55 mErrorLogUtilSyncCallback = 56 ErrorLogUtilSyncCallback.mockErrorLogUtilWithoutThrowable( 57 getTotalExpectedLogCalls(description)); 58 } 59 60 @Override getExpectedLogCalls(Description description)61 public Set<ErrorLogUtilCall> getExpectedLogCalls(Description description) { 62 if (mExpectedLogCallsCache == null) { 63 // Parsing the expected calls is required at least twice: once for configuring the 64 // number of calls for the sync callback and once in the verification step after 65 // test execution. Expected log calls are cached so it's parsed only once. 66 mExpectedLogCallsCache = parseExpectedLogCalls(description); 67 } 68 return mExpectedLogCallsCache; 69 } 70 71 @Override getActualLogCalls()72 public Set<ErrorLogUtilCall> getActualLogCalls() { 73 if (mErrorLogUtilSyncCallback == null) { 74 throw new IllegalStateException( 75 "mErrorLogUtilSyncCallback is null, which indicates mocking isn't done prior " 76 + "to fetching the actual log calls."); 77 } 78 79 return dedupeCalls(mErrorLogUtilSyncCallback.getResultsReceivedUponWaiting()); 80 } 81 82 @Override getResolutionMessage()83 public String getResolutionMessage() { 84 return "Please make sure to use @" 85 + ANNOTATION_NAME 86 + "(..) " 87 + "over test method to denote " 88 + "all expected ErrorLogUtil.e(int, int) calls."; 89 } 90 parseExpectedLogCalls(Description description)91 private Set<ErrorLogUtilCall> parseExpectedLogCalls(Description description) { 92 List<ExpectErrorLogUtilCall> annotations = getAnnotations(description); 93 SetErrorLogUtilDefaultParams defaultParams = 94 TestHelper.getAnnotationFromTypesOnly( 95 description.getTestClass(), SetErrorLogUtilDefaultParams.class); 96 97 if (annotations.isEmpty()) { 98 Log.v(mTag, "No @" + ANNOTATION_NAME + " found over test method."); 99 return ImmutableSet.of(); 100 } 101 102 Set<ErrorLogUtilCall> expectedCalls = 103 annotations.stream() 104 .peek(a -> validateTimes(a.times(), ANNOTATION_NAME)) 105 .map(a -> ErrorLogUtilCall.createFrom(a, defaultParams)) 106 .collect(Collectors.toSet()); 107 108 if (expectedCalls.size() != annotations.size()) { 109 throw new IllegalStateException( 110 "Detected @" 111 + ANNOTATION_NAME 112 + " annotations representing the same " 113 + "invocation! De-dupe by using times arg"); 114 } 115 116 return expectedCalls; 117 } 118 getAnnotations(Description description)119 private List<ExpectErrorLogUtilCall> getAnnotations(Description description) { 120 // Scan for multiple annotation container 121 ExpectErrorLogUtilCalls multiple = description.getAnnotation(ExpectErrorLogUtilCalls.class); 122 if (multiple != null) { 123 return Arrays.stream(multiple.value()).collect(Collectors.toList()); 124 } 125 126 // Scan for single annotation 127 ExpectErrorLogUtilCall single = description.getAnnotation(ExpectErrorLogUtilCall.class); 128 return single == null ? ImmutableList.of() : ImmutableList.of(single); 129 } 130 getTotalExpectedLogCalls(Description description)131 private int getTotalExpectedLogCalls(Description description) { 132 return getExpectedLogCalls(description).stream().mapToInt(call -> call.mTimes).sum(); 133 } 134 } 135