1package com.peachpasswords.backup23import java.io.File4import javax.xml.parsers.DocumentBuilderFactory5import org.junit.Assert.assertTrue6import org.junit.Test789class BackupRulesCoverageTest {10 @Test11 fun every_shared_preferences_filename_is_excluded_from_both_rule_files() {12 val sourceRoot = projectFile("src/main/java")13 val source = sourceRoot.walkTopDown()14 .filter { it.isFile && it.extension == "kt" }15 .map { it.readText() }16 .toList()17 val declarations = source18 .flatMap { SHARED_PREFERENCES_DECLARATION.findAll(it).map { match -> match.groupValues[1] to match.groupValues[2] } }19 .toMap()20 val preferences = source21 .flatMap { text ->22 SHARED_PREFERENCES_CALLS.findAll(text).mapNotNull { match ->23 val reference = (match.groups[1] ?: match.groups[2])?.value?.trim() ?: return@mapNotNull null24 val literal = reference.removeSurrounding("\"")25 if (literal != reference) return@mapNotNull literal.ifBlank { null }26 27 28 29 30 31 32 declarations[reference]33 } + SHARED_PREFERENCES_HELPER_CALLS.findAll(text).mapNotNull { match ->34 val reference = match.groupValues[1].trim()35 val literal = reference.removeSurrounding("\"")36 if (literal != reference) literal.ifBlank { null } else declarations[reference]37 }38 }39 .map { "$it.xml" }40 .toSet()41 assertTrue("no SharedPreferences file names were found", preferences.isNotEmpty())4243 val backup = excludedPaths(projectFile("src/main/res/xml/backup_rules.xml"))44 val extraction = excludedPaths(projectFile("src/main/res/xml/data_extraction_rules.xml"))45 preferences.forEach { filename ->46 assertTrue("$filename is missing from backup_rules.xml", filename in backup)47 assertTrue("$filename is missing from data_extraction_rules.xml", filename in extraction)48 }49 }5051 private fun excludedPaths(file: File): Set<String> {52 val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file)53 return (0 until document.getElementsByTagName("exclude").length)54 .map { index -> document.getElementsByTagName("exclude").item(index).attributes.getNamedItem("path").nodeValue }55 .toSet()56 }5758 private fun projectFile(moduleRelativePath: String): File =59 listOf(File(moduleRelativePath), File("app/$moduleRelativePath"))60 .firstOrNull { it.exists() }61 ?: error("could not locate $moduleRelativePath from ${File(".").absolutePath}")6263 private companion object {64 val SHARED_PREFERENCES_DECLARATION = Regex(65 "(?m)\\b(?:private\\s+|internal\\s+|public\\s+)?(?:const\\s+)?val\\s+(\\w+)\\s*=\\s*\"([^\"]+)\"",66 )67 val SHARED_PREFERENCES_CALLS = Regex(68 "(?:getSharedPreferences|deleteSharedPreferences)\\s*\\(\\s*([^,\\)]+)|EncryptedSharedPreferences\\.create\\s*\\(\\s*[^,]+,\\s*([^,\\)]+)",69 )7071 72 val SHARED_PREFERENCES_HELPER_CALLS = Regex(73 "encryptedPrefs\\s*\\(\\s*[^,]+,\\s*([^,\\)]+)",74 )75 }76}77