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 { 35 override fun getApplicableUastTypes(): List<Class<out UElement>> { 36 return listOf(UAnnotation::class.java) 37 } 38 39 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 = 47 """Please attach a bug id to track demoted test, """ + 48 """e.g. @FlakyTest(bugId = 123)""" 49 context.report(ISSUE, node, location, message) 50 } 51 } 52 // @Ignore has a String field for specifying reasons 53 if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) { 54 if (!containsBugString(node)) { 55 val location = context.getLocation(node) 56 val message = 57 """Please attach a bug to track demoted test, e.g. @Ignore("b/123")""" 58 context.report(ISSUE, node, location, message) 59 } 60 } 61 } 62 } 63 } 64 65 private fun containsBugId(node: UAnnotation): Boolean { 66 val bugId = node.findAttributeValue("bugId")?.evaluate() as Int? 67 return bugId != null && bugId > 0 68 } 69 70 private fun containsBugString(node: UAnnotation): Boolean { 71 val reason = node.findAttributeValue("value")?.evaluate() as String? 72 val bugPattern = Pattern.compile("b/\\d+") 73 return reason != null && bugPattern.matcher(reason).find() 74 } 75 76 companion object { 77 val DEMOTING_ANNOTATION_BUG_ID = 78 listOf( 79 "androidx.test.filters.FlakyTest", 80 "android.platform.test.annotations.FlakyTest", 81 "android.platform.test.rule.PlatinumRule.Platinum", 82 ) 83 84 const val DEMOTING_ANNOTATION_IGNORE = "org.junit.Ignore" 85 86 @JvmField 87 val ISSUE: Issue = 88 Issue.create( 89 id = "DemotingTestWithoutBug", 90 briefDescription = "Demoting a test without attaching a bug.", 91 explanation = 92 """ 93 Annotations (`@FlakyTest`) demote tests to an unmonitored \ 94 test suite. Please set the `bugId` field in such annotations to track \ 95 the test status. 96 """, 97 category = Category.TESTING, 98 priority = 8, 99 severity = Severity.WARNING, 100 implementation = 101 Implementation( 102 DemotingTestWithoutBugDetector::class.java, 103 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 104 ) 105 ) 106 } 107 } 108