Skip to content

Commit 27481b8

Browse files
ASalaveiMatkovIvan
andauthored
Instrumented tests for iOS (#1786)
Bring and launch instrumented tests from the [compose-multiplatform](https://github.com/JetBrains/compose-multiplatform/tree/master/instrumented-test). Some tests where ignored to be fixed later. Tests structure: - `:compose:ui:ui-xctest` project contains logic of converting Kotlin tests to XCTests, also implement touches simulation (CMPTestUtils). - `compose/ui/ui/src/uikitInstrumentedTest` contains Kotlin tests that are wrapped into the native iOS framework with XCTests API. - `compose/ui/ui/src/uikitInstrumentedTest/launcher` contains xcode project that launches XCTests inside UIApplication environment on iOS Simulator (tests are running with xcodebuild command). CI change: https://jetbrains.team/p/ui/reviews/23/files Fixes: https://youtrack.jetbrains.com/issue/CMP-7394/Move-iOS-Instrumented-tests-into-compose-multiplatform-core-after-switching-to-Kotlin-2.1.0 ## Release Notes N/A --------- Co-authored-by: Ivan Matkov <[email protected]>
1 parent 5810da4 commit 27481b8

File tree

38 files changed

+3067
-263
lines changed

38 files changed

+3067
-263
lines changed

Diff for: buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeMultiplatformExtensionImpl.kt

+36-63
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,16 @@
1616

1717
package androidx.build
1818

19-
import java.io.ByteArrayOutputStream
2019
import javax.inject.Inject
2120
import org.gradle.api.Project
22-
import org.gradle.api.attributes.Attribute
2321
import org.gradle.api.tasks.Copy
24-
import org.gradle.api.tasks.Exec
25-
import org.gradle.kotlin.dsl.creating
2622
import org.gradle.kotlin.dsl.dependencies
27-
import org.gradle.kotlin.dsl.getValue
23+
import org.gradle.kotlin.dsl.getByName
2824
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
2925
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
3026
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
3127
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
3228
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTests
33-
import org.jetbrains.kotlin.gradle.targets.native.DefaultSimulatorTestRun
3429
import org.jetbrains.kotlin.konan.target.KonanTarget
3530
import org.tomlj.Toml
3631

@@ -283,7 +278,17 @@ open class AndroidXComposeMultiplatformExtensionImpl @Inject constructor(
283278
addAll(darwinFlags)
284279
if (isIOS) addAll(iosFlags)
285280
}
286-
it.freeCompilerArgs = it.freeCompilerArgs + flags
281+
282+
// TODO: Remove when the issue is fixed in KGP
283+
// https://youtrack.jetbrains.com/issue/KT-74564
284+
// it.freeCompilerArgs += flags
285+
//
286+
// Fixes problem when instrumented tests compilation is not properly applied to
287+
// the framework configuration.
288+
it.linkTaskProvider.configure {
289+
@Suppress("DEPRECATION")
290+
it.kotlinOptions.freeCompilerArgs += flags
291+
}
287292
}
288293
}
289294
multiplatformExtension.run {
@@ -295,68 +300,36 @@ open class AndroidXComposeMultiplatformExtensionImpl @Inject constructor(
295300
}
296301
}
297302

