Browse Source
* Update Gradle used in tooling subprojects * Update Kotlin in Compose Gradle plugin * Decrease verbosity of Gradle plugin tests * Disable mac sign test * Add workflow to test Gradle plugin * Fix custom jdk tests on Linux * Make Compose Gradle plugin build compatible with Configuration cache * Print tests summary * Remove unused code * Refactor tests configuration * Turn off parallel execution * Try adding windows runner * Turn off fail fast * Fix Windows test issues #2368 * Adjust default proguard rules The following rule is needed to fix tests on Windows: ``` -dontwarn org.graalvm.compiler.core.aarch64.AArch64NodeMatchRules_MatchStatementSet* ``` Other rules are just to make builds less noisy. Kotlin's `*.internal` packages often contain bytecode, which triggers ProGuard's notes. However, these notes are not actionable for most users, so we can ignore notes by default. #2393pull/2521/head
Alexey Tsvetkov
2 years ago
committed by
GitHub
23 changed files with 448 additions and 128 deletions
@ -0,0 +1,35 @@ |
|||||||
|
name: Test Gradle plugin |
||||||
|
on: |
||||||
|
pull_request: |
||||||
|
paths: |
||||||
|
- 'gradle-plugins/**' |
||||||
|
- '.github/workflows/gradle-plugin.yml' |
||||||
|
jobs: |
||||||
|
test-gradle-plugin: |
||||||
|
strategy: |
||||||
|
fail-fast: false |
||||||
|
matrix: |
||||||
|
os: [ubuntu-20.04, macos-12, windows-2022] |
||||||
|
runs-on: ${{ matrix.os }} |
||||||
|
steps: |
||||||
|
- uses: actions/checkout@v3 |
||||||
|
- uses: actions/setup-java@v3 |
||||||
|
with: |
||||||
|
distribution: 'corretto' |
||||||
|
java-version: '16' |
||||||
|
- name: Test Gradle plugin |
||||||
|
shell: bash |
||||||
|
run: | |
||||||
|
cd gradle-plugins |
||||||
|
./gradlew assemble |
||||||
|
./gradlew :compose:check --continue |
||||||
|
- name: Print summary |
||||||
|
shell: bash |
||||||
|
if: always() |
||||||
|
run: | |
||||||
|
cd gradle-plugins/compose/build/test-summary |
||||||
|
for SUMMARY_FILE in `find . -name "*.md"`; do |
||||||
|
FILE_NAME=`basename $SUMMARY_FILE` |
||||||
|
echo "## $FILE_NAME" >> $GITHUB_STEP_SUMMARY |
||||||
|
cat $SUMMARY_FILE >> $GITHUB_STEP_SUMMARY |
||||||
|
done |
@ -0,0 +1,65 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask |
||||||
|
import org.gradle.api.file.RegularFile |
||||||
|
import org.gradle.api.model.ObjectFactory |
||||||
|
import org.gradle.api.provider.Property |
||||||
|
import org.gradle.api.provider.SetProperty |
||||||
|
import org.gradle.api.tasks.Input |
||||||
|
import org.gradle.api.tasks.InputFile |
||||||
|
import org.gradle.api.tasks.TaskAction |
||||||
|
import java.util.* |
||||||
|
import java.util.zip.ZipFile |
||||||
|
import javax.inject.Inject |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks that every class in a [jarFile] matches one of [allowedPackagePrefixes] |
||||||
|
*/ |
||||||
|
abstract class CheckJarPackagesTask @Inject constructor( |
||||||
|
objects: ObjectFactory |
||||||
|
) : DefaultTask() { |
||||||
|
@get:InputFile |
||||||
|
val jarFile: Property<RegularFile> = objects.fileProperty() |
||||||
|
|
||||||
|
@get:Input |
||||||
|
val allowedPackagePrefixes: SetProperty<String> = objects.setProperty(String::class.java) |
||||||
|
|
||||||
|
@TaskAction |
||||||
|
fun run() { |
||||||
|
ZipFile(jarFile.get().asFile).use { zip -> |
||||||
|
checkJarContainsExpectedPackages(zip) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun checkJarContainsExpectedPackages(jar: ZipFile) { |
||||||
|
val unexpectedClasses = arrayListOf<String>() |
||||||
|
val allowedPrefixes = allowedPackagePrefixes.get().map { it.replace(".", "/") } |
||||||
|
|
||||||
|
for (entry in jar.entries()) { |
||||||
|
if (entry.isDirectory || !entry.name.endsWith(".class")) continue |
||||||
|
|
||||||
|
if (allowedPrefixes.none { prefix -> entry.name.startsWith(prefix) }) { |
||||||
|
unexpectedClasses.add(entry.name) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (unexpectedClasses.any()) { |
||||||
|
error(buildString { |
||||||
|
appendLine("All classes in ${jar.name} must match allowed prefixes:") |
||||||
|
allowedPrefixes.forEach { |
||||||
|
appendLine(" * $it") |
||||||
|
} |
||||||
|
appendLine("Non-valid classes:") |
||||||
|
val unexpectedGroups = unexpectedClasses |
||||||
|
.groupByTo(TreeMap()) { it.substringBeforeLast("/") } |
||||||
|
for ((_, classes) in unexpectedGroups) { |
||||||
|
appendLine(" * ${classes.first()}") |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,32 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask |
||||||
|
import org.gradle.api.file.ConfigurableFileCollection |
||||||
|
import org.gradle.api.file.RegularFileProperty |
||||||
|
import org.gradle.api.model.ObjectFactory |
||||||
|
import org.gradle.api.tasks.InputFiles |
||||||
|
import org.gradle.api.tasks.OutputFile |
||||||
|
import org.gradle.api.tasks.TaskAction |
||||||
|
import java.io.File |
||||||
|
import javax.inject.Inject |
||||||
|
|
||||||
|
abstract class SerializeClasspathTask @Inject constructor( |
||||||
|
objects: ObjectFactory |
||||||
|
) : DefaultTask() { |
||||||
|
@get:InputFiles |
||||||
|
val classpathFileCollection: ConfigurableFileCollection = objects.fileCollection() |
||||||
|
|
||||||
|
@get:OutputFile |
||||||
|
val outputFile: RegularFileProperty = objects.fileProperty() |
||||||
|
|
||||||
|
@TaskAction |
||||||
|
fun run() { |
||||||
|
val classpath = classpathFileCollection.files.joinToString(File.pathSeparator) { it.absolutePath } |
||||||
|
val outputFile = outputFile.get().asFile |
||||||
|
outputFile.parentFile.mkdirs() |
||||||
|
outputFile.writeText(classpath) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,146 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.test.utils |
||||||
|
|
||||||
|
import org.junit.platform.engine.TestExecutionResult |
||||||
|
import org.junit.platform.engine.support.descriptor.MethodSource |
||||||
|
import org.junit.platform.launcher.TestExecutionListener |
||||||
|
import org.junit.platform.launcher.TestIdentifier |
||||||
|
import org.junit.platform.launcher.TestPlan |
||||||
|
import java.io.File |
||||||
|
import java.io.Writer |
||||||
|
|
||||||
|
class ComposeTestSummary : TestExecutionListener { |
||||||
|
private val summaryFile = TestProperties.summaryFile |
||||||
|
private val isEnabled = summaryFile != null |
||||||
|
private val startNanoTime = hashMapOf<TestIdentifier, Long>() |
||||||
|
private val results = arrayListOf<TestResult>() |
||||||
|
|
||||||
|
override fun executionStarted(testIdentifier: TestIdentifier) { |
||||||
|
if (isEnabled && testIdentifier.isTest) { |
||||||
|
startNanoTime[testIdentifier] = System.nanoTime() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun executionSkipped(testIdentifier: TestIdentifier, reason: String?) { |
||||||
|
if (isEnabled && testIdentifier.isTest) { |
||||||
|
addTestResult(testIdentifier, TestResult.Status.Skipped, durationMs = null) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun executionFinished(testIdentifier: TestIdentifier, testExecutionResult: TestExecutionResult) { |
||||||
|
if (isEnabled && testIdentifier.isTest) { |
||||||
|
val durationMs = (System.nanoTime() - startNanoTime[testIdentifier]!!) / 1_000_000 |
||||||
|
val status = when (testExecutionResult.status!!) { |
||||||
|
TestExecutionResult.Status.SUCCESSFUL -> TestResult.Status.Successful |
||||||
|
TestExecutionResult.Status.ABORTED -> TestResult.Status.Aborted |
||||||
|
TestExecutionResult.Status.FAILED -> |
||||||
|
TestResult.Status.Failed( |
||||||
|
testExecutionResult.throwable.orElse(null) |
||||||
|
) |
||||||
|
} |
||||||
|
addTestResult(testIdentifier, status, durationMs = durationMs) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun testPlanExecutionFinished(testPlan: TestPlan) { |
||||||
|
if (isEnabled) { |
||||||
|
MarkdownSummary.write(results, summaryFile!!) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun addTestResult( |
||||||
|
identifier: TestIdentifier, |
||||||
|
status: TestResult.Status, |
||||||
|
durationMs: Long? |
||||||
|
) { |
||||||
|
val result = TestResult( |
||||||
|
testCase = identifier.displayName, |
||||||
|
testClass = (identifier.source.get() as? MethodSource)?.className ?: "", |
||||||
|
status = status, |
||||||
|
durationMs = durationMs |
||||||
|
) |
||||||
|
results.add(result) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal data class TestResult( |
||||||
|
val testCase: String, |
||||||
|
val testClass: String, |
||||||
|
val durationMs: Long?, |
||||||
|
val status: Status |
||||||
|
) { |
||||||
|
sealed class Status { |
||||||
|
object Successful : Status() |
||||||
|
object Aborted : Status() |
||||||
|
object Skipped : Status() |
||||||
|
class Failed(val exception: Throwable?) : Status() |
||||||
|
} |
||||||
|
|
||||||
|
val displayName: String |
||||||
|
get() = "${testClass.substringAfterLast(".")}.$testCase" |
||||||
|
} |
||||||
|
|
||||||
|
internal object MarkdownSummary { |
||||||
|
fun write(testResults: List<TestResult>, file: File) { |
||||||
|
file.parentFile.mkdirs() |
||||||
|
file.bufferedWriter().use { writer -> |
||||||
|
writer.writeSummary(testResults) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun Writer.writeSummary(testResults: List<TestResult>) { |
||||||
|
writeLn() |
||||||
|
writeLn("|Status|Test case|Duration|") |
||||||
|
writeLn("|---|---|---:|") |
||||||
|
|
||||||
|
for (result in testResults) { |
||||||
|
val status = when (result.status) { |
||||||
|
is TestResult.Status.Successful -> ":white_check_mark:" |
||||||
|
is TestResult.Status.Aborted -> ":fast_forward:" |
||||||
|
is TestResult.Status.Failed -> ":x:" |
||||||
|
is TestResult.Status.Skipped -> ":fast_forward:" |
||||||
|
} |
||||||
|
writeLn("|$status|${result.displayName}|${result.durationMs ?: 0} ms|") |
||||||
|
} |
||||||
|
|
||||||
|
val failedTests = testResults.filter { it.status is TestResult.Status.Failed } |
||||||
|
if (failedTests.isEmpty()) return |
||||||
|
|
||||||
|
writeLn("#### ${failedTests.size} failed tests") |
||||||
|
for (failedTest in failedTests) { |
||||||
|
withDetails(failedTest.displayName) { |
||||||
|
withHtmlTag("samp") { |
||||||
|
val exception = (failedTest.status as TestResult.Status.Failed).exception |
||||||
|
val stacktrace = exception?.stackTraceToString() ?: "" |
||||||
|
write(stacktrace.replace("\n", "<br/>")) |
||||||
|
} |
||||||
|
} |
||||||
|
writeLn() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private inline fun Writer.withDetails(summary: String, details: Writer.() -> Unit) { |
||||||
|
withHtmlTag("details") { |
||||||
|
withHtmlTag("summary") { |
||||||
|
write(summary) |
||||||
|
} |
||||||
|
|
||||||
|
details() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private inline fun Writer.withHtmlTag(tag: String, fn: Writer.() -> Unit) { |
||||||
|
writeLn("<$tag>") |
||||||
|
fn() |
||||||
|
writeLn("</$tag>") |
||||||
|
} |
||||||
|
|
||||||
|
private fun Writer.writeLn(str: String = "") { |
||||||
|
write(str) |
||||||
|
write("\n") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
org.jetbrains.compose.test.utils.ComposeTestSummary |
@ -1,3 +1,4 @@ |
|||||||
-keep public class Main { |
-keep public class Main { |
||||||
public void keptByKeepRule(...); |
public void keptByKeepRule(...); |
||||||
} |
} |
||||||
|
-dontnote |
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
||||||
|
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
||||||
|
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
||||||
|
Loading…
Reference in new issue