Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 1 | // Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file |
| 2 | // for details. All rights reserved. Use of this source code is governed by a |
| 3 | // BSD-style license that can be found in the LICENSE file. |
| 4 | |
| 5 | import java.io.File |
| 6 | import java.io.FileOutputStream |
| 7 | import java.io.FileWriter |
| 8 | import java.io.PrintStream |
| 9 | import java.net.URLEncoder |
| 10 | import java.nio.file.Path |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 11 | import org.gradle.api.Project |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 12 | import org.gradle.api.logging.Logger |
| 13 | import org.gradle.api.tasks.Exec |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 14 | import org.gradle.api.tasks.testing.Test |
| 15 | import org.gradle.api.tasks.testing.TestDescriptor |
| 16 | import org.gradle.api.tasks.testing.TestListener |
| 17 | import org.gradle.api.tasks.testing.TestOutputEvent |
| 18 | import org.gradle.api.tasks.testing.TestOutputListener |
| 19 | import org.gradle.api.tasks.testing.TestResult |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 20 | import org.gradle.api.tasks.testing.TestResult.ResultType |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 21 | import org.gradle.kotlin.dsl.register |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 22 | |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 23 | // Utility to install tracking of test results in status files. |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 24 | class TestingState { |
| 25 | companion object { |
| 26 | |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 27 | const val MODE_PROPERTY = "testing-state-mode" |
| 28 | const val PATH_PROPERTY = "testing-state-path" |
| 29 | |
| 30 | // Operating mode for the test state. |
| 31 | enum class Mode { ALL, OUTSTANDING, FAILING, PAST_FAILING } |
| 32 | |
| 33 | // These are the files that are allowed for tracking test status. |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 34 | enum class StatusType { SUCCESS, FAILURE, PAST_FAILURE } |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 35 | |
| 36 | fun getRerunMode(project: Project) : Mode? { |
| 37 | val prop = project.findProperty(MODE_PROPERTY) ?: return null |
| 38 | return when (prop.toString().lowercase()) { |
| 39 | "all" -> Mode.ALL |
| 40 | "failing" -> Mode.FAILING |
| 41 | "past-failing" -> Mode.PAST_FAILING |
| 42 | "past_failing" -> Mode.PAST_FAILING |
| 43 | "outstanding" -> Mode.OUTSTANDING |
| 44 | else -> null |
Rico Wind | 237fc81 | 2023-09-11 10:02:24 +0000 | [diff] [blame] | 45 | } |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 46 | } |
| 47 | |
| 48 | fun setUpTestingState(task: Test) { |
| 49 | // Both the path and the mode must be defined for the testing state to be active. |
| 50 | val testingStatePath = task.project.findProperty(PATH_PROPERTY) ?: return |
| 51 | val testingStateMode = getRerunMode(task.project) ?: return |
| 52 | |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 53 | val projectName = task.project.name |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 54 | val indexDir = File(testingStatePath.toString()) |
| 55 | val reportDir = indexDir.resolve(projectName) |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 56 | val index = indexDir.resolve("index.html") |
| 57 | val resuming = reportDir.exists() |
| 58 | if (resuming) { |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 59 | applyTestFilters(testingStateMode, task, reportDir, indexDir, projectName) |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 60 | } |
| 61 | addTestHandler(task, projectName, index, reportDir) |
| 62 | } |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 63 | |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 64 | private fun applyTestFilters( |
| 65 | mode: Mode, |
| 66 | task: Test, |
| 67 | reportDir: File, |
| 68 | indexDir: File, |
| 69 | projectName: String, |
| 70 | ) { |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 71 | if (mode == Mode.ALL) { |
| 72 | // Running without filters will (re)run all tests. |
Rico Wind | 237fc81 | 2023-09-11 10:02:24 +0000 | [diff] [blame] | 73 | return |
| 74 | } |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 75 | val statusType = getStatusTypeForMode(mode) |
| 76 | val statusOutputFile = indexDir.resolve("${projectName}.${statusType.name}.txt") |
| 77 | val findStatusTask = task.project.tasks.register<Exec>("${projectName}-find-status-files") |
| 78 | { |
| 79 | inputs.dir(reportDir) |
| 80 | outputs.file(statusOutputFile) |
| 81 | workingDir(reportDir) |
| 82 | commandLine( |
| 83 | "find", ".", "-name", statusType.name |
| 84 | ) |
| 85 | doFirst { |
| 86 | standardOutput = statusOutputFile.outputStream() |
| 87 | } |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 88 | } |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 89 | task.dependsOn(findStatusTask) |
| 90 | task.doFirst { |
| 91 | if (mode == Mode.OUTSTANDING) { |
| 92 | forEachTestReportStatusMatching( |
| 93 | statusType, |
| 94 | findStatusTask.get().outputs.files.singleFile, |
| 95 | task.logger, |
| 96 | { clazz, name -> task.filter.excludeTestsMatching("${clazz}.${name}") }) |
| 97 | } else { |
| 98 | val hasMatch = forEachTestReportStatusMatching( |
| 99 | statusType, |
| 100 | findStatusTask.get().outputs.files.singleFile, |
| 101 | task.logger, |
| 102 | { clazz, name -> task.filter.includeTestsMatching("${clazz}.${name}") }) |
| 103 | if (!hasMatch) { |
| 104 | // Add a filter that does not match to ensure the test run is not "without filters" |
| 105 | // which would run all tests. |
| 106 | task.filter.includeTestsMatching("NON_MATCHING_TEST_FILTER") |
| 107 | } |
| 108 | } |
| 109 | } |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | private fun addTestHandler( |
| 113 | task: Test, |
| 114 | projectName: String, |
| 115 | index: File, |
| 116 | reportDir: File) { |
| 117 | task.addTestOutputListener(object : TestOutputListener { |
| 118 | override fun onOutput(desc: TestDescriptor, event: TestOutputEvent) { |
| 119 | withTestResultEntryWriter(reportDir, desc, event.getDestination().name, true, { |
| 120 | it.append(event.getMessage()) |
| 121 | }) |
| 122 | } |
| 123 | }) |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 124 | task.addTestListener(object : TestListener { |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 125 | |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 126 | override fun beforeSuite(desc: TestDescriptor) {} |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 127 | |
| 128 | override fun afterSuite(desc: TestDescriptor, result: TestResult) { |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 129 | if (desc.parent != null) { |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 130 | return |
| 131 | } |
| 132 | // Update the final test results in the index. |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 133 | val text = StringBuilder() |
| 134 | val successColor = "#a2ff99" |
| 135 | val failureColor = "#ff6454" |
| 136 | val emptyColor = "#d4d4d4" |
| 137 | val color: String; |
| 138 | if (result.testCount == 0L) { |
| 139 | color = emptyColor |
| 140 | } else if (result.resultType == TestResult.ResultType.SUCCESS) { |
| 141 | color = successColor |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 142 | } else if (result.resultType == TestResult.ResultType.FAILURE) { |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 143 | color = failureColor |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 144 | } else { |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 145 | color = failureColor |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 146 | } |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 147 | // The failure list has an open <ul> so close it before appending the module results. |
| 148 | text.append("</ul>") |
| 149 | text.append("<div style=\"background-color:${color}\">") |
| 150 | text.append("<h2>${projectName}: ${result.resultType.name}</h2>") |
| 151 | text.append("<ul>") |
| 152 | text.append("<li>Number of tests: ${result.testCount}") |
| 153 | text.append("<li>Failing tests: ${result.failedTestCount}") |
| 154 | text.append("<li>Successful tests: ${result.successfulTestCount}") |
| 155 | text.append("<li>Skipped tests: ${result.skippedTestCount}") |
| 156 | text.append("</ul></div>") |
| 157 | // Reopen a <ul> as other modules may still append test failures. |
| 158 | text.append("<ul>") |
| 159 | |
| 160 | index.appendText(text.toString()) |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 161 | } |
| 162 | |
| 163 | override fun beforeTest(desc: TestDescriptor) { |
| 164 | // Remove any stale output files before running the test. |
| 165 | for (destType in TestOutputEvent.Destination.values()) { |
| 166 | val destFile = getTestResultEntryOutputFile(reportDir, desc, destType.name) |
| 167 | if (destFile.exists()) { |
| 168 | destFile.delete() |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | override fun afterTest(desc: TestDescriptor, result: TestResult) { |
| 174 | if (result.testCount != 1L) { |
| 175 | throw IllegalStateException("Unexpected test with more than one result: ${desc}") |
| 176 | } |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 177 | updateStatusFiles(reportDir, desc, result.resultType) |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 178 | // Emit the test time. |
| 179 | withTestResultEntryWriter(reportDir, desc, "time", false, { |
| 180 | it.append("${result.getEndTime() - result.getStartTime()}") |
| 181 | }) |
| 182 | // For failed tests, update the index and emit stack trace information. |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 183 | if (result.resultType == ResultType.FAILURE) { |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 184 | val title = testLinkContent(desc) |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 185 | val link = getTestReportEntryURL(reportDir, desc) |
| 186 | index.appendText("<li><a href=\"${link}\">${title}</a></li>") |
| 187 | if (!result.exceptions.isEmpty()) { |
| 188 | printAllStackTracesToFile( |
| 189 | result.exceptions, |
| 190 | getTestResultEntryOutputFile( |
| 191 | reportDir, |
| 192 | desc, |
| 193 | "exceptions-raw.txt" |
| 194 | ) |
| 195 | ) |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 196 | // The raw stacktrace has lots of useless gradle test runner frames. |
| 197 | // As a convenience filter out those so the stack is just easier to read. |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 198 | filterStackTraces(result) |
| 199 | printAllStackTracesToFile( |
| 200 | result.exceptions, |
| 201 | getTestResultEntryOutputFile( |
| 202 | reportDir, |
| 203 | desc, |
| 204 | "exceptions-filtered.txt" |
| 205 | ) |
| 206 | ) |
| 207 | } |
| 208 | } |
| 209 | } |
| 210 | }) |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 211 | } |
| 212 | |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 213 | private fun getStatusTypeForMode(mode: Mode) : StatusType { |
| 214 | return when (mode) { |
| 215 | Mode.OUTSTANDING -> StatusType.SUCCESS |
| 216 | Mode.FAILING -> StatusType.FAILURE |
| 217 | Mode.PAST_FAILING -> StatusType.PAST_FAILURE |
| 218 | Mode.ALL -> throw RuntimeException("Unexpected mode 'all' in status determination") |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | private fun getStatusTypeForResult(result: ResultType) : StatusType { |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 223 | return when (result) { |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 224 | ResultType.FAILURE -> StatusType.FAILURE |
| 225 | ResultType.SUCCESS -> StatusType.SUCCESS |
| 226 | ResultType.SKIPPED -> StatusType.SUCCESS |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 227 | } |
| 228 | } |
| 229 | |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 230 | private fun escapeHtml(string: String): String { |
| 231 | return string |
| 232 | .replace("&", "&") |
| 233 | .replace("<", "<") |
| 234 | .replace(">", ">") |
| 235 | } |
| 236 | |
| 237 | private fun urlEncode(string: String): String { |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 238 | // Not sure why, but the + also needs to be converted to have working links. |
| 239 | return URLEncoder.encode(string, "UTF-8").replace("+", "%20") |
| 240 | } |
| 241 | |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 242 | private fun testLinkContent(desc: TestDescriptor) : String { |
| 243 | val pkgR8 = "com.android.tools.r8." |
| 244 | val className = desc.className!! |
| 245 | val shortClassName = |
| 246 | if (className.startsWith(pkgR8)) { |
| 247 | className.substring(pkgR8.length) |
| 248 | } else { |
| 249 | className |
| 250 | } |
| 251 | return escapeHtml("${shortClassName}.${desc.name}") |
| 252 | } |
| 253 | |
| 254 | private fun filterStackTraces(result: TestResult) { |
| 255 | for (throwable in result.getExceptions()) { |
| 256 | filterStackTrace(throwable) |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | // It would be nice to do this in a non-destructive way... |
| 261 | private fun filterStackTrace(exception: Throwable) { |
| 262 | val elements = ArrayList<StackTraceElement>() |
| 263 | val skipped = ArrayList<StackTraceElement>() |
| 264 | for (element in exception.getStackTrace()) { |
| 265 | if (element.toString().contains("com.android.tools.r8")) { |
| 266 | elements.addAll(skipped) |
| 267 | elements.add(element) |
| 268 | skipped.clear() |
| 269 | } else { |
| 270 | skipped.add(element) |
| 271 | } |
| 272 | } |
| 273 | exception.setStackTrace(elements.toTypedArray()) |
| 274 | } |
| 275 | |
| 276 | private fun printAllStackTracesToFile(exceptions: List<Throwable>, out: File) { |
| 277 | PrintStream(FileOutputStream(out)) |
| 278 | .use({ printer -> exceptions.forEach { it.printStackTrace(printer) } }) |
| 279 | } |
| 280 | |
| 281 | private fun ensureDir(dir: File): File { |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 282 | dir.mkdirs() |
| 283 | return dir |
| 284 | } |
| 285 | |
| 286 | // Some of our test parameters have new lines :-( We really don't want test names to span lines. |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 287 | private fun sanitizedTestName(testName: String): String { |
| 288 | if (testName.contains("\n")) { |
| 289 | throw RuntimeException("Unsupported use of newline in test name: '${testName}'") |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 290 | } |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 291 | return testName |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 292 | } |
| 293 | |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 294 | private fun getTestReportClassDirPath(reportDir: File, testClass: String): Path { |
| 295 | return reportDir.toPath().resolve(testClass) |
| 296 | } |
| 297 | |
| 298 | private fun getTestReportEntryDirFromString(reportDir: File, testClass: String, testName: String): File { |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 299 | return ensureDir( |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 300 | getTestReportClassDirPath(reportDir, testClass) |
| 301 | .resolve(sanitizedTestName(testName)) |
| 302 | .toFile()) |
| 303 | } |
| 304 | |
| 305 | private fun getTestReportEntryDirFromTest(reportDir: File, testDesc: TestDescriptor): File { |
| 306 | return getTestReportEntryDirFromString(reportDir, testDesc.className!!, testDesc.name) |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 307 | } |
| 308 | |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 309 | private fun getTestReportEntryURL(reportDir: File, testDesc: TestDescriptor): Path { |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 310 | val classDir = urlEncode(testDesc.className!!) |
| 311 | val testDir = urlEncode(sanitizedTestName(testDesc.name)) |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 312 | return reportDir.toPath().resolve(classDir).resolve(testDir) |
| 313 | } |
| 314 | |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 315 | private fun getTestResultEntryOutputFile( |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 316 | reportDir: File, |
| 317 | testDesc: TestDescriptor, |
| 318 | fileName: String |
| 319 | ): File { |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 320 | val dir = getTestReportEntryDirFromTest(reportDir, testDesc).toPath() |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 321 | return dir.resolve(fileName).toFile() |
| 322 | } |
| 323 | |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 324 | private fun updateStatusFiles( |
| 325 | reportDir: File, |
| 326 | desc: TestDescriptor, |
| 327 | result: ResultType) { |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 328 | val statusFile = getStatusTypeForResult(result) |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 329 | withTestResultEntryWriter(reportDir, desc, statusFile.name, false, { |
| 330 | it.append(statusFile.name) |
| 331 | }) |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 332 | if (statusFile == StatusType.FAILURE) { |
| 333 | getTestResultEntryOutputFile(reportDir, desc, StatusType.SUCCESS.name).delete() |
| 334 | val pastFailure = StatusType.PAST_FAILURE.name |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 335 | withTestResultEntryWriter(reportDir, desc, pastFailure, false, { |
| 336 | it.append(pastFailure) |
| 337 | }) |
| 338 | } else { |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 339 | getTestResultEntryOutputFile(reportDir, desc, StatusType.FAILURE.name).delete() |
Ian Zerny | f271e8d | 2023-09-11 12:30:41 +0200 | [diff] [blame] | 340 | } |
| 341 | } |
| 342 | |
Ian Zerny | 9f0345d | 2023-09-07 11:15:00 +0200 | [diff] [blame] | 343 | private fun withTestResultEntryWriter( |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 344 | reportDir: File, |
| 345 | testDesc: TestDescriptor, |
| 346 | fileName: String, |
| 347 | append: Boolean, |
| 348 | fn: (FileWriter) -> Unit |
| 349 | ) { |
| 350 | val file = getTestResultEntryOutputFile(reportDir, testDesc, fileName) |
| 351 | FileWriter(file, append).use(fn) |
| 352 | } |
| 353 | |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 354 | private fun forEachTestReportStatusMatching( |
| 355 | type: StatusType, file: File, logger: Logger, onTest: (String, String) -> Unit |
| 356 | ) : Boolean { |
| 357 | val fileName = type.name |
| 358 | var hasMatch = false |
| 359 | for (rawLine in file.bufferedReader().lineSequence()) { |
| 360 | // Lines are of the form: ./<class>/<name>/<mode> |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 361 | try { |
| 362 | val trimmed = rawLine.trim() |
| 363 | val line = trimmed.substring(2) |
| 364 | val sep = line.indexOf("/") |
| 365 | val clazz = line.substring(0, sep) |
| 366 | val name = line.substring(sep + 1, line.length - fileName.length - 1) |
| 367 | onTest(clazz, name) |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 368 | hasMatch = true |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 369 | } catch (e: Exception) { |
| 370 | logger.lifecycle("WARNING: failed attempt to read test description from: '${rawLine}'") |
| 371 | } |
| 372 | } |
Ian Zerny | 992ce74 | 2023-09-13 09:29:58 +0200 | [diff] [blame] | 373 | return hasMatch |
Ian Zerny | c2de7b7 | 2023-09-06 20:52:16 +0200 | [diff] [blame] | 374 | } |
| 375 | } |
| 376 | } |