Browse Source

Test Gradle plugin on relevant PRs (#2509)

* 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.

#2393
pull/2521/head
Alexey Tsvetkov 1 year ago committed by GitHub
parent
commit
382ad5b78f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 35
      .github/workflows/gradle-plugin.yml
  2. 12
      gradle-plugins/build.gradle.kts
  3. 65
      gradle-plugins/buildSrc/src/main/kotlin/CheckJarPackagesTask.kt
  4. 32
      gradle-plugins/buildSrc/src/main/kotlin/SerializeClasspathTask.kt
  5. 38
      gradle-plugins/buildSrc/src/main/kotlin/gradleUtils.kt
  6. 87
      gradle-plugins/compose/build.gradle.kts
  7. 1
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt
  8. 33
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt
  9. 17
      gradle-plugins/compose/src/main/resources/default-compose-desktop-rules.pro
  10. 37
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt
  11. 146
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/ComposeTestSummary.kt
  12. 3
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt
  13. 6
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt
  14. 1
      gradle-plugins/compose/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener
  15. 9
      gradle-plugins/compose/src/test/test-projects/application/androidx-compiler/build.gradle
  16. 9
      gradle-plugins/compose/src/test/test-projects/application/proguard/build.gradle
  17. 3
      gradle-plugins/compose/src/test/test-projects/application/proguard/rules.pro
  18. 2
      gradle-plugins/gradle.properties
  19. 2
      gradle-plugins/gradle/wrapper/gradle-wrapper.properties
  20. 28
      gradle-plugins/preview-rpc/build.gradle.kts
  21. 6
      gradle-plugins/preview-rpc/src/test/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/utils.kt
  22. 2
      idea-plugin/gradle/wrapper/gradle-wrapper.properties
  23. 2
      tooling/gradle/wrapper/gradle-wrapper.properties

35
.github/workflows/gradle-plugin.yml

@ -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

12
gradle-plugins/build.gradle.kts

@ -1,7 +1,8 @@
import com.gradle.publish.PluginBundleExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins {
val kotlinVersion = "1.5.30"
val kotlinVersion = "1.7.20"
kotlin("jvm") version kotlinVersion apply false
kotlin("plugin.serialization") version kotlinVersion apply false
id("com.gradle.plugin-publish") version "0.17.0" apply false
@ -26,6 +27,15 @@ subprojects {
}
}
plugins.withId("org.jetbrains.kotlin.jvm") {
tasks.withType(KotlinJvmCompile::class).configureEach {
// must be set to a language version of the kotlin compiler & runtime,
// which is bundled to the oldest supported Gradle
kotlinOptions.languageVersion = "1.5"
kotlinOptions.apiVersion = "1.5"
}
}
plugins.withId("maven-publish") {
configureIfExists<PublishingExtension> {
repositories {

65
gradle-plugins/buildSrc/src/main/kotlin/CheckJarPackagesTask.kt

@ -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()}")
}
})
}
}
}

32
gradle-plugins/buildSrc/src/main/kotlin/SerializeClasspathTask.kt

@ -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)
}
}

38
gradle-plugins/buildSrc/src/main/kotlin/gradleUtils.kt

