1 /* 2 * Copyright (C) 2023 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.internal.systemui.lint 18 19 import com.android.tools.lint.client.api.UElementHandler 20 import com.android.tools.lint.detector.api.Category 21 import com.android.tools.lint.detector.api.Detector 22 import com.android.tools.lint.detector.api.Implementation 23 import com.android.tools.lint.detector.api.Issue 24 import com.android.tools.lint.detector.api.JavaContext 25 import com.android.tools.lint.detector.api.Scope 26 import com.android.tools.lint.detector.api.Severity 27 import com.android.tools.lint.detector.api.SourceCodeScanner 28 import java.util.EnumSet 29 import java.util.regex.Pattern 30 import org.jetbrains.uast.UAnnotation 31 import org.jetbrains.uast.UElement 32 33 @Suppress("UnstableApiUsage") // For linter api 34 class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner { getApplicableUastTypesnull35 override fun getApplicableUastTypes(): List<Class<out UElement>> { 36 return listOf(UAnnotation::class.java) 37 } 38 createUastHandlernull39 override fun createUastHandler(context: JavaContext): UElementHandler { 40 return object : UElementHandler() { 41 override fun visitAnnotation(node: UAnnotation) { 42 // Annotations having int bugId field 43 if (node.qualifiedName in DEMOTING_ANNOTATION_BUG_ID) { 44 if (!containsBugId(node)) { 45 val location = context.getLocation(node) 46 val message = "Please attach a bug id to track demoted test" 47 context.report(ISSUE, node, location, message) 48 } 49 } 50 // @Ignore has a String field for specifying reasons 51 if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) { 52 if (!containsBugString(node)) { 53 val location = context.getLocation(node) 54 val message = "Please attach a bug (e.g. b/123) to track demoted test" 55 context.report(ISSUE, node, location, message) 56 } 57 } 58 } 59 } 60 } 61 containsBugIdnull62 private fun containsBugId(node: UAnnotation): Boolean { 63 val bugId = node.findAttributeValue("bugId")?.evaluate() as Int? 64 return bugId != null && bugId > 0 65 } 66 containsBugStringnull67 private fun containsBugString(node: UAnnotation): Boolean { 68 val reason = node.findAttributeValue("value")?.evaluate() as String? 69 val bugPattern = Pattern.compile("b/\\d+") 70 return reason != null && bugPattern.matcher(reason).find() 71 } 72 73 companion object { 74 val DEMOTING_ANNOTATION_BUG_ID = 75 listOf( 76 "androidx.test.filters.FlakyTest", 77 "android.platform.test.annotations.FlakyTest", 78 "android.platform.test.rule.PlatinumRule.Platinum", 79 ) 80 81 const val DEMOTING_ANNOTATION_IGNORE = "org.junit.Ignore" 82 83 @JvmField 84 val ISSUE: Issue = 85 Issue.create( 86 id = "DemotingTestWithoutBug", 87 briefDescription = "Demoting a test without attaching a bug.", 88 explanation = 89 """ 90 Annotations (`@FlakyTest`) demote tests to an unmonitored \ 91 test suite. Please set the `bugId` field in such annotations to track \ 92 the test status. 93 """, 94 category = Category.TESTING, 95 priority = 8, 96 severity = Severity.WARNING, 97 implementation = 98 Implementation( 99 DemotingTestWithoutBugDetector::class.java, 100 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 101 ) 102 ) 103 } 104 } 105