298-
// https://youtrack.jetbrains.com/issue/KT-55751/MPP-Gradle-Consumable-configurations-must-have-unique-attributes
299-
private val instrumentedTestAttribute = Attribute.of("instrumentedTest", String::class.java)
300-
private val instrumentedTestCompilationAttribute = Attribute.of("instrumentedTestCompilation", String::class.java)
301-
override fun iosInstrumentedTest(): Unit =
303+
override fun iosInstrumentedTest() {
302304
multiplatformExtension.run {
303-
fun getDeviceName(): String? {
304-
return project.findProperty("iosSimulatorName") as? String
305-
}
306-
307-
val bootTask = project.tasks.register("bootIosSimulator", Exec::class.java) { task ->
308-
task.isIgnoreExitValue = true
309-
task.errorOutput = ByteArrayOutputStream()
310-
task.doFirst {
311-
val simulatorName = getDeviceName()
312-
?: error("Device is not provided. Use Use the -PiosSimulatorName=<Device name> flag to pass the device.")
313-
task.commandLine("xcrun", "simctl", "boot", simulatorName)
314-
}
315-
task.doLast {
316-
val result = task.executionResult.get()
317-
if (result.exitValue != 148 && result.exitValue != 149) { // ignoring device already booted errors
318-
result.assertNormalExitValue()
319-
}
320-
}
321-
}
305+
val uikitInstrumentedTest = sourceSets.create("uikitInstrumentedTest")
322306

323307
fun KotlinNativeTargetWithSimulatorTests.configureTestRun() {
324-
attributes.attribute(instrumentedTestAttribute, "test")
325-
testRuns.forEach {
326-
(it as DefaultSimulatorTestRun).executionTask.configure { task ->
327-
task.dependsOn(bootTask)
328-
task.standalone.set(false)
329-
task.device.set(getDeviceName())
308+
val testCompilation = compilations.create("instrumentedTest") {
309+
compilerOptions {
310+
// Generate K/N test runner for kotlin.test @Test support
311+
freeCompilerArgs.add("-tr")
330312
}
313+
314+
it.associateWith(compilations.getByName("test"))
315+
it.defaultSourceSet.dependsOn(uikitInstrumentedTest)
331316
}
332-
compilations.forEach {
333-
it.attributes.attribute(instrumentedTestCompilationAttribute, "test")
317+
binaries.framework("InstrumentedTest", setOf(DEBUG)) {
318+
compilation = testCompilation
319+
baseName = "InstrumentedTest"
320+
isStatic = true
334321
}
335322
}
336-
337-
iosX64("uikitInstrumentedX64") {
338-
configureTestRun()
339-
}
340-
// Testing on real iOS devices is not supported.
341-
// iosArm64("uikitInstrumentedArm64") { ... }
342-
iosSimulatorArm64("uikitInstrumentedSimArm64") {
343-
configureTestRun()
344-
}
345-
346-
val uikitMain = sourceSets.getByName("uikitMain")
347-
val uikitInstrumentedMain = sourceSets.create("uikitInstrumentedMain")
348-
val uikitInstrumentedX64Main = sourceSets.getByName("uikitInstrumentedX64Main")
349-
val uikitInstrumentedSimArm64Main = sourceSets.getByName("uikitInstrumentedSimArm64Main")
350-
uikitInstrumentedMain.dependsOn(uikitMain)
351-
uikitInstrumentedX64Main.dependsOn(uikitInstrumentedMain)
352-
uikitInstrumentedSimArm64Main.dependsOn(uikitInstrumentedMain)
353-
354-
val commonTest = sourceSets.getByName("commonTest")
355-
val uikitInstrumentedTest = sourceSets.create("uikitInstrumentedTest")
356-
val uikitInstrumentedX64Test = sourceSets.getByName("uikitInstrumentedX64Test")
357-
val uikitInstrumentedSimArm64Test = sourceSets.getByName("uikitInstrumentedSimArm64Test")
358-
uikitInstrumentedTest.dependsOn(commonTest)
359-
uikitInstrumentedX64Test.dependsOn(uikitInstrumentedTest)
360-
uikitInstrumentedSimArm64Test.dependsOn(uikitInstrumentedTest)
323+
testableTargets.getByName(
324+
"uikitX64",
325+
KotlinNativeTargetWithSimulatorTests::class,
326+
KotlinNativeTargetWithSimulatorTests::configureTestRun
327+
)
328+
testableTargets.getByName(
329+
"uikitSimArm64",
330+
KotlinNativeTargetWithSimulatorTests::class,
331+
KotlinNativeTargetWithSimulatorTests::configureTestRun
332+
)
361333
}
334+
}
362335
}

Diff for: buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt

+11-1
Original file line numberDiff line numberDiff line change
@@ -842,7 +842,17 @@ class AndroidXImplPlugin @Inject constructor(val componentFactory: SoftwareCompo
842842
kotlinTarget.binaries.all {
843843
// Use std allocator to avoid the following warning:
844844
// w: Mimalloc allocator isn't supported on target <target>. Used standard mode.
845-
it.freeCompilerArgs += "-Xallocator=std"
845+
846+
// TODO: Remove when the issue is fixed in KGP
847+
// https://youtrack.jetbrains.com/issue/KT-74564
848+
// it.freeCompilerArgs += "-Xallocator=std"
849+
//
850+
// Fixes problem when instrumented tests compilation is not properly applied to
851+
// the framework configuration.
852+
it.linkTaskProvider.configure {
853+
@Suppress("DEPRECATION")
854+
it.kotlinOptions.freeCompilerArgs += "-Xallocator=std"
855+
}
846856
}
847857
}
848858
}

Diff for: compose/ui/ui-uikit/src/nativeInterop/cinterop/utils.def

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
#
2+
# Copyright 2023 The Android Open Source Project
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
117
linkerOpts = -framework UIKit
218
package = androidx.compose.ui.uikit.utils
319
language = Objective-C

Diff for: compose/ui/ui/build.gradle

+4-4
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
144144
darwin()
145145
js()
146146
wasm()
147-
// TODO: Migrate iosInstrumentedTest to kotlin 2.1.0
148-
// https://youtrack.jetbrains.com/issue/CMP-7390/Migrate-iosInstrumentedTest-target-to-kotlin-2.1.0
149-
// Kotlin 2.1.0 doesn't support declaring multiple targets of the same type
150-
// iosInstrumentedTest()
147+
iosInstrumentedTest()
151148

152149
configureDarwinFlags()
153150
}
@@ -326,7 +323,10 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
326323
}
327324
uikitInstrumentedTest.dependencies {
328325
implementation(project(":compose:material:material"))
326+
implementation(project(":compose:material3:material3"))
329327
implementation(project(":compose:foundation:foundation"))
328+
implementation(project(":compose:ui:ui-test-junit4"))
329+
implementation(project(":internal-testutils-xctest"))
330330
}
331331

332332
desktopTest.dependencies {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.ui
18+
19+
import androidx.compose.test.interaction.BasicInteractionTest
20+
import androidx.compose.xctest.setupXCTestSuite
21+
import kotlinx.cinterop.ExperimentalForeignApi
22+
import platform.XCTest.XCTestSuite
23+
24+
@Suppress("unused")
25+
@OptIn(ExperimentalForeignApi::class)
26+
fun testSuite(): XCTestSuite = setupXCTestSuite(
27+
// Run all test cases from the tests
28+
// BasicInteractionTest::class,
29+
// LayersAccessibilityTest::class,
30+
31+
// Run test cases from a test
32+
BasicInteractionTest::testTextFieldCallout,
33+
// LayersAccessibilityTest::testLayersAppearanceOrder
34+
)

0 commit comments

Comments
 (0)