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