@ -5,10 +5,17 @@
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import java.io.File
@ -35,13 +42,14 @@ fun Test.configureJavaForComposeTest() {
}
}
fun Project.configureJUnit() {
fun Project.configureAllTests(fn: Test.() -> Unit = {}) {
fun DependencyHandler.testImplementation(notation: Any) =
add(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, notation)
dependencies {
testImplementation(platform("org.junit:junit-bom:5.7.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.junit.platform:junit-platform-launcher")
}
tasks.withType<Test>().configureEach {
@ -49,5 +57,31 @@ fun Project.configureJUnit() {
testLogging {
events("passed", "skipped", "failed")
}
fn()
}
}
}
fun Test.systemProperties(map: Map<String, Any>) {
for ((k, v) in map) {
systemProperty(k, v)
}
}
fun TaskProvider<*>.dependsOn(vararg dependencies: Any) {
configure {
dependsOn(dependencies)
}
}
inline fun <reified T : Task> TaskContainer.registerVerificationTask(
name: String,
crossinline fn: T.() -> Unit
): TaskProvider<T> =
register(name, T::class) {
fn()
}.apply {
named("check").dependsOn(this)
}
val Provider<out Jar>.archiveFile: Provider<RegularFile>
get() = flatMap { it.archiveFile }

87
gradle-plugins/compose/build.gradle.kts

@ -1,6 +1,4 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem
import java.util.zip.ZipFile
plugins {
kotlin("jvm")
@ -61,8 +59,6 @@ dependencies {
compileOnly(kotlin("native-utils"))
testImplementation(gradleTestKit())
testImplementation(platform("org.junit:junit-bom:5.7.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation(kotlin("gradle-plugin-api"))
// include relocated download task to avoid potential runtime conflicts
@ -89,62 +85,17 @@ val jar = tasks.named<Jar>("jar") {
this.duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
// __SUPPORTED_GRADLE_VERSIONS__
//testGradleVersion("6.7.1") // min supported by kotlin 1.7.0 gradle plugin https://kotlinlang.org/docs/gradle.html
// despite that, some tests didn't pass
testGradleVersion("7.1.1")
testGradleVersion("7.3.3")
val javaHomeForTests: String? = when {
// __COMPOSE_NATIVE_DISTRIBUTIONS_MIN_JAVA_VERSION__
JavaVersion.current() >= JavaVersion.VERSION_15 -> System.getProperty("java.home")
else -> System.getenv("JDK_15")
?: System.getenv("JDK_FOR_GRADLE_TESTS")
}
val isWindows = getCurrentOperatingSystem().isWindows
val supportedGradleVersions = project.property("compose.tests.gradle.versions")
.toString().split(",")
.map { it.trim() }
val gradleTestsPattern = "org.jetbrains.compose.test.tests.integration.*"
// check we don't accidentally including unexpected classes (e.g. from embedded dependencies)
val checkJar by tasks.registering {
tasks.registerVerificationTask<CheckJarPackagesTask>("checkJar") {
dependsOn(jar)
doLast {
val file = jar.get().archiveFile.get().asFile
ZipFile(file).use { zip ->
checkJarContainsExpectedPackages(zip)
}
}
}
// we want to avoid accidentally including unexpected jars/packages, e.g kotlin-stdlib etc
fun checkJarContainsExpectedPackages(jar: ZipFile) {
val expectedPackages = arrayOf(
"org/jetbrains/compose",
"kotlinx/serialization"
)
val unexpectedClasses = arrayListOf<String>()
for (entry in jar.entries()) {
if (entry.isDirectory || !entry.name.endsWith(".class")) continue
if (expectedPackages.none { prefix -> entry.name.startsWith(prefix) }) {
unexpectedClasses.add(entry.name)
}
}
if (unexpectedClasses.any()) {
error(buildString {
appendLine("Some classes from ${jar.name} are not from 'org.jetbrains.compose' package:")
unexpectedClasses.forEach {
appendLine(" * $it")
}
})
}
}
tasks.check {
dependsOn(checkJar)
jarFile.set(jar.archiveFile)
allowedPackagePrefixes.addAll("org.jetbrains.compose", "kotlinx.serialization")
}
tasks.test {
@ -154,34 +105,24 @@ tasks.test {
excludeTestsMatching(gradleTestsPattern)
}
}
fun testGradleVersion(gradleVersion: String) {
val taskProvider = tasks.register("testGradle-$gradleVersion", Test::class) {
tasks.test.get().let { defaultTest ->
classpath = defaultTest.classpath
}
for (gradleVersion in supportedGradleVersions) {
tasks.registerVerificationTask<Test>("testGradle-$gradleVersion") {
classpath = tasks.test.get().classpath
systemProperty("compose.tests.gradle.version", gradleVersion)
filter {
includeTestsMatching(gradleTestsPattern)
}
}
tasks.named("check") {
dependsOn(taskProvider)
}
}
configureJUnit()
tasks.withType<Test>().configureEach {
configureAllTests {
configureJavaForComposeTest()
dependsOn(":publishToMavenLocal")
systemProperty("compose.tests.compose.gradle.plugin.version", BuildProperties.deployVersion(project))
for ((k, v) in project.properties) {
if (k.startsWith("compose.")) {
systemProperty(k, v.toString())
}
}
val summaryDir = project.buildDir.resolve("test-summary")
systemProperty("compose.tests.summary.file", summaryDir.resolve("$name.md").absolutePath)
systemProperties(project.properties.filter { it.key.startsWith("compose.") })
}
task("printAllAndroidxReplacements") {

1
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt

@ -44,6 +44,7 @@ class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin {
KotlinPlatformType.js -> isApplicableJsTarget(kotlinCompilation.target)
KotlinPlatformType.androidJvm -> true
KotlinPlatformType.native -> true
KotlinPlatformType.wasm -> false
}
private fun isApplicableJsTarget(kotlinTarget: KotlinTarget): Boolean {

33
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt

@ -316,10 +316,8 @@ abstract class AbstractJPackageTask @Inject constructor(
cliArg("--main-jar", mappedJar)
cliArg("--main-class", launcherMainClass)
when (currentOS) {
OS.Windows -> {
cliArg("--win-console", winConsole)
}
if (currentOS == OS.Windows) {
cliArg("--win-console", winConsole)
}
cliArg("--icon", iconFile)
launcherArgs.orNull?.forEach {
@ -369,6 +367,7 @@ abstract class AbstractJPackageTask @Inject constructor(
cliArg("--win-menu-group", winMenuGroup)
cliArg("--win-upgrade-uuid", winUpgradeUuid)
}
OS.MacOS -> {}
}
}
@ -383,20 +382,18 @@ abstract class AbstractJPackageTask @Inject constructor(
cliArg("--app-version", packageVersion)
cliArg("--vendor", packageVendor)
when (currentOS) {
OS.MacOS -> {
cliArg("--mac-package-name", macPackageName)
cliArg("--mac-package-identifier", nonValidatedMacBundleID)
cliArg("--mac-app-store", macAppStore)
cliArg("--mac-app-category", macAppCategory)
cliArg("--mac-entitlements", macEntitlementsFile)
macSigner?.let { signer ->
cliArg("--mac-sign", true)
cliArg("--mac-signing-key-user-name", signer.settings.identity)
cliArg("--mac-signing-keychain", signer.settings.keychain)
cliArg("--mac-package-signing-prefix", signer.settings.prefix)
}
if (currentOS == OS.MacOS) {
cliArg("--mac-package-name", macPackageName)
cliArg("--mac-package-identifier", nonValidatedMacBundleID)
cliArg("--mac-app-store", macAppStore)
cliArg("--mac-app-category", macAppCategory)
cliArg("--mac-entitlements", macEntitlementsFile)
macSigner?.let { signer ->
cliArg("--mac-sign", true)
cliArg("--mac-signing-key-user-name", signer.settings.identity)
cliArg("--mac-signing-keychain", signer.settings.keychain)
cliArg("--mac-package-signing-prefix", signer.settings.prefix)
}
}
}

17
gradle-plugins/compose/src/main/resources/default-compose-desktop-rules.pro

@ -30,4 +30,19 @@
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# https://github.com/Kotlin/kotlinx.coroutines/issues/2046
-dontwarn android.annotation.SuppressLint
-dontwarn android.annotation.SuppressLint
# https://github.com/JetBrains/compose-jb/issues/2393
-dontnote kotlin.coroutines.jvm.internal.**
-dontnote kotlin.internal.**
-dontnote kotlin.jvm.internal.**
-dontnote kotlin.reflect.**
-dontnote kotlinx.coroutines.debug.internal.**
-dontnote kotlinx.coroutines.internal.**
-keep class kotlin.coroutines.Continuation
-keep class kotlinx.coroutines.CancellableContinuation
-keep class kotlinx.coroutines.channels.Channel
-keep class kotlinx.coroutines.CoroutineDispatcher
-keep class kotlinx.coroutines.CoroutineScope
# this is a weird one, but breaks build on some combinations of OS and JDK (reproduced on Windows 10 + Corretto 16)
-dontwarn org.graalvm.compiler.core.aarch64.AArch64NodeMatchRules_MatchStatementSet*

37
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt

@ -18,6 +18,7 @@ import kotlin.collections.HashSet
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class DesktopApplicationTest : GradlePluginTestBase() {
@ -77,7 +78,11 @@ class DesktopApplicationTest : GradlePluginTestBase() {
}
@Test
fun proguard(): Unit = with(testProject(TestProjects.proguard)) {
fun proguard(): Unit = with(
testProject(
TestProjects.proguard,
testEnvironment = defaultTestEnvironment.copy(composeVerbose = false))
) {
val enableObfuscation = """
compose.desktop {
application {
@ -117,11 +122,6 @@ class DesktopApplicationTest : GradlePluginTestBase() {
}
}
@Test
fun packageJvm() = with(testProject(TestProjects.jvm)) {
testPackageJvmDistributions()
}
@Test
fun gradleBuildCache() = with(testProject(TestProjects.jvm)) {
modifyGradleProperties {
@ -148,6 +148,11 @@ class DesktopApplicationTest : GradlePluginTestBase() {
}
}
@Test
fun packageJvm() = with(testProject(TestProjects.jvm)) {
testPackageJvmDistributions()
}
@Test
fun packageMpp() = with(testProject(TestProjects.mpp)) {
testPackageJvmDistributions()
@ -174,11 +179,19 @@ class DesktopApplicationTest : GradlePluginTestBase() {
val packageFile = packageDirFiles.single()
if (currentOS == OS.Linux) {
val isTestPackage = packageFile.name.contains("test-package", ignoreCase = true) ||
packageFile.name.contains("testpackage", ignoreCase = true)
val isDeb = packageFile.name.endsWith(".$ext")
check(isTestPackage && isDeb) {
"Expected contain testpackage*.deb or test-package*.deb package in $packageDir, got '${packageFile.name}'"
// The default naming scheme was changed in JDK 18
// https://bugs.openjdk.org/browse/JDK-8276084
// This test might be used with different JDKs,
// so as a workaround we check that the
// package name is either one of two expected values.
// TODO: Check a corresponding value for each JDK
val possibleNames = listOf(
"test-package_1.0.0-1_amd64.$ext",
"test-package_1.0.0_amd64.$ext",
)
check(possibleNames.any { packageFile.name.equals(it, ignoreCase = true) }) {
"Unexpected package name '${packageFile.name}' in $packageDir\n" +
"Possible names: ${possibleNames.joinToString(", ") { "'$it'" }}"
}
} else {
Assert.assertEquals(packageFile.name, "TestPackage-1.0.0.$ext", "Unexpected package name")
@ -285,6 +298,8 @@ class DesktopApplicationTest : GradlePluginTestBase() {
}
@Test
@Disabled
// the test does not work on CI and locally unless test keychain is opened manually
fun testMacSign() {
Assumptions.assumeTrue(currentOS == OS.MacOS)

146
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/ComposeTestSummary.kt

@ -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")
}
}

3
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt

@ -15,6 +15,7 @@ data class TestEnvironment(
val kotlinVersion: String = TestKotlinVersions.Default,
val composeGradlePluginVersion: String = TestProperties.composeGradlePluginVersion,
val composeCompilerArtifact: String? = null,
val composeVerbose: Boolean = true
) {
private val placeholders = linkedMapOf(
"COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER" to composeGradlePluginVersion,
@ -41,7 +42,7 @@ class TestProject(
private val additionalArgs = listOf(
"--stacktrace",
"--init-script", testProjectsRootDir.resolve("init.gradle").absolutePath,
"-P${ComposeProperties.VERBOSE}=true"
"-P${ComposeProperties.VERBOSE}=${testEnvironment.composeVerbose}"
)
init {

6
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt

@ -5,6 +5,8 @@
package org.jetbrains.compose.test.utils
import java.io.File
object TestProperties {
val composeCompilerVersion: String
get() = notNullSystemProperty("compose.tests.compiler.version")
@ -27,6 +29,10 @@ object TestProperties {
val gradleVersionForTests: String?
get() = System.getProperty("compose.tests.gradle.version")
val summaryFile: File?
get() = System.getProperty("compose.tests.summary.file")?.let { File(it) }
private fun notNullSystemProperty(property: String): String =
System.getProperty(property) ?: error("The '$property' system property is not set")
}

1
gradle-plugins/compose/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener

@ -0,0 +1 @@
org.jetbrains.compose.test.utils.ComposeTestSummary

9
gradle-plugins/compose/src/test/test-projects/application/androidx-compiler/build.gradle

@ -1,3 +1,4 @@
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
@ -21,11 +22,15 @@ compose {
desktop {
application {
mainClass = "Main"
args(project.projectDir.absolutePath)
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
}
args(project.projectDir.absolutePath)
def projectPath = project.projectDir.absolutePath
if (DefaultNativePlatform.currentOperatingSystem.isWindows()) {
projectPath = projectPath.replace("\\", "\\\\")
}
args(projectPath)
}
}
}

9
gradle-plugins/compose/src/test/test-projects/application/proguard/build.gradle

@ -1,3 +1,4 @@
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
@ -17,11 +18,15 @@ dependencies {
compose.desktop {
application {
mainClass = "Main"
args(project.projectDir.absolutePath)
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
}
args(project.projectDir.absolutePath)
def projectPath = project.projectDir.absolutePath
if (DefaultNativePlatform.currentOperatingSystem.isWindows()) {
projectPath = projectPath.replace("\\", "\\\\")
}
args(projectPath)
buildTypes.release.proguard {
configurationFiles.from("rules.pro")

3
gradle-plugins/compose/src/test/test-projects/application/proguard/rules.pro

@ -1,3 +1,4 @@
-keep public class Main {
public void keptByKeepRule(...);
}
}
-dontnote

2
gradle-plugins/gradle.properties

@ -14,6 +14,8 @@ compose.tests.js.compiler.compatible.kotlin.version=1.7.20
# https://developer.android.com/jetpack/androidx/releases/compose-kotlin
compose.tests.androidx.compiler.version=1.1.1
compose.tests.androidx.compiler.compatible.kotlin.version=1.6.10
# __SUPPORTED_GRADLE_VERSIONS__
compose.tests.gradle.versions=7.0.2, 7.6
# A version of Gradle plugin, that will be published,
# unless overridden by COMPOSE_GRADLE_PLUGIN_VERSION env var.

2
gradle-plugins/gradle/wrapper/gradle-wrapper.properties vendored

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

28
gradle-plugins/preview-rpc/build.gradle.kts

@ -13,20 +13,24 @@ dependencies {
implementation(kotlin("stdlib"))
}
configureJUnit()
configureAllTests()
val serializeClasspath by tasks.registering(SerializeClasspathTask::class) {
val runtimeClasspath = configurations.runtimeClasspath
val jar = tasks.jar
dependsOn(runtimeClasspath, jar)
classpathFileCollection.from(jar.flatMap { it.archiveFile })
classpathFileCollection.from(runtimeClasspath)
outputFile.set(project.layout.buildDirectory.file("rpc.classpath.txt"))
}
tasks.test.configure {
configureJavaForComposeTest()
val runtimeClasspath = configurations.runtimeClasspath
dependsOn(runtimeClasspath)
val jar = tasks.jar
dependsOn(jar)
doFirst {
val rpcClasspath = LinkedHashSet<File>()
rpcClasspath.add(jar.get().archiveFile.get().asFile)
rpcClasspath.addAll(runtimeClasspath.get().files)
val classpathString = rpcClasspath.joinToString(File.pathSeparator) { it.absolutePath }
systemProperty("org.jetbrains.compose.test.rpc.classpath", classpathString)
}
dependsOn(serializeClasspath)
systemProperty(
"org.jetbrains.compose.tests.rpc.classpath.file",
serializeClasspath.get().outputFile.get().asFile.absolutePath
)
}

6
gradle-plugins/preview-rpc/src/test/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/utils.kt

@ -5,6 +5,8 @@
package org.jetbrains.compose.desktop.ui.tooling.preview.rpc.utils
import java.io.File
internal fun systemProperty(name: String): String =
System.getProperty(name) ?: error("System property is not found: '$name'")
@ -12,7 +14,9 @@ internal val isWindows =
systemProperty("os.name").startsWith("windows", ignoreCase = true)
internal val previewTestClaspath: String
get() = systemProperty("org.jetbrains.compose.test.rpc.classpath")
get() = systemProperty("org.jetbrains.compose.tests.rpc.classpath.file").let {
File(it).readText()
}
internal val Int.secondsAsMillis: Int
get() = this * 1000

2
idea-plugin/gradle/wrapper/gradle-wrapper.properties vendored

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

2
tooling/gradle/wrapper/gradle-wrapper.properties vendored

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

Loading…
Cancel
Save