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 2 years 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 com.gradle.publish.PluginBundleExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins { plugins {
val kotlinVersion = "1.5.30" val kotlinVersion = "1.7.20"
kotlin("jvm") version kotlinVersion apply false kotlin("jvm") version kotlinVersion apply false
kotlin("plugin.serialization") version kotlinVersion apply false kotlin("plugin.serialization") version kotlinVersion apply false
id("com.gradle.plugin-publish") version "0.17.0" 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") { plugins.withId("maven-publish") {
configureIfExists<PublishingExtension> { configureIfExists<PublishingExtension> {
repositories { 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.JavaVersion
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.JavaPlugin 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.api.tasks.testing.Test
import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType import org.gradle.kotlin.dsl.withType
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import java.io.File 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) = fun DependencyHandler.testImplementation(notation: Any) =
add(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, notation) add(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, notation)
dependencies { dependencies {
testImplementation(platform("org.junit:junit-bom:5.7.0")) testImplementation(platform("org.junit:junit-bom:5.7.0"))
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.junit.platform:junit-platform-launcher")
} }
tasks.withType<Test>().configureEach { tasks.withType<Test>().configureEach {
@ -49,5 +57,31 @@ fun Project.configureJUnit() {
testLogging { testLogging {
events("passed", "skipped", "failed") 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 com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem
import java.util.zip.ZipFile
plugins { plugins {
kotlin("jvm") kotlin("jvm")
@ -61,8 +59,6 @@ dependencies {
compileOnly(kotlin("native-utils")) compileOnly(kotlin("native-utils"))
testImplementation(gradleTestKit()) testImplementation(gradleTestKit())
testImplementation(platform("org.junit:junit-bom:5.7.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation(kotlin("gradle-plugin-api")) testImplementation(kotlin("gradle-plugin-api"))
// include relocated download task to avoid potential runtime conflicts // include relocated download task to avoid potential runtime conflicts
@ -89,62 +85,17 @@ val jar = tasks.named<Jar>("jar") {
this.duplicatesStrategy = DuplicatesStrategy.INCLUDE this.duplicatesStrategy = DuplicatesStrategy.INCLUDE
} }
// __SUPPORTED_GRADLE_VERSIONS__ val supportedGradleVersions = project.property("compose.tests.gradle.versions")
//testGradleVersion("6.7.1") // min supported by kotlin 1.7.0 gradle plugin https://kotlinlang.org/docs/gradle.html .toString().split(",")
// despite that, some tests didn't pass .map { it.trim() }
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 gradleTestsPattern = "org.jetbrains.compose.test.tests.integration.*" val gradleTestsPattern = "org.jetbrains.compose.test.tests.integration.*"
// check we don't accidentally including unexpected classes (e.g. from embedded dependencies) // check we don't accidentally including unexpected classes (e.g. from embedded dependencies)
val checkJar by tasks.registering { tasks.registerVerificationTask<CheckJarPackagesTask>("checkJar") {
dependsOn(jar) dependsOn(jar)
jarFile.set(jar.archiveFile)
doLast { allowedPackagePrefixes.addAll("org.jetbrains.compose", "kotlinx.serialization")
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)
} }
tasks.test { tasks.test {
@ -154,34 +105,24 @@ tasks.test {
excludeTestsMatching(gradleTestsPattern) excludeTestsMatching(gradleTestsPattern)
} }
} }
fun testGradleVersion(gradleVersion: String) {
val taskProvider = tasks.register("testGradle-$gradleVersion", Test::class) { for (gradleVersion in supportedGradleVersions) {
tasks.test.get().let { defaultTest -> tasks.registerVerificationTask<Test>("testGradle-$gradleVersion") {
classpath = defaultTest.classpath classpath = tasks.test.get().classpath
}
systemProperty("compose.tests.gradle.version", gradleVersion) systemProperty("compose.tests.gradle.version", gradleVersion)
filter { filter {
includeTestsMatching(gradleTestsPattern) includeTestsMatching(gradleTestsPattern)
} }
} }
tasks.named("check") {
dependsOn(taskProvider)
}
} }
configureJUnit() configureAllTests {
tasks.withType<Test>().configureEach {
configureJavaForComposeTest() configureJavaForComposeTest()
dependsOn(":publishToMavenLocal") dependsOn(":publishToMavenLocal")
systemProperty("compose.tests.compose.gradle.plugin.version", BuildProperties.deployVersion(project)) systemProperty("compose.tests.compose.gradle.plugin.version", BuildProperties.deployVersion(project))
for ((k, v) in project.properties) { val summaryDir = project.buildDir.resolve("test-summary")
if (k.startsWith("compose.")) { systemProperty("compose.tests.summary.file", summaryDir.resolve("$name.md").absolutePath)
systemProperty(k, v.toString()) systemProperties(project.properties.filter { it.key.startsWith("compose.") })
}
}
} }
task("printAllAndroidxReplacements") { 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.js -> isApplicableJsTarget(kotlinCompilation.target)
KotlinPlatformType.androidJvm -> true KotlinPlatformType.androidJvm -> true
KotlinPlatformType.native -> true KotlinPlatformType.native -> true
KotlinPlatformType.wasm -> false
} }
private fun isApplicableJsTarget(kotlinTarget: KotlinTarget): Boolean { 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-jar", mappedJar)
cliArg("--main-class", launcherMainClass) cliArg("--main-class", launcherMainClass)
when (currentOS) { if (currentOS == OS.Windows) {
OS.Windows -> { cliArg("--win-console", winConsole)
cliArg("--win-console", winConsole)
}
} }
cliArg("--icon", iconFile) cliArg("--icon", iconFile)
launcherArgs.orNull?.forEach { launcherArgs.orNull?.forEach {
@ -369,6 +367,7 @@ abstract class AbstractJPackageTask @Inject constructor(
cliArg("--win-menu-group", winMenuGroup) cliArg("--win-menu-group", winMenuGroup)
cliArg("--win-upgrade-uuid", winUpgradeUuid) cliArg("--win-upgrade-uuid", winUpgradeUuid)
} }
OS.MacOS -> {}
} }
} }
@ -383,20 +382,18 @@ abstract class AbstractJPackageTask @Inject constructor(
cliArg("--app-version", packageVersion) cliArg("--app-version", packageVersion)
cliArg("--vendor", packageVendor) cliArg("--vendor", packageVendor)
when (currentOS) { if (currentOS == OS.MacOS) {
OS.MacOS -> { cliArg("--mac-package-name", macPackageName)
cliArg("--mac-package-name", macPackageName) cliArg("--mac-package-identifier", nonValidatedMacBundleID)
cliArg("--mac-package-identifier", nonValidatedMacBundleID) cliArg("--mac-app-store", macAppStore)
cliArg("--mac-app-store", macAppStore) cliArg("--mac-app-category", macAppCategory)
cliArg("--mac-app-category", macAppCategory) cliArg("--mac-entitlements", macEntitlementsFile)
cliArg("--mac-entitlements", macEntitlementsFile)
macSigner?.let { signer ->
macSigner?.let { signer -> cliArg("--mac-sign", true)
cliArg("--mac-sign", true) cliArg("--mac-signing-key-user-name", signer.settings.identity)
cliArg("--mac-signing-key-user-name", signer.settings.identity) cliArg("--mac-signing-keychain", signer.settings.keychain)
cliArg("--mac-signing-keychain", signer.settings.keychain) cliArg("--mac-package-signing-prefix", signer.settings.prefix)
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 -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# https://github.com/Kotlin/kotlinx.coroutines/issues/2046 # 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.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class DesktopApplicationTest : GradlePluginTestBase() { class DesktopApplicationTest : GradlePluginTestBase() {
@ -77,7 +78,11 @@ class DesktopApplicationTest : GradlePluginTestBase() {
} }
@Test @Test
fun proguard(): Unit = with(testProject(TestProjects.proguard)) { fun proguard(): Unit = with(
testProject(
TestProjects.proguard,
testEnvironment = defaultTestEnvironment.copy(composeVerbose = false))
) {
val enableObfuscation = """ val enableObfuscation = """
compose.desktop { compose.desktop {
application { application {
@ -117,11 +122,6 @@ class DesktopApplicationTest : GradlePluginTestBase() {
} }
} }
@Test
fun packageJvm() = with(testProject(TestProjects.jvm)) {
testPackageJvmDistributions()
}
@Test @Test
fun gradleBuildCache() = with(testProject(TestProjects.jvm)) { fun gradleBuildCache() = with(testProject(TestProjects.jvm)) {
modifyGradleProperties { modifyGradleProperties {
@ -148,6 +148,11 @@ class DesktopApplicationTest : GradlePluginTestBase() {
} }
} }
@Test
fun packageJvm() = with(testProject(TestProjects.jvm)) {
testPackageJvmDistributions()
}
@Test @Test
fun packageMpp() = with(testProject(TestProjects.mpp)) { fun packageMpp() = with(testProject(TestProjects.mpp)) {
testPackageJvmDistributions() testPackageJvmDistributions()
@ -174,11 +179,19 @@ class DesktopApplicationTest : GradlePluginTestBase() {
val packageFile = packageDirFiles.single() val packageFile = packageDirFiles.single()
if (currentOS == OS.Linux) { if (currentOS == OS.Linux) {
val isTestPackage = packageFile.name.contains("test-package", ignoreCase = true) || // The default naming scheme was changed in JDK 18
packageFile.name.contains("testpackage", ignoreCase = true) // https://bugs.openjdk.org/browse/JDK-8276084
val isDeb = packageFile.name.endsWith(".$ext") // This test might be used with different JDKs,
check(isTestPackage && isDeb) { // so as a workaround we check that the
"Expected contain testpackage*.deb or test-package*.deb package in $packageDir, got '${packageFile.name}'" // 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 { } else {
Assert.assertEquals(packageFile.name, "TestPackage-1.0.0.$ext", "Unexpected package name") Assert.assertEquals(packageFile.name, "TestPackage-1.0.0.$ext", "Unexpected package name")
@ -285,6 +298,8 @@ class DesktopApplicationTest : GradlePluginTestBase() {
} }
@Test @Test
@Disabled
// the test does not work on CI and locally unless test keychain is opened manually
fun testMacSign() { fun testMacSign() {
Assumptions.assumeTrue(currentOS == OS.MacOS) 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 kotlinVersion: String = TestKotlinVersions.Default,
val composeGradlePluginVersion: String = TestProperties.composeGradlePluginVersion, val composeGradlePluginVersion: String = TestProperties.composeGradlePluginVersion,
val composeCompilerArtifact: String? = null, val composeCompilerArtifact: String? = null,
val composeVerbose: Boolean = true
) { ) {
private val placeholders = linkedMapOf( private val placeholders = linkedMapOf(
"COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER" to composeGradlePluginVersion, "COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER" to composeGradlePluginVersion,
@ -41,7 +42,7 @@ class TestProject(
private val additionalArgs = listOf( private val additionalArgs = listOf(
"--stacktrace", "--stacktrace",
"--init-script", testProjectsRootDir.resolve("init.gradle").absolutePath, "--init-script", testProjectsRootDir.resolve("init.gradle").absolutePath,
"-P${ComposeProperties.VERBOSE}=true" "-P${ComposeProperties.VERBOSE}=${testEnvironment.composeVerbose}"
) )
init { 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 package org.jetbrains.compose.test.utils
import java.io.File
object TestProperties { object TestProperties {
val composeCompilerVersion: String val composeCompilerVersion: String
get() = notNullSystemProperty("compose.tests.compiler.version") get() = notNullSystemProperty("compose.tests.compiler.version")
@ -27,6 +29,10 @@ object TestProperties {
val gradleVersionForTests: String? val gradleVersionForTests: String?
get() = System.getProperty("compose.tests.gradle.version") 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 = private fun notNullSystemProperty(property: String): String =
System.getProperty(property) ?: error("The '$property' system property is not set") 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 import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins { plugins {
@ -21,11 +22,15 @@ compose {
desktop { desktop {
application { application {
mainClass = "Main" mainClass = "Main"
args(project.projectDir.absolutePath)
nativeDistributions { nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 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 import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins { plugins {
@ -17,11 +18,15 @@ dependencies {
compose.desktop { compose.desktop {
application { application {
mainClass = "Main" mainClass = "Main"
args(project.projectDir.absolutePath)
nativeDistributions { nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 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 { buildTypes.release.proguard {
configurationFiles.from("rules.pro") configurationFiles.from("rules.pro")

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

@ -1,3 +1,4 @@
-keep public class Main { -keep public class Main {
public void keptByKeepRule(...); 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 # https://developer.android.com/jetpack/androidx/releases/compose-kotlin
compose.tests.androidx.compiler.version=1.1.1 compose.tests.androidx.compiler.version=1.1.1
compose.tests.androidx.compiler.compatible.kotlin.version=1.6.10 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, # A version of Gradle plugin, that will be published,
# unless overridden by COMPOSE_GRADLE_PLUGIN_VERSION env var. # 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 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

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

@ -13,20 +13,24 @@ dependencies {
implementation(kotlin("stdlib")) 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 { tasks.test.configure {
configureJavaForComposeTest() configureJavaForComposeTest()
val runtimeClasspath = configurations.runtimeClasspath dependsOn(serializeClasspath)
dependsOn(runtimeClasspath) systemProperty(
val jar = tasks.jar "org.jetbrains.compose.tests.rpc.classpath.file",
dependsOn(jar) serializeClasspath.get().outputFile.get().asFile.absolutePath
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)
}
} }

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 package org.jetbrains.compose.desktop.ui.tooling.preview.rpc.utils
import java.io.File
internal fun systemProperty(name: String): String = internal fun systemProperty(name: String): String =
System.getProperty(name) ?: error("System property is not found: '$name'") System.getProperty(name) ?: error("System property is not found: '$name'")
@ -12,7 +14,9 @@ internal val isWindows =
systemProperty("os.name").startsWith("windows", ignoreCase = true) systemProperty("os.name").startsWith("windows", ignoreCase = true)
internal val previewTestClaspath: String 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 internal val Int.secondsAsMillis: Int
get() = this * 1000 get() = this * 1000

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

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

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

@ -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…
Cancel
Save