From 40c99d5841b8c8740607c52c37cff2d6e13a5e11 Mon Sep 17 00:00:00 2001 From: arunkumar9t2 Date: Thu, 18 Apr 2024 11:11:13 +0800 Subject: [PATCH] Implement `junit` compatible report for Android Lint (#167) Fixes #167 --- project_views/default.bazelproject | 8 + rules/android/lint/lint_aspect.bzl | 9 +- rules/android/lint/lint_test.bzl | 15 +- rules/android/lint/providers.bzl | 1 + tools/cli_utils/BUILD.bazel | 2 - .../src/main/java/com/grab/lint/Document.kt | 9 + .../lint/src/main/java/com/grab/lint/File.kt | 22 -- .../main/java/com/grab/lint/LintDependency.kt | 1 - .../java/com/grab/lint/LintReportCommand.kt | 27 +- .../main/java/com/grab/lint/LintResults.kt | 123 +++++- .../java/com/grab/lint/LintResultsTest.kt | 361 +++++++++--------- 11 files changed, 342 insertions(+), 236 deletions(-) create mode 100644 tools/lint/src/main/java/com/grab/lint/Document.kt delete mode 100644 tools/lint/src/main/java/com/grab/lint/File.kt diff --git a/project_views/default.bazelproject b/project_views/default.bazelproject index 99b217ed..53da3bcc 100644 --- a/project_views/default.bazelproject +++ b/project_views/default.bazelproject @@ -1,6 +1,11 @@ directories: . + -bazel-bin + -bazel-grab-bazel-common + -bazel-out -bazel-cache + -bazel-testlogs + -bazel-genfiles # Automatically includes all relevant targets under the 'directories' above derive_targets_from_directories: true @@ -9,6 +14,9 @@ workspace_type: java java_language_level: 11 +test_sources: + */src/test/* + additional_languages: java kotlin diff --git a/rules/android/lint/lint_aspect.bzl b/rules/android/lint/lint_aspect.bzl index a882cc52..c8c84251 100644 --- a/rules/android/lint/lint_aspect.bzl +++ b/rules/android/lint/lint_aspect.bzl @@ -360,6 +360,7 @@ def _lint_report_action( fail_on_warning, fail_on_information, lint_result_xml_file, + lint_result_junit_xml_file, partial_results_dir, jdk_home, project_xml_file, @@ -402,7 +403,8 @@ def _lint_report_action( args.add("--fail-on-warning", fail_on_warning) args.add("--fail-on-information", fail_on_information) - args.add("--output-xml", lint_result_xml_file.path) + args.add("--output-xml", lint_result_xml_file) + args.add("--output-junit-xml", lint_result_junit_xml_file) args.add("--result-code", result_code) mnemonic = "AndroidLint" @@ -458,6 +460,7 @@ def _lint_aspect_impl(target, ctx): lint_results_dir = ctx.actions.declare_directory("lint/" + target.label.name + "_results_dir") lint_result_xml_file = ctx.actions.declare_file("lint/" + target.label.name + "_lint_result.xml") + lint_result_junit_xml_file = ctx.actions.declare_file("lint/" + target.label.name + "_lint_result_junit.xml") lint_result_code_file = ctx.actions.declare_file("lint/" + target.label.name + "_lint_result_code") # Project Xmls @@ -573,6 +576,7 @@ def _lint_aspect_impl(target, ctx): fail_on_warning = sources.fail_on_warning, fail_on_information = sources.fail_on_information, lint_result_xml_file = lint_result_xml_file, + lint_result_junit_xml_file = lint_result_junit_xml_file, partial_results_dir = lint_results_dir, models_dir = lint_models_dir, jdk_home = java_runtime_info.java_home, @@ -599,6 +603,7 @@ def _lint_aspect_impl(target, ctx): outputs = [ lint_results_dir, lint_result_xml_file, + lint_result_junit_xml_file, lint_updated_baseline_file, lint_result_code_file, ], @@ -612,6 +617,7 @@ def _lint_aspect_impl(target, ctx): partial_results_dir = lint_partial_results_dir, models_dir = lint_models_dir, lint_result_xml = lint_result_xml_file, + lint_junit_xml = lint_result_junit_xml_file, result_code = lint_result_code_file, updated_baseline = lint_updated_baseline_file, ) @@ -623,6 +629,7 @@ def _lint_aspect_impl(target, ctx): enabled = enabled, partial_results_dir = None, lint_result_xml = None, + lint_junit_xml = None, result_code = None, updated_baseline = None, ) diff --git a/rules/android/lint/lint_test.bzl b/rules/android/lint/lint_test.bzl index b0f7d5be..c859e510 100644 --- a/rules/android/lint/lint_test.bzl +++ b/rules/android/lint/lint_test.bzl @@ -7,6 +7,7 @@ def _lint_test_impl(ctx): executable = ctx.actions.declare_file("%s_lint.sh" % target.label.name) + lint_junit_xml_file = lint_info.lint_junit_xml lint_result_xml_file = ctx.outputs.lint_result lint_result_code = lint_info.result_code if lint_info.enabled: @@ -15,8 +16,12 @@ def _lint_test_impl(ctx): output = lint_result_xml_file, ) else: - lint_result_code = ctx.actions.declare_file("%s_result_code" % target.label.name) + lint_junit_xml_file = ctx.actions.declare_file("%s_junit.xml" % target.label.name) + ctx.actions.write(output = lint_junit_xml_file, content = "") + ctx.actions.write(output = lint_result_xml_file, content = "") + + lint_result_code = ctx.actions.declare_file("%s_result_code" % target.label.name) ctx.actions.write(output = lint_result_code, content = "0") ctx.actions.write( @@ -25,9 +30,11 @@ def _lint_test_impl(ctx): content = """ #!/bin/bash cat {lint_result_xml_file} +cp {lint_junit_xml_file} $XML_OUTPUT_FILE exit $(cat {lint_result_code}) """.format( lint_result_xml_file = lint_result_xml_file.short_path, + lint_junit_xml_file = lint_junit_xml_file.short_path, lint_result_code = lint_result_code.short_path, ), ) @@ -35,7 +42,11 @@ exit $(cat {lint_result_code}) return [ DefaultInfo( executable = executable, - runfiles = ctx.runfiles(files = [lint_result_xml_file, lint_result_code]), + runfiles = ctx.runfiles(files = [ + lint_junit_xml_file, + lint_result_xml_file, + lint_result_code, + ]), files = depset([ ctx.outputs.lint_result, ]), diff --git a/rules/android/lint/providers.bzl b/rules/android/lint/providers.bzl index 32c242c2..883f7430 100644 --- a/rules/android/lint/providers.bzl +++ b/rules/android/lint/providers.bzl @@ -10,6 +10,7 @@ AndroidLintNodeInfo = provider( models_dir = "Lint models directory", updated_baseline = "The updated baseline XML", lint_result_xml = "The lint results XML file", + lint_junit_xml = "Lint result formatted in Junit format", ), ) diff --git a/tools/cli_utils/BUILD.bazel b/tools/cli_utils/BUILD.bazel index 09fac063..1d673925 100644 --- a/tools/cli_utils/BUILD.bazel +++ b/tools/cli_utils/BUILD.bazel @@ -8,6 +8,4 @@ kotlin_library( visibility = [ "//visibility:public", ], - deps = [ - ], ) diff --git a/tools/lint/src/main/java/com/grab/lint/Document.kt b/tools/lint/src/main/java/com/grab/lint/Document.kt new file mode 100644 index 00000000..8e508f71 --- /dev/null +++ b/tools/lint/src/main/java/com/grab/lint/Document.kt @@ -0,0 +1,9 @@ +package com.grab.lint + +import org.w3c.dom.Element +import org.w3c.dom.NodeList + +fun NodeList.elements() = (0 until length).map { item(it) as Element } + +operator fun Element.get(name: String): String = getAttribute(name) +operator fun Element.set(name: String, value: String) = setAttribute(name, value) \ No newline at end of file diff --git a/tools/lint/src/main/java/com/grab/lint/File.kt b/tools/lint/src/main/java/com/grab/lint/File.kt deleted file mode 100644 index b4173908..00000000 --- a/tools/lint/src/main/java/com/grab/lint/File.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.grab.lint - -import org.apache.commons.io.FileUtils -import java.io.File -import java.nio.file.Path - -/** - * Ensures files in [originalDir] are converted to real files instead of being symlinks. This is done by staging them in a temp dir - * under [tmpDir] and copying back to [originalDir] - * - * This is needed since Lint is adamant on resolving and using canonical paths and that sometimes throws permissions errors - */ -fun resolveSymlinks(originalDir: File, tmpDir: Path): File = try { - val tmp = tmpDir.resolve("partial_results_dir").toFile() - FileUtils.copyDirectory(originalDir, tmp) - FileUtils.deleteDirectory(originalDir) - FileUtils.copyDirectory(tmp, originalDir) - originalDir -} catch (e: Exception) { - e.printStackTrace() - originalDir -} \ No newline at end of file diff --git a/tools/lint/src/main/java/com/grab/lint/LintDependency.kt b/tools/lint/src/main/java/com/grab/lint/LintDependency.kt index dc9d78cf..3bd17c09 100644 --- a/tools/lint/src/main/java/com/grab/lint/LintDependency.kt +++ b/tools/lint/src/main/java/com/grab/lint/LintDependency.kt @@ -12,7 +12,6 @@ data class LintDependency( companion object { fun from(encodedString: String): LintDependency { val (name, android, library, partialResultsDir, modelsDir) = encodedString.split("^") - // val partialResults = resolveSymlinks(File(partialResultsDir), workingDir) return LintDependency( name = name, android = android.toBoolean(), diff --git a/tools/lint/src/main/java/com/grab/lint/LintReportCommand.kt b/tools/lint/src/main/java/com/grab/lint/LintReportCommand.kt index ae786ad5..59055ad9 100644 --- a/tools/lint/src/main/java/com/grab/lint/LintReportCommand.kt +++ b/tools/lint/src/main/java/com/grab/lint/LintReportCommand.kt @@ -7,6 +7,7 @@ import com.github.ajalt.clikt.parameters.options.required import java.io.File import java.nio.file.Path import kotlin.io.path.pathString +import kotlin.system.measureTimeMillis import com.android.tools.lint.Main as LintCli class LintReportCommand : LintBaseCommand() { @@ -17,12 +18,18 @@ class LintReportCommand : LintBaseCommand() { help = "The lint baseline file" ).convert { File(it) }.required() - private val outputXml by option( + private val lintResultXml by option( "-o", "--output-xml", help = "Lint output xml" ).convert { File(it) }.required() + private val outputJunitXml by option( + "-oj", + "--output-junit-xml", + help = "Lint output in Junit format" + ).convert { File(it) }.required() + private val resultCode by option( "-rc", "--result-code", @@ -46,13 +53,18 @@ class LintReportCommand : LintBaseCommand() { projectXml: File, tmpBaseline: File, ) { - val newBaseline = runLint(workingDir, projectXml, tmpBaseline) - newBaseline.copyTo(updatedBaseline) + val elapsed = measureTimeMillis { + runLint(workingDir, projectXml, tmpBaseline, lintResultXml) + } + tmpBaseline.copyTo(updatedBaseline) LintResults( + name = name, + lintResultsFile = lintResultXml, + elapsed = elapsed, resultCodeFile = resultCode, - lintResultsFile = outputXml, - failOnWarnings = failOnWarnings, + outputJunitXml = outputJunitXml, failOnInformation = failOnInformation, + failOnWarnings = failOnWarnings, ).process() } @@ -62,10 +74,10 @@ class LintReportCommand : LintBaseCommand() { override val createProjectXml: Boolean = false - private fun runLint(workingDir: Path, projectXml: File, tmpBaseline: File): File { + private fun runLint(workingDir: Path, projectXml: File, tmpBaseline: File, lintResultXml: File) { val cliArgs = (defaultLintOptions + listOf( "--project", projectXml.toString(), - "--xml", outputXml.toString(), + "--xml", lintResultXml.toString(), "--baseline", tmpBaseline.absolutePath, "--path-variables", pathVariables, "--cache-dir", workingDir.resolve("cache").pathString, @@ -73,6 +85,5 @@ class LintReportCommand : LintBaseCommand() { "--report-only" // Only do reporting )).toTypedArray() LintCli().run(cliArgs) - return tmpBaseline } } \ No newline at end of file diff --git a/tools/lint/src/main/java/com/grab/lint/LintResults.kt b/tools/lint/src/main/java/com/grab/lint/LintResults.kt index 01c256bb..893a0ffd 100644 --- a/tools/lint/src/main/java/com/grab/lint/LintResults.kt +++ b/tools/lint/src/main/java/com/grab/lint/LintResults.kt @@ -1,38 +1,125 @@ package com.grab.lint import org.w3c.dom.Element -import org.w3c.dom.NodeList import java.io.File +import java.io.FileWriter +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import java.util.concurrent.TimeUnit +import javax.xml.parsers.DocumentBuilder import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.Transformer +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult class LintResults( - val resultCodeFile: File, - val lintResultsFile: File, - val failOnWarnings: Boolean = true, - val failOnInformation: Boolean = true, + private val name: String, + private val lintResultsFile: File, + private val elapsed: Long, + private val resultCodeFile: File, + private val outputJunitXml: File, + private val failOnInformation: Boolean = true, + private val failOnWarnings: Boolean = true, ) { - private fun NodeList.elements() = (0 until length).map { item(it) as Element } - fun process() { try { val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() val lintResults = documentBuilder.parse(lintResultsFile) - val issues = lintResults.getElementsByTagName("issue") - val hasErrors = issues.elements().any { - it.getAttribute("id") != "LintBaseline" && - it.getAttribute("id") != "LintError" && - ( - it.getAttribute("severity") == "Fatal" || - it.getAttribute("severity") == "Error" || - (failOnWarnings && it.getAttribute("severity") == "Warning") || - (failOnInformation && it.getAttribute("severity") == "Information") - ) + val issues = lintResults.getElementsByTagName("issue").elements() + val errorIssues = issues.filter { issue -> + val id = issue["id"] + val message = issue["message"] + val severity = issue["severity"] + when (id) { + in ALLOWED_ISSUES -> ALLOWED_ISSUES[id].toString() !in message // Only fail if expected message does not match + else -> when (severity) { + "Fatal", "Error" -> true + "Warning" -> failOnWarnings + "Information" -> failOnInformation + else -> true + } + } } + val hasErrors = errorIssues.isNotEmpty() + buildJunitReport(documentBuilder, errorIssues) resultCodeFile.writeText(if (hasErrors) "1" else "0") } catch (e: Exception) { - e.printStackTrace() + Files.copy( + lintResultsFile.toPath(), + outputJunitXml.toPath(), StandardCopyOption.REPLACE_EXISTING + ) resultCodeFile.writeText("1") } } + + private fun buildJunitReport(documentBuilder: DocumentBuilder, errorIssues: List) { + val junitDoc = documentBuilder.newDocument() + val testSuites = junitDoc.createElement("testsuites").also { + it["name"] = name + it["tests"] = errorIssues.size.toString() + it["time"] = TimeUnit.MILLISECONDS.toSeconds(elapsed).toString() + } + junitDoc.appendChild(testSuites) + + errorIssues.groupBy { it["id"] }.forEach { (id, issues) -> + val failures = issues.size.toString() + val testSuite = junitDoc.createElement("testsuite").also { + it["name"] = id + it["tests"] = failures + it["failures"] = failures + } + testSuites.appendChild(testSuite) + + issues.forEach { issue -> + val message = issue["message"] + val summary = issue["summary"] + val location = issue.getElementsByTagName("location").elements().first() + val file = location["file"].replace("../", "") + val line = location["line"] + val explanation = listOf( + issue["explanation"], + "\nFile: $file:$line", + "Error line:", + issue["errorLine1"], + issue["errorLine2"] + ).joinToString(separator = "\n") + + val testCase = junitDoc.createElement("testcase").also { + it["name"] = message + it["classname"] = summary + it["file"] = file + it["line"] = line + } + val failure = junitDoc.createElement("failure").also { + it["message"] = explanation + } + testCase.appendChild(failure) + testSuite.appendChild(testCase) + } + } + + try { + FileWriter(outputJunitXml).use { fileWriter -> + val transformer: Transformer = TransformerFactory.newInstance().newTransformer() + transformer.setOutputProperty(OutputKeys.INDENT, "yes") + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2") + transformer.transform(DOMSource(junitDoc), StreamResult(fileWriter)) + } + } catch (e: Exception) { + throw RuntimeException(e) + } + } + + companion object { + /** + * Map of Android Lint Issue id to message text that is recognized as success. + */ + private val ALLOWED_ISSUES = mapOf( + "LintBaseline" to "filtered out because", + "LintBaselineFixed" to "perhaps they have been fixed" + ) + } } \ No newline at end of file diff --git a/tools/lint/src/test/java/com/grab/lint/LintResultsTest.kt b/tools/lint/src/test/java/com/grab/lint/LintResultsTest.kt index 4d080311..1f6540ab 100644 --- a/tools/lint/src/test/java/com/grab/lint/LintResultsTest.kt +++ b/tools/lint/src/test/java/com/grab/lint/LintResultsTest.kt @@ -1,10 +1,12 @@ package com.grab.lint -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import org.w3c.dom.Document +import org.w3c.dom.Element import java.io.File +import javax.xml.parsers.DocumentBuilderFactory import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -13,15 +15,25 @@ class LintResultsTest { @get:Rule val temporaryFolder = TemporaryFolder() - private lateinit var resultXml: File + private lateinit var lintResultXml: File private lateinit var resultCode: File + private lateinit var outputJunitXml: File private lateinit var lintResults: LintResults - @Before - fun setup() { - resultXml = temporaryFolder.newFile("result.xml") + fun setup(failOnInformation: Boolean = true, failOnWarnings: Boolean = true, block: () -> Unit) { + lintResultXml = temporaryFolder.newFile("result.xml") resultCode = temporaryFolder.newFile("result_code") - lintResults = LintResults(resultCode, resultXml) + outputJunitXml = temporaryFolder.newFile("output.xml") + lintResults = LintResults( + name = "Test", + lintResultsFile = lintResultXml, + elapsed = 0L, + resultCodeFile = resultCode, + outputJunitXml = outputJunitXml, + failOnInformation = failOnInformation, + failOnWarnings = failOnWarnings + ) + block() } private fun assertResultCode(value: String, message: String) { @@ -30,186 +42,71 @@ class LintResultsTest { } @Test - fun `test lint xml errors are parsed and exit code is calculated`() { - resultXml.writeText( + fun `test empty lint xml is considered as pass`() = setup { + lintResultXml.writeText( """ - - - - - """.trimIndent() ) lintResults.process() - assertResultCode("0", "Result is 0 when only baseline entry is there.") + assertResultCode("0", "Result is 0 for empty xml") } @Test - fun `assert when Fail for Information severity is true, result code is 1`() { - lintResults = LintResults(resultCode, resultXml, failOnInformation = true) - resultXml.writeText( + fun `test lint xml errors are parsed and exit code is calculated`() = setup { + lintResultXml.writeText( """ - - - - + + $BASELINE_ISSUE + + $BASELINE_FIXED_ISSUE """.trimIndent() ) lintResults.process() - assertResultCode("1", "Result is 1 when additional errors are there") + assertResultCode("0", "Result is 0 when only baseline entry is there.") } @Test - fun `assert when Fail for Information severity is false, result code is 0`() { - lintResults = LintResults(resultCode, resultXml, failOnInformation = false) - resultXml.writeText( - """ - - - - - + fun `assert when Fail for Information severity is true, result code is 1`() = setup(failOnInformation = true) { + lintResultXml.writeText(INFORMATION_ISSUE) + lintResults.process() + assertResultCode("1", "Result is 1 when additional errors are there") + } - - """.trimIndent() - ) + @Test + fun `assert when Fail for Information severity is false, result code is 0`() = setup(failOnInformation = false) { + lintResultXml.writeText(INFORMATION_ISSUE) lintResults.process() assertResultCode("0", "Result is 0 when Fail for Information severity is false") } @Test - fun `assert when Fail for Warnings severity is true, result code is 1`() { - lintResults = LintResults(resultCode, resultXml, failOnWarnings = true) - resultXml.writeText( - """ - - - - - - - - """.trimIndent() - ) + fun `assert when Fail for Warnings severity is true, result code is 1`() = setup(failOnWarnings = true) { + lintResultXml.writeText(WARNING_ISSUE) lintResults.process() assertResultCode("1", "Result is 1 when Fail for Warnings severity is true") } @Test - fun `assert when Fail for Warning severity is false, result code is 0`() { - lintResults = LintResults(resultCode, resultXml, failOnWarnings = false) - resultXml.writeText( - """ - - - - - - - - """.trimIndent() - ) + fun `assert when Fail for Warning severity is false, result code is 0`() = setup(failOnWarnings = false) { + lintResultXml.writeText(WARNING_ISSUE) lintResults.process() assertResultCode("0", "Result is 0 when Fail for Warning severity is false") } @Test - fun `assert when any other error apart from LintBaseLine and LintError are there, result code is 1`() { - resultXml.writeText( + fun `assert when any other error apart from LintBaseLine are there, result code is 1`() = setup { + lintResultXml.writeText( """ - - + - - - + $BASELINE_ISSUE - - - - - - - + $LONGTAG_ISSUE """.trimIndent() @@ -219,51 +116,151 @@ class LintResultsTest { } @Test - fun `assert when there is no erro other than LintBaseLine and LintError, result code is 0`() { - resultXml.writeText( + fun `assert when result file is malformed or does not exist, result is 1`() = setup { + lintResults.process() + assertResultCode("1", "Result is 1 when file does not exist") + lintResultXml.writeText( """ + <<< + """.trimIndent() + ) + assertResultCode("1", "Result is 1 when file is malformed") + } + + + private fun Document.assertNode(nodeName: String, vararg expectedAttributes: Pair) { + val givenNode = getElementsByTagName(nodeName) + assertEquals(1, givenNode.length, "$nodeName is generated") + val nodeElement = givenNode.item(0) as Element + mapOf(*expectedAttributes).forEach { (attributeName, expected) -> + assertEquals(expected, nodeElement.getAttribute(attributeName), "$nodeName's $attributeName is added") + } + } + + @Test + fun `assert output junit xml is generated`() = setup { + lintResultXml.writeText(INFORMATION_ISSUE) + lintResults.process() + DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(outputJunitXml).apply { + assertNode( + "testsuites", + "name" to "Test", + "tests" to "1", + "time" to "0" + ) + assertNode( + "testsuite", + "name" to "MissingApplicationIcon", + "tests" to "1", + "failures" to "1" + ) + assertNode( + "testcase", + "name" to "Should explicitly set `android:icon`, there is no default", + "classname" to "Missing application icon", + "file" to "tests/android/binary/src/main/AndroidManifest.xml", + "line" to "4", + ) + assertNode( + "failure", + "message" to """You should set an icon for the application as whole because there is no default. This attribute must be set as a reference to a drawable resource containing the image (for example `@drawable/icon`). + +File: tests/android/binary/src/main/AndroidManifest.xml:4 +Error line: + + ~~~~~~~~~~~""" + ) + } + } + + companion object { + private val BASELINE_ISSUE = """ + + + + """.trimIndent() + + private val BASELINE_FIXED_ISSUE = """ + + + + """.trimIndent() + + private val LONGTAG_ISSUE = """ + + + + """.trimIndent() + + private val INFORMATION_ISSUE = """ - + message="Should explicitly set `android:icon`, there is no default" + category="Usability:Icons" + priority="5" + summary="Missing application icon" + explanation="You should set an icon for the application as whole because there is no default. This attribute must be set as a reference to a drawable resource containing the image (for example `@drawable/icon`)." + url="https://developer.android.com/studio/publish/preparing#publishing-configure" + urls="https://developer.android.com/studio/publish/preparing#publishing-configure" + errorLine1=" <application android:label="@string/app_name">" + errorLine2=" ~~~~~~~~~~~"> + file="../../../../../../../tests/android/binary/src/main/AndroidManifest.xml" + line="4" + column="6"/> - + + """.trimIndent() + + private val WARNING_ISSUE = """ + + + id="PrivateResource" + severity="Warning" + message="The resource `@color/material_blue_grey_950` is marked as private in material-1.2.1.aar" + category="Correctness" + priority="3" + summary="Using private resources" + explanation="Private resources should not be referenced; the may not be present everywhere, and even where they are they may disappear without notice. To fix this, copy the resource into your own project instead." + errorLine1=" val material_res = com.google.android.material.R.color.material_blue_grey_950" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + file="../../../../../../../tests/android/binary/src/main/java/com/grab/test/TestActivity.kt" + line="9" + column="60"/> - - """.trimIndent() - ) - lintResults.process() - assertResultCode("0", "Result is 0 when additional errors are there") - } - @Test - fun `assert when result file is malformed or does not exist, result is 1`() { - lintResults.process() - assertResultCode("1", "Result is 1 when file does not exist") - resultXml.writeText( - """ - <<< + """.trimIndent() - ) - assertResultCode("1", "Result is 1 when file is malformed") } } \ No newline at end of file