mirror of
https://github.com/jlengrand/compose-multiplatform.git
synced 2026-03-10 08:11:20 +00:00
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
This commit is contained in:
35
.github/workflows/gradle-plugin.yml
vendored
Normal file
35
.github/workflows/gradle-plugin.yml
vendored
Normal file
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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") {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,3 +31,18 @@
|
||||
|
||||
# https://github.com/Kotlin/kotlinx.coroutines/issues/2046
|
||||
-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*
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.compose.test.utils.ComposeTestSummary
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
-keep public class Main {
|
||||
public void keptByKeepRule(...);
|
||||
}
|
||||
-dontnote
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user