From 3e2a4a02f4d5971d24b8502b8d4cd76d806ea8af Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Sun, 13 Dec 2020 15:56:37 +0100 Subject: [PATCH] Report lint issues (#372) * Add missing lint plugin * Fix broken test * Introduce module metadata * Introduce module metadata * Introduce back lint * Disable lint unless ran explicitly * Add basic LintReport plugin with printing stuff * Try to add Lint to CI * Proper name to Lint job * Add proper analytics reporting * Add Lint report to scheduled job * Add Lint report to scheduled job * Add Lint report to check job to test End to End * Fix caching, remove lint report from pipeline and add reporting message --- .circleci/config.yml | 32 +++- app/build.gradle | 1 + build.gradle | 10 +- core-api/build.gradle | 1 + core-testing/build.gradle | 1 + feature/config-debug-api/build.gradle | 1 + feature/identity-api/build.gradle | 1 + feature/identity/build.gradle | 1 + feature/navigation-deeplink/build.gradle | 1 + plugins/build.gradle | 4 + .../com/jraska/lint/LintAnalyticsReporter.kt | 77 ++++++++ .../main/java/com/jraska/lint/LintIssue.kt | 42 +++++ .../com/jraska/lint/LintProjectExtractor.kt | 55 ++++++ .../java/com/jraska/lint/LintReportProcess.kt | 23 +++ .../com/jraska/lint/LintReporterPlugin.kt | 22 +++ .../java/com/jraska/lint/LintXmlParser.kt | 79 ++++++++ .../java/com/jraska/module/ModuleMetadata.kt | 12 ++ .../com/jraska/module/ModuleStatistics.kt | 12 +- .../com/jraska/module/ModuleStatsReporter.kt | 8 +- .../extract/StatisticsGradleExtractor.kt | 38 ++-- .../report/FirebaseResultExtractorTest.kt | 5 +- .../com/jraska/lint/LintXmlParserTest.kt | 178 ++++++++++++++++++ 22 files changed, 561 insertions(+), 43 deletions(-) create mode 100644 plugins/src/main/java/com/jraska/lint/LintAnalyticsReporter.kt create mode 100644 plugins/src/main/java/com/jraska/lint/LintIssue.kt create mode 100644 plugins/src/main/java/com/jraska/lint/LintProjectExtractor.kt create mode 100644 plugins/src/main/java/com/jraska/lint/LintReportProcess.kt create mode 100644 plugins/src/main/java/com/jraska/lint/LintReporterPlugin.kt create mode 100644 plugins/src/main/java/com/jraska/lint/LintXmlParser.kt create mode 100644 plugins/src/main/java/com/jraska/module/ModuleMetadata.kt create mode 100644 plugins/src/test/kotlin/com/jraska/lint/LintXmlParserTest.kt diff --git a/.circleci/config.yml b/.circleci/config.yml index 85c93102..389f6537 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,14 +9,14 @@ jobs: steps: - checkout - restore_cache: - key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}-{{ checksum "core/build.gradle" }}-{{ checksum "core-android-api/build.gradle" }} + key: cache-{{ checksum "build.gradle" }}-{{ checksum "dependencies.gradle" }}-{{ checksum "app/build.gradle" }} - run: name: Get Dependencies command: ./gradlew androidDependencies --max-workers=2 --stacktrace - save_cache: paths: - ~/.gradle - key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}-{{ checksum "core/build.gradle" }}-{{ checksum "core-android-api/build.gradle" }} + key: cache-{{ checksum "build.gradle" }}-{{ checksum "dependencies.gradle" }}-{{ checksum "app/build.gradle" }} - run: name: Run Tests command: ./gradlew check --max-workers=2 --stacktrace @@ -35,16 +35,37 @@ jobs: - run: name: Execute Data Processing command: ./gradlew reportModuleStatistics --max-workers=2 --stacktrace --no-configure-on-demand - - save_cache: - paths: - - ~/.gradle + lint: + environment: + JVM_OPTS: -Xmx4096m + docker: + - image: circleci/android:api-30 + steps: + - checkout + - restore_cache: key: cache-{{ checksum "build.gradle" }}-{{ checksum "dependencies.gradle" }}-{{ checksum "app/build.gradle" }} + - run: + name: Run Lint + command: ./gradlew lint --max-workers=2 --stacktrace + lintReport: + environment: + JVM_OPTS: -Xmx4096m + docker: + - image: circleci/android:api-30 + steps: + - checkout + - restore_cache: + key: cache-{{ checksum "build.gradle" }}-{{ checksum "dependencies.gradle" }}-{{ checksum "app/build.gradle" }} + - run: + name: Run Lint Report + command: ./gradlew lintStatisticsReport --max-workers=2 --stacktrace workflows: version: 2 check: jobs: - build + - lint statisticsReport: triggers: - schedule: @@ -55,4 +76,5 @@ workflows: - master jobs: - moduleStatisticsReport + - lintReport diff --git a/app/build.gradle b/app/build.gradle index 1b6117f5..34f3cc81 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,7 @@ plugins { id 'com.jraska.gradle.buildtime' id 'com.jraska.github.client.release' id 'com.jraska.module.stats' + id 'com.jraska.module.lint.report' } apply plugin: 'com.android.application' diff --git a/build.gradle b/build.gradle index b21ef3cd..c6bfce4b 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.1.1' - classpath ('com.google.firebase:firebase-plugins:2.0.0') { + classpath('com.google.firebase:firebase-plugins:2.0.0') { exclude group: 'com.google.api-client', module: 'google-api-client' // conflicts with com.github.triplet.play } classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' @@ -45,9 +45,11 @@ subprojects { } subprojects { - tasks.configureEach { - if (name.startsWith("lint")) { - enabled = false + if (!gradle.startParameter.taskNames.any { it.contains("lint") }) { + tasks.configureEach { + if (name.startsWith("lint")) { + enabled = false + } } } } diff --git a/core-api/build.gradle b/core-api/build.gradle index a616ee43..d5f25aa2 100644 --- a/core-api/build.gradle +++ b/core-api/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'com.android.lint' apply plugin: 'kotlin' dependencies { diff --git a/core-testing/build.gradle b/core-testing/build.gradle index d74e278f..b859a459 100644 --- a/core-testing/build.gradle +++ b/core-testing/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'com.android.lint' apply plugin: 'kotlin' apply plugin: 'kotlin-kapt' diff --git a/feature/config-debug-api/build.gradle b/feature/config-debug-api/build.gradle index ea5ab4d4..82f81cbe 100644 --- a/feature/config-debug-api/build.gradle +++ b/feature/config-debug-api/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'com.android.lint' apply plugin: 'kotlin' dependencies { diff --git a/feature/identity-api/build.gradle b/feature/identity-api/build.gradle index ea5ab4d4..82f81cbe 100644 --- a/feature/identity-api/build.gradle +++ b/feature/identity-api/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'com.android.lint' apply plugin: 'kotlin' dependencies { diff --git a/feature/identity/build.gradle b/feature/identity/build.gradle index 9b25c5d9..0572893f 100644 --- a/feature/identity/build.gradle +++ b/feature/identity/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'com.android.lint' apply plugin: 'kotlin' apply plugin: 'kotlin-kapt' diff --git a/feature/navigation-deeplink/build.gradle b/feature/navigation-deeplink/build.gradle index 2a539ef1..c97cfc82 100644 --- a/feature/navigation-deeplink/build.gradle +++ b/feature/navigation-deeplink/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'com.android.lint' apply plugin: 'kotlin' apply plugin: 'kotlin-kapt' diff --git a/plugins/build.gradle b/plugins/build.gradle index d8e2728b..00353f59 100644 --- a/plugins/build.gradle +++ b/plugins/build.gradle @@ -52,5 +52,9 @@ gradlePlugin { id = 'com.jraska.module.stats' implementationClass = 'com.jraska.module.ModuleStatsPlugin' } + lintReportPlugin { + id = 'com.jraska.module.lint.report' + implementationClass = 'com.jraska.lint.LintReporterPlugin' + } } } diff --git a/plugins/src/main/java/com/jraska/lint/LintAnalyticsReporter.kt b/plugins/src/main/java/com/jraska/lint/LintAnalyticsReporter.kt new file mode 100644 index 00000000..b2840724 --- /dev/null +++ b/plugins/src/main/java/com/jraska/lint/LintAnalyticsReporter.kt @@ -0,0 +1,77 @@ +package com.jraska.lint + +import com.jraska.analytics.AnalyticsEvent +import com.jraska.analytics.AnalyticsReporter + +class LintAnalyticsReporter( + private val analyticsReporter: AnalyticsReporter +) { + fun report(projectResult: LintProjectResult) { + val allEvents = mutableListOf(convertProjectResult(projectResult)) + + allEvents.addAll( + projectResult.moduleResults + .flatMap { convertModule(it) } + ) + + analyticsReporter.report(*allEvents.toTypedArray()) + + println("$GRAPH_ICON Lint results reported $GRAPH_ICON") + } + + private fun convertModule(moduleResult: LintModuleResult): List { + val moduleEvents = moduleResult.issues.map { convertIssue(it) }.toMutableList() + + moduleEvents.add( + AnalyticsEvent( + "Lint Module Result", + mapOf( + "totalIssues" to moduleResult.issuesCount, + "totalWarnings" to moduleResult.warningsCount, + "totalInfo" to moduleResult.infoCount, + "totalErrors" to moduleResult.errorsCount, + "totalIgnored" to moduleResult.ignoredCount, + "moduleName" to moduleResult.metadata.moduleName, + "moduleType" to moduleResult.metadata.type.name + ) + ) + ) + + return moduleEvents + } + + private fun convertIssue(issue: LintIssue): AnalyticsEvent { + return AnalyticsEvent( + "Lint Issue Result", + mapOf( + "category" to issue.category, + "errorLine" to issue.errorLine, + "issueId" to issue.id, + "location" to issue.location, + "message" to issue.message, + "moduleName" to issue.metadata.moduleName, + "moduleType" to issue.metadata.type.name, + "priority" to issue.priority, + "severity" to issue.severity.name, + "summary" to issue.summary, + ) + ) + } + + private fun convertProjectResult(projectResult: LintProjectResult): AnalyticsEvent { + return AnalyticsEvent( + "Lint Project Result", + mapOf( + "totalIssues" to projectResult.issuesCount, + "totalWarnings" to projectResult.warningsCount, + "totalInfo" to projectResult.infoCount, + "totalErrors" to projectResult.errorsCount, + "totalIgnored" to projectResult.ignoredCount, + ) + ) + } + + companion object { + private val GRAPH_ICON = "\uD83D\uDCC9" + } +} diff --git a/plugins/src/main/java/com/jraska/lint/LintIssue.kt b/plugins/src/main/java/com/jraska/lint/LintIssue.kt new file mode 100644 index 00000000..30080f0a --- /dev/null +++ b/plugins/src/main/java/com/jraska/lint/LintIssue.kt @@ -0,0 +1,42 @@ +package com.jraska.lint + +import com.jraska.module.ModuleMetadata + +data class LintIssue( + val metadata: ModuleMetadata, + val id: String, + val message: String, + val severity: Severity, + val category: String, + val priority: Int, + val summary: String, + val errorLine: String?, + val location: String? +) + +data class LintModuleResult( + val metadata: ModuleMetadata, + val issues: List, + val issuesCount: Int, + val errorsCount: Int, + val warningsCount: Int, + val infoCount: Int, + val ignoredCount: Int +) + +data class LintProjectResult( + val issuesCount: Int, + val errorsCount: Int, + val warningsCount: Int, + val infoCount: Int, + val ignoredCount: Int, + val moduleResults: List +) + +enum class Severity { + Error, + Warning, + Info, + Fatal, + Ignore +} diff --git a/plugins/src/main/java/com/jraska/lint/LintProjectExtractor.kt b/plugins/src/main/java/com/jraska/lint/LintProjectExtractor.kt new file mode 100644 index 00000000..3600ae6a --- /dev/null +++ b/plugins/src/main/java/com/jraska/lint/LintProjectExtractor.kt @@ -0,0 +1,55 @@ +package com.jraska.lint + +import com.jraska.module.extract.StatisticsGradleExtractor.Companion.moduleMetadata +import org.gradle.api.Project +import java.io.File + +class LintProjectExtractor { + fun extract(project: Project): LintProjectResult { + val moduleStats = project.rootProject + .subprojects + .filter { it.childProjects.isEmpty() } + .map { extractFromModule(it) } + + return LintProjectResult( + moduleResults = moduleStats, + issuesCount = moduleStats.map { it.issuesCount }.sum(), + warningsCount = moduleStats.map { it.warningsCount }.sum(), + infoCount = moduleStats.map { it.infoCount }.sum(), + errorsCount = moduleStats.map { it.errorsCount }.sum(), + ignoredCount = moduleStats.map { it.ignoredCount }.sum() + ) + } + + private fun extractFromModule(module: Project): LintModuleResult { + val moduleMetadata = module.moduleMetadata() + + val xml = locateLintFile(module).readText() + val issues = LintXmlParser(moduleMetadata).parse(xml) + + return LintModuleResult( + metadata = moduleMetadata, + issues = issues, + issuesCount = issues.size, + warningsCount = issues.count { it.severity == Severity.Warning }, + errorsCount = issues.count { it.severity == Severity.Error || it.severity == Severity.Fatal }, + ignoredCount = issues.count { it.severity == Severity.Ignore }, + infoCount = issues.count { it.severity == Severity.Info } + ) + } + + private fun locateLintFile(project: Project): File { + val buildDir = project.buildDir + val androidModuleCase = File(buildDir, "reports/lint-results.xml") + if (androidModuleCase.exists()) { + return androidModuleCase + } + + val jvmModuleCase = File(buildDir, "test-results/lint-results.xml") + if (jvmModuleCase.exists()) { + return jvmModuleCase + } + + throw IllegalStateException("Lint results didn't found in paths: [$androidModuleCase, $jvmModuleCase]") + } +} diff --git a/plugins/src/main/java/com/jraska/lint/LintReportProcess.kt b/plugins/src/main/java/com/jraska/lint/LintReportProcess.kt new file mode 100644 index 00000000..cc774013 --- /dev/null +++ b/plugins/src/main/java/com/jraska/lint/LintReportProcess.kt @@ -0,0 +1,23 @@ +package com.jraska.lint + +import com.jraska.analytics.AnalyticsReporter +import org.gradle.api.Project + +class LintReportProcess( + private val lintProjectExtractor: LintProjectExtractor, + private val lintAnalyticsReporter: LintAnalyticsReporter +) { + fun executeReport(project: Project) { + val report = lintProjectExtractor.extract(project) + lintAnalyticsReporter.report(report) + } + + companion object { + fun create(): LintReportProcess { + return LintReportProcess( + LintProjectExtractor(), + LintAnalyticsReporter(AnalyticsReporter.create("Lint Reporter")) + ) + } + } +} diff --git a/plugins/src/main/java/com/jraska/lint/LintReporterPlugin.kt b/plugins/src/main/java/com/jraska/lint/LintReporterPlugin.kt new file mode 100644 index 00000000..5092e0cd --- /dev/null +++ b/plugins/src/main/java/com/jraska/lint/LintReporterPlugin.kt @@ -0,0 +1,22 @@ +package com.jraska.lint + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class LintReporterPlugin : Plugin { + override fun apply(project: Project) { + project.afterEvaluate { + it.tasks.register("lintStatisticsReport") { lintReportTask -> + lintReportTask.doLast { + LintReportProcess.create().executeReport(lintReportTask.project) + } + + project.rootProject.subprojects + .filter { it.childProjects.isEmpty() } + .forEach { + lintReportTask.dependsOn(it.tasks.named("lint")) + } + } + } + } +} diff --git a/plugins/src/main/java/com/jraska/lint/LintXmlParser.kt b/plugins/src/main/java/com/jraska/lint/LintXmlParser.kt new file mode 100644 index 00000000..589c13ed --- /dev/null +++ b/plugins/src/main/java/com/jraska/lint/LintXmlParser.kt @@ -0,0 +1,79 @@ +package com.jraska.lint + +import com.jraska.module.ModuleMetadata +import groovy.util.Node +import groovy.util.NodeList +import groovy.util.XmlParser + +class LintXmlParser( + private val moduleMetadata: ModuleMetadata, +) { + fun parse(lintXml: String): List { + val testSuiteNode = XmlParser().parseText(lintXml) + + return (testSuiteNode.get("issue") as NodeList) + .map { it as Node } + .map { parseIssue(it) } + } + + private fun parseIssue(node: Node): LintIssue { + return LintIssue( + moduleMetadata, + id = node.attributeString("id"), + severity = node.severity(), + category = node.attributeString("category"), + summary = node.attributeString("summary"), + priority = node.attributeInt("priority"), + message = node.attributeString("message").trimToMaxLength(), + errorLine = node.attribute("errorLine1")?.toString()?.trimToMaxLength(), + location = location(node) + ) + } + + private fun String.trimToMaxLength(): String { + val fullMessage = this + + if (fullMessage.length > MAX_TEXT_LENGTH) { + return "${fullMessage.substring(0, MAX_TEXT_LENGTH)}..." + } else { + return fullMessage + } + } + + private fun location(issueNode: Node): String? { + val locationNode = (issueNode.get("location") as NodeList).firstOrNull() as Node? ?: return null + + val absolutePath = locationNode.attributeString("file") + + val pathIndex = absolutePath.indexOf(moduleMetadata.moduleName) + return if (pathIndex == -1) { + absolutePath + } else { + absolutePath.substring(pathIndex) + } + } + + private fun Node.attributeString(name: String): String { + return attribute(name).toString() + } + + private fun Node.severity(): Severity { + val severityString = attributeString("severity") + return when (severityString) { + "Error" -> Severity.Error + "Warning" -> Severity.Warning + "Information" -> Severity.Info + "Ignore" -> Severity.Ignore + "Fatal" -> Severity.Fatal + else -> throw IllegalArgumentException("Unknown severity: $severityString") + } + } + + private fun Node.attributeInt(name: String): Int { + return attribute(name)?.toString()?.toInt() ?: 0 + } + + companion object { + private val MAX_TEXT_LENGTH = 200 + } +} diff --git a/plugins/src/main/java/com/jraska/module/ModuleMetadata.kt b/plugins/src/main/java/com/jraska/module/ModuleMetadata.kt new file mode 100644 index 00000000..1351fb58 --- /dev/null +++ b/plugins/src/main/java/com/jraska/module/ModuleMetadata.kt @@ -0,0 +1,12 @@ +package com.jraska.module + +data class ModuleMetadata( + val moduleName: String, + val type: ModuleType +) + +enum class ModuleType { + Api, + Implementation, + App +} diff --git a/plugins/src/main/java/com/jraska/module/ModuleStatistics.kt b/plugins/src/main/java/com/jraska/module/ModuleStatistics.kt index dc297236..c2dddd00 100644 --- a/plugins/src/main/java/com/jraska/module/ModuleStatistics.kt +++ b/plugins/src/main/java/com/jraska/module/ModuleStatistics.kt @@ -13,11 +13,10 @@ data class ProjectStatistics( ) data class ModuleStatistics( - val moduleName: String, + val metadata: ModuleMetadata, val containedProdFiles: Collection, val containedUnitTestFiles: Collection, val containedAndroidTestFiles: Collection, - val type: ModuleType ) data class FileTypeStatistics( @@ -27,8 +26,7 @@ data class FileTypeStatistics( ) data class ModuleArtifactDependency( - val moduleName: String, - val type: ModuleType, + val metadata: ModuleMetadata, val fullName: String, val group: String, val artifact: String, @@ -43,12 +41,6 @@ enum class ArtifactDependencyType { Kapt } -enum class ModuleType { - Api, - Implementation, - App -} - enum class FileType(val suffix: String) { KOTLIN(".kt"), JAVA(".java"), diff --git a/plugins/src/main/java/com/jraska/module/ModuleStatsReporter.kt b/plugins/src/main/java/com/jraska/module/ModuleStatsReporter.kt index 4eea87d9..a98fe4f2 100644 --- a/plugins/src/main/java/com/jraska/module/ModuleStatsReporter.kt +++ b/plugins/src/main/java/com/jraska/module/ModuleStatsReporter.kt @@ -41,8 +41,8 @@ class ModuleStatsReporter( private fun convertSingleModule(moduleStats: ModuleStatistics): Map { val properties = mutableMapOf( - "moduleName" to moduleStats.moduleName, - "moduleType" to moduleStats.type.name, + "moduleName" to moduleStats.metadata.moduleName, + "moduleType" to moduleStats.metadata.type.name, ) properties.addFilesData(moduleStats.containedProdFiles, "prod") @@ -70,8 +70,8 @@ class ModuleStatsReporter( private fun convertModuleDependency(artifactDependency: ModuleArtifactDependency): Map { return mapOf( - "moduleName" to artifactDependency.moduleName, - "moduleType" to artifactDependency.type.name, + "moduleName" to artifactDependency.metadata.moduleName, + "moduleType" to artifactDependency.metadata.type.name, "groupId" to artifactDependency.group, "artifactId" to artifactDependency.artifact, "version" to artifactDependency.version, diff --git a/plugins/src/main/java/com/jraska/module/extract/StatisticsGradleExtractor.kt b/plugins/src/main/java/com/jraska/module/extract/StatisticsGradleExtractor.kt index 5a6edbcf..de947585 100644 --- a/plugins/src/main/java/com/jraska/module/extract/StatisticsGradleExtractor.kt +++ b/plugins/src/main/java/com/jraska/module/extract/StatisticsGradleExtractor.kt @@ -4,6 +4,7 @@ import com.jraska.module.ArtifactDependencyType import com.jraska.module.FileType import com.jraska.module.FileTypeStatistics import com.jraska.module.ModuleArtifactDependency +import com.jraska.module.ModuleMetadata import com.jraska.module.ModuleStatistics import com.jraska.module.ModuleType import com.jraska.module.ProjectStatistics @@ -16,7 +17,7 @@ import java.io.FileReader import java.util.LinkedList import java.util.Queue -class StatisticsGradleExtractor() { +class StatisticsGradleExtractor { private val typesToLookFor = arrayOf(FileType.JAVA, FileType.KOTLIN, FileType.XML) fun extract(project: Project): ProjectStatistics { @@ -32,9 +33,9 @@ class StatisticsGradleExtractor() { return ProjectStatistics( modulesCount = moduleStats.size, - apiModules = moduleStats.count { it.type == ModuleType.Api }, - appModules = moduleStats.count { it.type == ModuleType.App }, - implementationModules = moduleStats.count { it.type == ModuleType.Implementation }, + apiModules = moduleStats.count { it.metadata.type == ModuleType.Api }, + appModules = moduleStats.count { it.metadata.type == ModuleType.App }, + implementationModules = moduleStats.count { it.metadata.type == ModuleType.Implementation }, moduleStatistics = moduleStats, externalDependencies = externalDependencies, prodKotlinTotalLines = moduleStats @@ -52,9 +53,7 @@ class StatisticsGradleExtractor() { } private fun extractDependencies(module: Project): List { - val moduleType = moduleType(module) - val moduleName = module.name - + val metadata = module.moduleMetadata() val dependencies = module.configurations .filter { CONFIGURATION_TO_LOOK.containsKey(it.name) } .flatMap { configuration -> @@ -64,8 +63,7 @@ class StatisticsGradleExtractor() { .flatMap { traverseAndAddChildren(it) } .map { ModuleArtifactDependency( - moduleName = moduleName, - type = moduleType, + metadata = metadata, group = it.moduleGroup, dependencyType = CONFIGURATION_TO_LOOK.getValue(configuration.name), artifact = it.moduleName, @@ -106,15 +104,7 @@ class StatisticsGradleExtractor() { val unitTestFileStats = typesToLookFor.map { getFileTypeStatistics(it, File(module.projectDir, "src/test")) } val androidTestFileStats = typesToLookFor.map { getFileTypeStatistics(it, File(module.projectDir, "src/androidTest")) } - return ModuleStatistics(module.name, prodFileStats, unitTestFileStats, androidTestFileStats, moduleType(module)) - } - - private fun moduleType(module: Project): ModuleType { - return when { - module.name.endsWith("-api") -> ModuleType.Api - module.name.startsWith("app") -> ModuleType.App - else -> ModuleType.Implementation - } + return ModuleStatistics(module.moduleMetadata(), prodFileStats, unitTestFileStats, androidTestFileStats) } private fun getFileTypeStatistics(type: FileType, src: File): FileTypeStatistics { @@ -150,5 +140,17 @@ class StatisticsGradleExtractor() { "testCompileClasspath" to ArtifactDependencyType.Test, "kapt" to ArtifactDependencyType.Kapt ) + + fun Project.moduleMetadata(): ModuleMetadata { + return ModuleMetadata(name, moduleType(this)) + } + + private fun moduleType(module: Project): ModuleType { + return when { + module.name.endsWith("-api") -> ModuleType.Api + module.name.startsWith("app") -> ModuleType.App + else -> ModuleType.Implementation + } + } } } diff --git a/plugins/src/test/kotlin/com/jraska/github/client/firebase/report/FirebaseResultExtractorTest.kt b/plugins/src/test/kotlin/com/jraska/github/client/firebase/report/FirebaseResultExtractorTest.kt index 4c13456f..311adafd 100644 --- a/plugins/src/test/kotlin/com/jraska/github/client/firebase/report/FirebaseResultExtractorTest.kt +++ b/plugins/src/test/kotlin/com/jraska/github/client/firebase/report/FirebaseResultExtractorTest.kt @@ -1,5 +1,6 @@ package com.jraska.github.client.firebase.report +import com.jraska.github.client.firebase.Device import com.jraska.github.client.firebase.TestOutcome import com.jraska.gradle.git.GitInfo import org.assertj.core.api.Assertions.assertThat @@ -12,7 +13,7 @@ class FirebaseResultExtractorTest { @Before fun setUp() { - extractor = FirebaseResultExtractor("someUrl", GitInfo("exampleBrach", "123", false, ""), "someDevice") + extractor = FirebaseResultExtractor("someUrl", GitInfo("exampleBrach", "123", false, ""), Device.Pixel4) } @Test @@ -59,7 +60,7 @@ class FirebaseResultExtractorTest { assertThat(firstTest.outcome).isEqualTo(TestOutcome.PASSED) assertThat(firstTest.className).isEqualTo("com.jraska.github.client.AppSetupTest") assertThat(firstTest.methodName).isEqualTo("appCreateEventFired") - assertThat(firstTest.failure).isEmpty() + assertThat(firstTest.failure).isNull() assertThat(firstTest.fullName).isEqualTo("com.jraska.github.client.AppSetupTest#appCreateEventFired") diff --git a/plugins/src/test/kotlin/com/jraska/lint/LintXmlParserTest.kt b/plugins/src/test/kotlin/com/jraska/lint/LintXmlParserTest.kt new file mode 100644 index 00000000..15c31b86 --- /dev/null +++ b/plugins/src/test/kotlin/com/jraska/lint/LintXmlParserTest.kt @@ -0,0 +1,178 @@ +package com.jraska.lint + +import com.jraska.module.ModuleMetadata +import com.jraska.module.ModuleType +import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat +import org.junit.Test + +class LintXmlParserTest { + @Test + fun whenEmptyXml_thenNoResults() { + val lintXmlParser = LintXmlParser(ModuleMetadata("fakeModule", ModuleType.Implementation)) + + val issues = lintXmlParser.parse(EMPTY_LINT_RESULT) + + assertThat(issues).isEmpty() + } + + @Test + fun whenXmlWithSeveralIssuesFound_thenReportsCorrectly() { + val moduleMetadata = ModuleMetadata("app-partial-users", ModuleType.Implementation) + val lintXmlParser = LintXmlParser(moduleMetadata) + + val issues = lintXmlParser.parse(APP_XML_RESULT) + + assertThat(issues).hasSize(2) + + val expectedFirstIssue = LintIssue( + id = "AllowBackup", + metadata = moduleMetadata, + category = "Security", + severity = Severity.Warning, + message = "On SDK version 23 and up, your app data will be automatically backed up and restored on app install. Consider adding the attribute `android:fullBackupContent` to specify an `@xml` resource which confi...", + priority = 3, + summary = "AllowBackup/FullBackupContent Problems", + errorLine = """ + + +""" + + val RESULT_WITH_ERROR_LOCATION = """ + + + + + + + + + + + +""" + + val APP_XML_RESULT = """ + + + + + + + + + + + +""" + } +}