1 /* <lambda>null2 * 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.tools.metalava.testing 18 19 import java.io.File 20 import java.io.InputStreamReader 21 import java.io.LineNumberReader 22 import java.util.TreeMap 23 import java.util.TreeSet 24 25 /** Encapsulates information read from a test baseline file. */ 26 interface BaselineFile { 27 /** Check to see whether the specified [className] and [testName] are expected to fail. */ 28 fun isExpectedFailure(className: String, testName: String): Boolean 29 30 companion object { 31 /** 32 * Read the source baseline file from the containing project's directory. 33 * 34 * @param projectDir the project directory from which this baseline will be loaded. 35 * @param resourcePath the resource path to the baseline file. 36 */ 37 fun forProject(projectDir: File, resourcePath: String): MutableBaselineFile { 38 // Load it from the project's test resources directory. 39 val baselineFile = projectDir.resolve("src/test/resources").resolve(resourcePath) 40 return forFile(baselineFile) 41 } 42 43 /** 44 * Read the source baseline file from the file. 45 * 46 * @param baselineFile the baseline [File] from which this baseline will be loaded. 47 */ 48 fun forFile(baselineFile: File): MutableBaselineFile { 49 val baseline = MutableBaselineFile(baselineFile = baselineFile) 50 if (baselineFile.exists()) { 51 baselineFile.reader().use { baseline.read(it, baselineFile.path) } 52 } 53 return baseline 54 } 55 56 /** 57 * Get the baseline file from the [Thread.contextClassLoader]. 58 * 59 * @param resourcePath the resource path to the baseline file. 60 */ 61 fun fromResource(resourcePath: String): BaselineFile { 62 val baseline = MutableBaselineFile() 63 val contextClassLoader = Thread.currentThread().contextClassLoader 64 val resource = contextClassLoader.getResource(resourcePath) 65 resource?.openStream()?.reader()?.use { baseline.read(it, resource.toExternalForm()) } 66 return baseline 67 } 68 } 69 } 70 71 /** 72 * Mutable representation of a baseline file. 73 * 74 * Used by the update command line tool to update the baseline based on the information found in the 75 * test reports. 76 */ 77 class MutableBaselineFile 78 internal constructor( 79 private val baselineFile: File? = null, 80 private val expectedFailures: MutableMap<String, MutableSet<String>> = TreeMap(), 81 ) : BaselineFile { 82 83 /** 84 * Read the baseline file from the reader. 85 * 86 * @param location the location, (either a file path or url), of the baseline being read. 87 */ readnull88 internal fun read(streamReader: InputStreamReader, location: String) { 89 val reader = LineNumberReader(streamReader) 90 var currentClassName: String? = null 91 do { 92 val line = reader.readLine() ?: break 93 when { 94 line.isEmpty() -> currentClassName = null 95 line.startsWith(" ") -> { 96 val testName = line.substring(2).trimEnd() 97 currentClassName 98 ?: throw IllegalStateException( 99 "$location:${reader.lineNumber}: test name found but no preceding class name was found" 100 ) 101 addExpectedFailure(currentClassName, testName) 102 } 103 else -> currentClassName = line.trimEnd() 104 } 105 } while (true) 106 } 107 writenull108 fun write() { 109 baselineFile ?: throw IllegalStateException("Cannot write baseline read from resources") 110 111 // If there are no expected failures then there is no point having a baseline file so 112 // delete it if necessary. 113 if (expectedFailures.isEmpty()) { 114 if (baselineFile.exists()) { 115 baselineFile.delete() 116 } 117 return 118 } 119 120 // Write the file. 121 baselineFile.parentFile.mkdirs() 122 baselineFile.printWriter().use { writer -> 123 var separator = "" 124 expectedFailures.forEach { (className, testNames) -> 125 if (testNames.isNotEmpty()) { 126 writer.print(separator) 127 separator = "\n" 128 129 writer.println(className) 130 testNames.forEach { testName -> writer.println(" $testName") } 131 } 132 } 133 } 134 } 135 isExpectedFailurenull136 override fun isExpectedFailure(className: String, testName: String): Boolean { 137 return expectedFailures[className]?.contains(testName) ?: false 138 } 139 140 /** Add an expected failure to the baseline. */ addExpectedFailurenull141 fun addExpectedFailure(className: String, testName: String) { 142 val classFailures = expectedFailures.computeIfAbsent(className) { TreeSet() } 143 classFailures.add(testName) 144 } 145 146 /** Remove an expected failure from the baseline. */ removeExpectedFailurenull147 fun removeExpectedFailure(className: String, testName: String) { 148 val classFailures = expectedFailures[className] ?: return 149 classFailures.remove(testName) 150 if (classFailures.isEmpty()) { 151 expectedFailures.remove(className) 152 } 153 } 154 } 155