| // 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.File |
| import java.io.PrintStream |
| 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") |
| } |
| 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 = 1000 |
| 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(3), 1) |
| // On low cpu count machines (bots) we under subscribe, so increase the count. |
| if (processors == 32) { |
| test.maxParallelForks = 15 |
| } |
| } |
| } |
| } |
| } |