test project confirming that lint checks are running

Bug: 177437928
Test: ./gradlew buildOnServer --dry-run 2>&1 | grep validateLint # to confirm this task runs
Test: ./gradlew validateLint

Change-Id: Ice436382f6abb3a07fd0e8ec4beee7e71e8b1457
diff --git a/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
index 158065e..4ce1461 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -74,17 +74,20 @@
         project.rootProject.project(":lint-checks")
     )
 
+    // The purpose of this specific project is to test that lint is running, so
+    // it contains expected violations that we do not want to trigger a build failure
+    val isTestingLintItself = (project.path == ":lint-checks:integration-tests")
+
     // If -PupdateLintBaseline was set we should update the baseline if it exists
-    val updateLintBaseline = hasProperty(UPDATE_LINT_BASELINE)
+    val updateLintBaseline = hasProperty(UPDATE_LINT_BASELINE) && !isTestingLintItself
 
     // Lint is configured entirely in afterEvaluate so that individual projects cannot easily
-    // disable individual checks in the DSL for any reason. That being said, when rolling out a new
-    // check as fatal, it can be beneficial to set it to fatal above this comment. This allows you
-    // to override it in a build script rather than messing with the baseline files. This is
-    // especially relevant for checks which cause hundreds or more failures.
+    // disable individual checks in the DSL for any reason.
     afterEvaluate {
         lintOptions.apply {
-            isAbortOnError = true
+            if (!isTestingLintItself) {
+                isAbortOnError = true
+            }
             isIgnoreWarnings = true
 
             // Workaround for b/177359055 where 27.2.0-beta04 incorrectly computes severity.
diff --git a/lint-checks/integration-tests/build.gradle b/lint-checks/integration-tests/build.gradle
new file mode 100644
index 0000000..4c80155
--- /dev/null
+++ b/lint-checks/integration-tests/build.gradle
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.BuildOnServerKt
+import androidx.build.uptodatedness.EnableCachingKt
+import org.apache.tools.ant.filters.ReplaceTokens
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+}
+
+dependencies {
+    implementation("androidx.annotation:annotation:1.0.0")
+}
+
+androidx {
+    name = "Lint Checks Integration Tests"
+    description = "This is a sample library for confirming that lint checks execute correctly, b/177437928"
+}
+
+android {
+    lintOptions {
+        // lint is supposed to detect errors in this project
+        // We don't need to see the errors in stdout
+        textOutput("${buildDir}/lint-output.txt")
+        // We don't want errors to cause lint to fail
+        abortOnError false
+    }
+}
+
+
+class CompareFilesTask extends DefaultTask {
+    @InputFile
+    File actualFile
+    @InputFile
+    File expectedFile
+
+    @TaskAction
+    def compare() {
+        def actualResults = actualFile.text
+        def expectedResults = expectedFile.text
+        if (actualResults != expectedResults) {
+            throw new GradleException("Incorrect lint results.\n" +
+                "\n" +
+                "Actual   text: '" + actualResults + "'\n" +
+                "\n" +
+                "Expected text: '" + expectedResults + "'\n" +
+                "\n" +
+                "Are all lint checks running?\n" +
+                "\n" +
+                "Actual   output at: " + actualFile + "\n" +
+                "Expected output at: " + expectedFile + "\n")
+        }
+    }
+}
+
+def lintOutputFile = project.file("${buildDir}/reports/lint-results-debug.xml")
+def lintOutputFileNormalized = project.file("${buildDir}/lint-results-normalized/lint-results-debug.xml.normalized")
+
+def normalizeLintOutput = tasks.register("normalizeLintOutput", Copy) {
+    from(lintOutputFile) {
+        filter { line ->
+            return line.replace("${project.rootProject.projectDir}", "\$SUPPORT")
+        }
+    }
+    into(lintOutputFileNormalized.parentFile)
+    rename(".*", lintOutputFileNormalized.name)
+    dependsOn("lintDebug")
+}
+
+def validateLint = tasks.register("validateLint", CompareFilesTask) { task ->
+    task.actualFile = lintOutputFileNormalized
+    task.expectedFile = project.file("expected-lint-results.xml")
+    EnableCachingKt.cacheEvenIfNoOutputs(task)
+    dependsOn(normalizeLintOutput)
+}
+
+BuildOnServerKt.addToBuildOnServer(project, validateLint)
diff --git a/lint-checks/integration-tests/expected-lint-results.xml b/lint-checks/integration-tests/expected-lint-results.xml
new file mode 100644
index 0000000..4820c1f
--- /dev/null
+++ b/lint-checks/integration-tests/expected-lint-results.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.2.0-beta04">
+
+    <issue
+        id="BanConcurrentHashMap"
+        severity="Error"
+        message="Detected ConcurrentHashMap usage."
+        category="Correctness"
+        priority="5"
+        summary="ConcurrentHashMap usage is not allowed"
+        explanation="ConcurrentHashMap has an issue on Android’s Lollipop release that can lead to lost updates under thread contention."
+        errorLine1="import java.util.concurrent.ConcurrentHashMap;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="$SUPPORT/lint-checks/integration-tests/src/main/java/Sample.java"
+            line="19"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        severity="Fatal"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        category="Interoperability:Kotlin Interoperability"
+        priority="6"
+        summary="Unknown nullness"
+        explanation="To improve referencing this code from Kotlin, consider adding&#xA;explicit nullness information here with either `@NonNull` or `@Nullable`.&#xA;&#xA;You can set the environment variable&#xA;    `ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED=true`&#xA;if you want lint to ignore classes and members that have been annotated with&#xA;`@Deprecated`."
+        url="https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        urls="https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public static Sample confirmIntrinisicLintChecksRun() {"
+        errorLine2="                  ~~~~~~">
+        <location
+            file="$SUPPORT/lint-checks/integration-tests/src/main/java/Sample.java"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        severity="Fatal"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        category="Interoperability:Kotlin Interoperability"
+        priority="6"
+        summary="Unknown nullness"
+        explanation="To improve referencing this code from Kotlin, consider adding&#xA;explicit nullness information here with either `@NonNull` or `@Nullable`.&#xA;&#xA;You can set the environment variable&#xA;    `ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED=true`&#xA;if you want lint to ignore classes and members that have been annotated with&#xA;`@Deprecated`."
+        url="https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        urls="https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public static void confirmCustomAndroidXChecksRun(ConcurrentHashMap m) {"
+        errorLine2="                                                      ~~~~~~~~~~~~~~~~~">
+        <location
+            file="$SUPPORT/lint-checks/integration-tests/src/main/java/Sample.java"
+            line="37"
+            column="55"/>
+    </issue>
+
+</issues>
diff --git a/lint-checks/integration-tests/src/main/AndroidManifest.xml b/lint-checks/integration-tests/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1a39913
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest package="androidx.lint.integration.tests" />
diff --git a/lint-checks/integration-tests/src/main/java/Sample.java b/lint-checks/integration-tests/src/main/java/Sample.java
new file mode 100644
index 0000000..5d2ba27
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/Sample.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Sample {
+
+    /**
+     * This function does not specify the nullability of its return type.
+     * Lint should catch this and report an error.
+     * If Lint does not catch this, then Lint's intrinsic checks are not running
+     */
+    public static Sample confirmIntrinisicLintChecksRun() {
+        return null;
+    }
+
+    /**
+     * This function uses a disallowed annotation
+     * Lint should catch this and report an error.
+     * If Lint does not catch this, then our AndroidX-specific checks are not running
+     */
+    public static void confirmCustomAndroidXChecksRun(ConcurrentHashMap m) {
+    }
+
+    private Sample() {
+    }
+}
diff --git a/settings.gradle b/settings.gradle
index d133174..326b1cd 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -383,6 +383,7 @@
 includeProject(":lifecycle:lifecycle-viewmodel-ktx", "lifecycle/lifecycle-viewmodel-ktx", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":lifecycle:lifecycle-viewmodel-savedstate", "lifecycle/lifecycle-viewmodel-savedstate", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":lint-checks", "lint-checks")
+includeProject(":lint-checks:integration-tests", "lint-checks/integration-tests", [BuildType.COMPOSE])
 includeProject(":lint-checks:tests", "lint-checks/tests", [BuildType.MAIN])
 includeProject(":lint-demos:lint-demo-appcompat", "lint-demos/lint-demo-appcompat", [BuildType.MAIN])
 includeProject(":loader:loader", "loader/loader", [BuildType.MAIN])