| // Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import java.io.ByteArrayOutputStream |
| import java.io.File |
| import java.io.PrintStream |
| import java.nio.charset.StandardCharsets |
| import java.util.Date |
| import java.util.concurrent.TimeUnit |
| import org.gradle.api.Project |
| import org.gradle.api.tasks.testing.Test |
| import org.gradle.api.tasks.testing.TestDescriptor |
| import org.gradle.api.tasks.testing.TestListener |
| import org.gradle.api.tasks.testing.TestResult |
| |
| class TestConfigurationHelper { |
| |
| companion object { |
| |
| fun retrace(project: Project, r8jar: File, mappingFile: File, exception: Throwable): String { |
| val out = StringBuilder() |
| val header = "RETRACED STACKTRACE: " + System.currentTimeMillis() |
| out.append("\n--------------------------------------\n") |
| out.append("${header}\n") |
| out.append("--------------------------------------\n") |
| val retracePath = project.getRoot().resolveAll("tools", "retrace.py") |
| val command = |
| mutableListOf( |
| "python3", |
| retracePath.toString(), |
| "--quiet", |
| "--map", |
| mappingFile.toString(), |
| "--r8jar", |
| r8jar.toString(), |
| ) |
| val process = ProcessBuilder(command).start() |
| process.outputStream.use { exception.printStackTrace(PrintStream(it)) } |
| process.outputStream.close() |
| val processCompleted = process.waitFor(20L, TimeUnit.SECONDS) && process.exitValue() == 0 |
| out.append(process.inputStream.bufferedReader().use { it.readText() }) |
| if (!processCompleted) { |
| out.append(command.joinToString(" ") + "\n") |
| out.append("ERROR DURING RETRACING: " + System.currentTimeMillis() + "\n") |
| out.append(process.errorStream.bufferedReader().use { it.readText() }) |
| } |
| if (project.hasProperty("print_obfuscated_stacktraces") || !processCompleted) { |
| out.append("\n\n--------------------------------------\n") |
| out.append("OBFUSCATED STACKTRACE\n") |
| out.append("--------------------------------------\n") |
| var baos = ByteArrayOutputStream() |
| exception.printStackTrace(PrintStream(baos, true, StandardCharsets.UTF_8)) |
| out.append(baos.toString()) |
| } |
| return out.toString() |
| } |
| |
| fun setupTestTask(test: Test, isR8Lib: Boolean, r8Jar: File?, r8LibMappingFile: File?) { |
| val project = test.project |
| if (project.hasProperty("testfilter")) { |
| val testFilter = project.property("testfilter").toString() |
| test.filter.setFailOnNoMatchingTests(false) |
| test.filter.setIncludePatterns(*(testFilter.split("|").toTypedArray())) |
| } |
| if (project.hasProperty("kotlin_compiler_dev")) { |
| test.systemProperty("com.android.tools.r8.kotlincompilerdev", "1") |
| } |
| |
| if (project.hasProperty("kotlin_compiler_old")) { |
| test.systemProperty("com.android.tools.r8.kotlincompilerold", "1") |
| } |
| |
| if (project.hasProperty("dex_vm") && project.property("dex_vm") != "default") { |
| println("NOTE: Running with non default vm: " + project.property("dex_vm")) |
| test.systemProperty("dex_vm", project.property("dex_vm")!!) |
| } |
| |
| // Forward runtime configurations for test parameters. |
| if (project.hasProperty("runtimes")) { |
| println("NOTE: Running with runtimes: " + project.property("runtimes")) |
| test.systemProperty("runtimes", project.property("runtimes")!!) |
| } |
| |
| if (project.hasProperty("art_profile_rewriting_completeness_check")) { |
| test.systemProperty( |
| "com.android.tools.r8.artprofilerewritingcompletenesscheck", |
| project.property("art_profile_rewriting_completeness_check")!!, |
| ) |
| } |
| |
| if (project.hasProperty("disable_assertions")) { |
| test.enableAssertions = false |
| } |
| |
| // Forward project properties into system properties. |
| listOf( |
| "local_development", |
| "slow_tests", |
| "desugar_jdk_json_dir", |
| "desugar_jdk_libs", |
| "test_dir", |
| "command_cache_dir", |
| "command_cache_stats_dir", |
| ) |
| .forEach { |
| val propertyName = it |
| if (project.hasProperty(propertyName)) { |
| project.property(propertyName)?.let { v -> test.systemProperty(propertyName, v) } |
| } |
| } |
| |
| if (project.hasProperty("no_internal")) { |
| test.exclude("com/android/tools/r8/internal/**") |
| } |
| if (project.hasProperty("only_internal")) { |
| test.include("com/android/tools/r8/internal/**") |
| } |
| if (project.hasProperty("no_arttests")) { |
| test.exclude("com/android/tools/r8/art/**") |
| } |
| |
| if (project.hasProperty("test_xmx")) { |
| test.maxHeapSize = project.property("test_xmx")!!.toString() |
| } else { |
| test.maxHeapSize = "4G" |
| } |
| |
| if ( |
| isR8Lib || |
| project.hasProperty("one_line_per_test") || |
| project.hasProperty("update_test_timestamp") |
| ) { |
| test.addTestListener( |
| object : TestListener { |
| val testTimes = mutableMapOf<TestDescriptor?, Long>() |
| val maxPrintTimesCount = 200 |
| |
| override fun beforeSuite(desc: TestDescriptor?) {} |
| |
| override fun afterSuite(desc: TestDescriptor?, result: TestResult?) { |
| if (project.hasProperty("print_times")) { |
| // desc.parent == null when we are all done |
| if (desc?.parent == null) { |
| testTimes |
| .toList() |
| .sortedByDescending { it.second } |
| .take(maxPrintTimesCount) |
| .forEach { println("${it.first} took: ${it.second}") } |
| } |
| } |
| } |
| |
| override fun beforeTest(desc: TestDescriptor?) { |
| if (project.hasProperty("one_line_per_test")) { |
| println("Start executing ${desc}") |
| } |
| if (project.hasProperty("print_times")) { |
| testTimes[desc] = Date().getTime() |
| } |
| } |
| |
| override fun afterTest(desc: TestDescriptor?, result: TestResult?) { |
| if (project.hasProperty("one_line_per_test")) { |
| println("Done executing ${desc} with result: ${result?.resultType}") |
| } |
| if (project.hasProperty("print_times")) { |
| testTimes[desc] = Date().getTime() - testTimes[desc]!! |
| } |
| if (project.hasProperty("update_test_timestamp")) { |
| File(project.property("update_test_timestamp")!!.toString()) |
| .writeText(Date().getTime().toString()) |
| } |
| if ( |
| isR8Lib && |
| result?.resultType == TestResult.ResultType.FAILURE && |
| result.exception != null |
| ) { |
| println( |
| retrace(project, r8Jar!!, r8LibMappingFile!!, result.exception as Throwable) |
| ) |
| } |
| } |
| } |
| ) |
| } |
| |
| val userDefinedCoresPerFork = System.getenv("R8_GRADLE_CORES_PER_FORK") |
| val processors = Runtime.getRuntime().availableProcessors() |
| // See https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html. |
| if (!userDefinedCoresPerFork.isNullOrEmpty()) { |
| test.maxParallelForks = processors.div(userDefinedCoresPerFork.toInt()) |
| } else { |
| // On work machines this seems to give the best test execution time (without freezing). |
| test.maxParallelForks = maxOf(processors.div(8), 1) |
| // On low cpu count machines (bots) we under subscribe, so increase the count. |
| if (processors == 32) { |
| test.maxParallelForks = 15 |
| } |
| } |
| } |
| } |
| } |