1 /* <lambda>null2 * 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 android.adservices.lint.test 18 19 import android.adservices.lint.test.ErrorLogUtilMockingUsageDetector.Constants.ERROR_LOG_UTIL 20 import android.adservices.lint.test.ErrorLogUtilMockingUsageDetector.Constants.INVALID_ANNOTATION_MESSAGE 21 import android.adservices.lint.test.ErrorLogUtilMockingUsageDetector.Constants.INVALID_MOCKING_MESSAGE 22 import android.adservices.lint.test.ErrorLogUtilMockingUsageDetector.Constants.MOCK_STATIC_ANNOTATION 23 import android.adservices.lint.test.ErrorLogUtilMockingUsageDetector.Constants.SPY_STATIC_ANNOTATION 24 import android.adservices.lint.test.ErrorLogUtilMockingUsageDetector.Constants.SUPERCLASS 25 import com.android.tools.lint.client.api.UElementHandler 26 import com.android.tools.lint.detector.api.* 27 import com.intellij.psi.PsiMethod 28 import org.jetbrains.uast.* 29 30 class ErrorLogUtilMockingUsageDetector : Detector(), SourceCodeScanner { 31 object Constants { 32 const val MOCK_STATIC_ANNOTATION = "MockStatic" 33 const val SPY_STATIC_ANNOTATION = "SpyStatic" 34 const val ERROR_LOG_UTIL = "ErrorLogUtil" 35 const val INVALID_ANNOTATION_MESSAGE = "Do not spy or mock ErrorLogUtil" 36 const val INVALID_MOCKING_MESSAGE = "Do not mock ErrorLogUtil behavior" 37 38 // Only allow ErrorLogUtil spy/mock annotation in the superclass 39 const val SUPERCLASS = "AdServicesExtendedMockitoTestCase" 40 } 41 42 override fun getApplicableUastTypes() = listOf(UAnnotation::class.java) 43 44 override fun getApplicableMethodNames(): List<String> { 45 return listOf("doNothingOnErrorLogUtilError", "when") 46 } 47 48 override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { 49 if (method.name == "when" && !isMockingErrorLogUtilInvocation(node)) { 50 return 51 } 52 53 if (isSubclassOfAdServicesExtendedMockitoTestCase(findUClass(node))) { 54 context.report( 55 MOCKING_INVOCATION_ISSUE, 56 node, 57 context.getLocation(node), 58 INVALID_MOCKING_MESSAGE 59 ) 60 } 61 } 62 63 override fun createUastHandler(context: JavaContext): UElementHandler? { 64 return object : UElementHandler() { 65 override fun visitAnnotation(node: UAnnotation) { 66 if (isMockOrSpyStaticAnnotation(node)) { 67 val annotationArgs = node.attributeValues 68 annotationArgs.forEach { arg -> 69 val argValue = arg.expression.asSourceString() 70 if ( 71 argValue.contains(ERROR_LOG_UTIL) && 72 findUClass(node)?.name != SUPERCLASS 73 ) { 74 context.report( 75 INVALID_ANNOTATION_ISSUE, 76 node, 77 context.getLocation(node), 78 INVALID_ANNOTATION_MESSAGE 79 ) 80 } 81 } 82 } 83 } 84 } 85 } 86 87 private fun isMockOrSpyStaticAnnotation(node: UAnnotation): Boolean { 88 val annotation = node.qualifiedName?.substringAfterLast('.') 89 return annotation == MOCK_STATIC_ANNOTATION || annotation == SPY_STATIC_ANNOTATION 90 } 91 92 private fun findUClass(node: UElement): UClass? { 93 var parent: UElement? = node.uastParent 94 while (parent != null) { 95 if (parent is UClass) { 96 return parent 97 } 98 parent = parent.uastParent 99 } 100 101 return null // UClass not found 102 } 103 104 private fun isSubclassOfAdServicesExtendedMockitoTestCase(node: UClass?): Boolean { 105 var currentClass = node 106 while (currentClass != null) { 107 val superClass = currentClass.javaPsi.superClass ?: break 108 if (superClass.name == SUPERCLASS) { 109 return true 110 } 111 currentClass = 112 UastFacade.convertElementWithParent(superClass, UClass::class.java) as? UClass 113 } 114 115 return false 116 } 117 118 private fun isMockingErrorLogUtilInvocation(node: UCallExpression): Boolean { 119 return node.valueArguments.size == 1 && 120 node.valueArguments[0].asSourceString().contains(ERROR_LOG_UTIL) 121 } 122 123 companion object Issues { 124 val INVALID_ANNOTATION_ISSUE = 125 Issue.create( 126 id = "DoNotMockOrSpyErrorLogUtil", 127 briefDescription = 128 "Do not define @SpyStatic(ErrorLogUtil.class) or @MockStatic(ErrorLogUtil.class)", 129 explanation = 130 """ 131 All subclasses that extend AdServicesExtendedMockitoTestCase are 132 automatically configured to spy ErrorLogUtil for AdServicesLoggingUsageRule. 133 Simply use @ExpectErrorLogUtilCall and/or @ExpectErrorLogUtilWithExceptionCall 134 directly over test methods to verify ErrorLogUtil calls. 135 """, 136 moreInfo = "documentation/ErrorLogUtilMockingUsageDetector.md", 137 category = Category.COMPLIANCE, 138 severity = Severity.ERROR, 139 implementation = 140 Implementation( 141 ErrorLogUtilMockingUsageDetector::class.java, 142 Scope.JAVA_FILE_SCOPE 143 ) 144 ) 145 146 val MOCKING_INVOCATION_ISSUE = 147 Issue.create( 148 id = "DoNotMockErrorLogUtilBehavior", 149 briefDescription = "Do not mock ErrorLogUtil invocation behavior explicitly", 150 explanation = 151 """ 152 All subclasses that extend AdServicesExtendedMockitoTestCase are 153 automatically configured to spy ErrorLogUtil for AdServicesLoggingUsageRule. 154 ErrorLogUtil invocation behavior is already mocked in a specific way for the 155 rule to work; do not override it by mocking ErrorLogUtil behavior. Simply use 156 @ExpectErrorLogUtilCall and/or @ExpectErrorLogUtilWithExceptionCall directly 157 over test methods to verify ErrorLogUtil calls. 158 """, 159 moreInfo = "documentation/ErrorLogUtilMockingUsageDetector.md", 160 category = Category.COMPLIANCE, 161 severity = Severity.ERROR, 162 implementation = 163 Implementation( 164 ErrorLogUtilMockingUsageDetector::class.java, 165 Scope.JAVA_FILE_SCOPE 166 ) 167 ) 168 } 169 } 170