From 184966dcba86cd2a8110e172f4c2605523f56fab Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sat, 28 Feb 2026 10:15:42 +0100 Subject: [PATCH 1/5] fix(test): Drone CI now runs the instrumented tests again. The Drone CI job is running ./gradlew createGplayDebugCoverageReport This only executes tests with coverage. In 5fd2e2942c2b375b86c52256f7c8921395f5594c, the line testCoverageEnabled = project.hasProperty("coverage") was replaced with enableUnitTestCoverage = project.hasProperty("coverage") which only left the unit tests with coverage Enabling the coverage for androidTest (the instrumented tests) makes Drone run them again. Closes #16350 Signed-off-by: Philipp Hasper --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index af36c934194b..0e9c56ece3e0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -130,6 +130,7 @@ android { debug { enableUnitTestCoverage = project.hasProperty("coverage") + enableAndroidTestCoverage = project.hasProperty("coverage") resConfigs("xxxhdpi") } } From 60a3a701a9f0b16f5a61a79a9aab03ea0788231f Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sat, 28 Feb 2026 11:21:52 +0100 Subject: [PATCH 2/5] fix(test): Upload of instrumented test results now uses correct folder The drone tests simply ended with the log line: stable-IT test failed, but no output was generated. Maybe a preliminary stage failed. The reason was that the script didn't use the correct folder Signed-off-by: Philipp Hasper --- scripts/uploadReport.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/uploadReport.sh b/scripts/uploadReport.sh index 2e1af039cca3..e3bb5eea5459 100755 --- a/scripts/uploadReport.sh +++ b/scripts/uploadReport.sh @@ -51,7 +51,7 @@ if [ -z $USER ] || [ -z $PASS ]; then fi if [ $TYPE = "IT" ]; then - FOLDER=app/build/reports/androidTests/connected/flavors/gplay + FOLDER=app/build/reports/androidTests/connected/debug/flavors/gplay elif [ $TYPE = "Unit" ]; then FOLDER=app/build/reports/tests/testGplayDebugUnitTest else @@ -68,10 +68,10 @@ else -X POST https://api.github.com/repos/nextcloud/android/issues/$PR/comments \ -d "{ \"body\" : \"$BRANCH_TYPE test failed, but no output was generated. Maybe a preliminary stage failed. \" }" - if [ -e app/build/reports/androidTests/connected/flavors/gplay ] ; then + if [ -e app/build/reports/androidTests/connected/debug/flavors/gplay ] ; then TYPE="IT" BRANCH_TYPE=$BRANCH-$TYPE - upload "app/build/reports/androidTests/connected/flavors/gplay" + upload "app/build/reports/androidTests/connected/debug/flavors/gplay" fi if [ -e app/build/reports/tests/testGplayDebugUnitTest ] ; then From 670f567d805f1dd7044ae181440d659ece31807f Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sat, 28 Feb 2026 18:52:11 +0100 Subject: [PATCH 3/5] fix(test): Repaired broken instrumented tests 1. SetOnlineStatusBottomSheetIT: Checked for the wrong view, at the wrong point in time 2. SetStatusMessageBottomSheetIT: Fragment transactions are scheduled asynchronously, so you couldn't set the status inside the same scenario where show() is shown. Now, the setPredefinedStatus() method instead pre-stores the status and only updates the adapter if available Signed-off-by: Philipp Hasper --- .../java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt | 5 ++--- .../java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt | 7 ++++++- .../java/com/nextcloud/ui/SetStatusMessageBottomSheet.kt | 7 +++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt index e0840fd93dbb..c9e07da94cfd 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt @@ -37,15 +37,14 @@ class SetOnlineStatusBottomSheetIT : AbstractIT() { launchActivity().use { scenario -> onView(isRoot()).check(matches(isDisplayed())) - onView(withId(R.id.clearStatusAfterSpinner)) - .check(matches(isDisplayed())) - scenario.onActivity { activity -> val sut = SetOnlineStatusBottomSheet( Status(StatusType.DND, "Working hard…", "🤖", -1) ) sut.show(activity.supportFragmentManager, "") } + onView(withId(R.id.onlineStatus)) + .check(matches(isDisplayed())) } } } diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index dbb424284897..b9e9feec6e2e 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -14,8 +14,10 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.rule.GrantPermissionRule import com.owncloud.android.AbstractIT +import com.owncloud.android.R import com.owncloud.android.lib.resources.users.ClearAt import com.owncloud.android.lib.resources.users.PredefinedStatus import com.owncloud.android.lib.resources.users.Status @@ -41,7 +43,6 @@ class SetStatusMessageBottomSheetIT : AbstractIT() { user, Status(StatusType.DND, "Working hard…", "🤖", -1) ) - sut.show(activity.supportFragmentManager, "") val predefinedStatus: ArrayList = arrayListOf( PredefinedStatus("meeting", "📅", "In a meeting", ClearAt("period", "3600")), PredefinedStatus("commuting", "🚌", "Commuting", ClearAt("period", "1800")), @@ -51,7 +52,11 @@ class SetStatusMessageBottomSheetIT : AbstractIT() { PredefinedStatus("vacationing", "🌴", "Vacationing", null) ) sut.setPredefinedStatus(predefinedStatus) + sut.show(activity.supportFragmentManager, "") } + + onView(withId(R.id.predefinedStatusList)) + .check(matches(isDisplayed())) } } } diff --git a/app/src/main/java/com/nextcloud/ui/SetStatusMessageBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/SetStatusMessageBottomSheet.kt index 8712727746c4..86efd789df05 100644 --- a/app/src/main/java/com/nextcloud/ui/SetStatusMessageBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/SetStatusMessageBottomSheet.kt @@ -354,7 +354,10 @@ class SetStatusMessageBottomSheet(val user: User, val currentStatus: Status?) : @SuppressLint("NotifyDataSetChanged") @VisibleForTesting fun setPredefinedStatus(predefinedStatus: ArrayList) { - adapter.list = predefinedStatus - binding.predefinedStatusList.adapter?.notifyDataSetChanged() + this.predefinedStatus = predefinedStatus + if (this::adapter.isInitialized) { + adapter.list = predefinedStatus + binding.predefinedStatusList.adapter?.notifyDataSetChanged() + } } } From e463af5b590bc3fc1c884625d1c03b4c2f0d8256 Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sun, 1 Mar 2026 17:56:25 +0100 Subject: [PATCH 4/5] fix(test): Futile attempt at fixing the UploadDateTests crash The tests themselves run through, but immediately after the android process crashes. Bluntly mocking the whole System class causes problems everywhere. Even limiting the mocking to a single method still causes the crashes. The issue is that mocking System.currentTimeMillis() on Android (ART runtime) is fundamentally unsafe, even with a function reference. The mock interferes with framework internals (WorkManager, schedulers, etc.) that run after the test completes, causing the process crash. Signed-off-by: Philipp Hasper --- .../java/com/nextcloud/utils/UploadDateTests.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/nextcloud/utils/UploadDateTests.kt b/app/src/androidTest/java/com/nextcloud/utils/UploadDateTests.kt index 00177f0f872c..6c8199810567 100644 --- a/app/src/androidTest/java/com/nextcloud/utils/UploadDateTests.kt +++ b/app/src/androidTest/java/com/nextcloud/utils/UploadDateTests.kt @@ -18,6 +18,8 @@ import com.owncloud.android.utils.DisplayUtils import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue @@ -47,10 +49,15 @@ class UploadDateTests { fun setup() { context = InstrumentationRegistry.getInstrumentation().context MockKAnnotations.init(this, relaxed = true) - mockkStatic(System::class) + mockkStatic(System::currentTimeMillis) every { System.currentTimeMillis() } returns JANUARY_27_2026 } + @After + fun tearDown() { + unmockkStatic(System::currentTimeMillis) + } + @Test fun uploadEntityConvertsToOCUploadAndBackCorrectly() { val entity = UploadEntity( From ca5f5d00db68c0560afddb433fa411e5539c0a1a Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sun, 1 Mar 2026 19:19:22 +0100 Subject: [PATCH 5/5] fix(test) AutoRenameTests don't fail on stable server anymore The test-stable failed in the skipAutoRenameWhenWCFDisabled test, while test-master succeeded. This is due to the different server versions. At the time of writing, stable is using version 30 and master is using version 32. The WCF handling differs per version, as detailed in the OCCapabilityExtensions.kt file. Hence the test needs to adjust the server version. While we are on it, we are testing the three different cases Signed-off-by: Philipp Hasper --- .../com/nextcloud/utils/AutoRenameTests.kt | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/utils/AutoRenameTests.kt b/app/src/androidTest/java/com/nextcloud/utils/AutoRenameTests.kt index 68e99b9b40df..440b10ea1a9b 100644 --- a/app/src/androidTest/java/com/nextcloud/utils/AutoRenameTests.kt +++ b/app/src/androidTest/java/com/nextcloud/utils/AutoRenameTests.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Philipp Hasper * SPDX-FileCopyrightText: 2024 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -243,11 +244,32 @@ class AutoRenameTests : AbstractOnServerIT() { @Test fun skipAutoRenameWhenWCFDisabled() { + var capability = capability + val filename = " readme.txt " + + // Nextcloud 32+: Respects the isWCFEnabled flag capability = capability.apply { + versionMayor = NextcloudVersion.nextcloud_32.majorVersionNumber isWCFEnabled = CapabilityBooleanType.FALSE } - val filename = " readme.txt " - val result = AutoRename.rename(filename, capability, isFolderPath = true) + var result = AutoRename.rename(filename, capability, isFolderPath = true) + assert(result == filename) { "Expected $filename but got $result" } + + // Nextcloud 30-31+: Always applies WCF restrictions, even if flag is set to false + capability = capability.apply { + versionMayor = NextcloudVersion.nextcloud_30.majorVersionNumber + isWCFEnabled = CapabilityBooleanType.FALSE + } + result = AutoRename.rename(filename, capability, isFolderPath = true) + val expectedWCFFilename = "readme.txt" + assert(result == expectedWCFFilename) { "Expected $expectedWCFFilename but got $result" } + + // Nextcloud <30: No WCF restrictions, even if flag is set to true + capability = capability.apply { + versionMayor = NextcloudVersion.nextcloud_29.majorVersionNumber + isWCFEnabled = CapabilityBooleanType.TRUE + } + result = AutoRename.rename(filename, capability, isFolderPath = true) assert(result == filename) { "Expected $filename but got $result" } } }