summaryrefslogtreecommitdiff
path: root/libs/documents/src/androidTest
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2024-09-10 23:46:21 +0200
committerJoel Klinghed <the_jk@spawned.biz>2024-09-10 23:50:27 +0200
commit994672608db65a68b3ba3db8fa37bb613de89c20 (patch)
tree4873d7177a7949f7e1501e9494e2897e2da35d03 /libs/documents/src/androidTest
parent3e1b734cd804dbdb8bff8bbdc944a0fd141bed75 (diff)
Add libs:documents
Reads the abomination that is SAF, or Androids best effort to make files and directories completely and utterly unusable on Android. The androidTest was (and is) a pain, only known to work on a Pixel3 API 34 emulator but it showed a lot of things that the fake content provider in the unit tests failed to show.
Diffstat (limited to 'libs/documents/src/androidTest')
-rw-r--r--libs/documents/src/androidTest/AndroidManifest.xml6
-rw-r--r--libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/DocumentTreeAndroidTest.kt216
-rw-r--r--libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/test/TestActivity.kt26
3 files changed, 248 insertions, 0 deletions
diff --git a/libs/documents/src/androidTest/AndroidManifest.xml b/libs/documents/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..ddb7408
--- /dev/null
+++ b/libs/documents/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <application>
+ <activity android:name="org.the_jk.cleversync.documents.test.TestActivity" />
+ </application>
+</manifest>
diff --git a/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/DocumentTreeAndroidTest.kt b/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/DocumentTreeAndroidTest.kt
new file mode 100644
index 0000000..d0d6bb3
--- /dev/null
+++ b/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/DocumentTreeAndroidTest.kt
@@ -0,0 +1,216 @@
+package org.the_jk.cleversync.documents
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Environment
+import android.os.Handler
+import android.os.Looper
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.the_jk.cleversync.TreeAbstractTest
+import org.the_jk.cleversync.documents.test.TestActivity
+import java.io.File
+import kotlin.time.Duration.Companion.seconds
+
+@RunWith(AndroidJUnit4::class)
+class DocumentTreeAndroidTest : TreeAbstractTest() {
+ @get:Rule
+ val activityRule = ActivityScenarioRule(TestActivity::class.java)
+
+ private lateinit var testDir: File
+ private lateinit var mainHandler: Handler
+
+ @Before
+ fun setUp() {
+ // If tests fail in a certain way we lose the right to download the
+ // directory which confuses the rest of the tests as they expect
+ // an empty directory. Better, create a new directory if needed.
+ // Build script tries to remove them all after a run.
+ var i = 0
+ while (true) {
+ testDir = File(Environment.getExternalStorageDirectory(), "Download/DocumentTreeTest-$i")
+ if (!testDir.exists()) break
+ testDir.deleteRecursively()
+ if (!testDir.exists()) break
+ i++
+ }
+ testDir.mkdirs()
+ }
+
+ @After
+ fun tearDown() {
+ testDir.deleteRecursively()
+ }
+
+ @Test
+ override fun empty() {
+ runTest { super.empty() }
+ }
+
+ @Test
+ override fun emptyLive() {
+ runTest { super.emptyLive() }
+ }
+
+ @Test
+ override fun createDirectory() {
+ runTest { super.createDirectory() }
+ }
+
+ @Test(timeout = 30000)
+ override fun observeCreateDirectory() {
+ runTest { super.observeCreateDirectory() }
+ }
+
+ @Test
+ override fun createFile() {
+ runTest { super.createFile() }
+ }
+
+ @Test
+ override fun overwriteFile() {
+ runTest { super.overwriteFile() }
+ }
+
+ @Test
+ override fun removeDir() {
+ runTest { super.removeDir() }
+ }
+
+ @Test(timeout = 30000)
+ override fun removeDirLive() {
+ runTest { super.removeDirLive() }
+ }
+
+ @Test
+ override fun sameDir() {
+ runTest { super.sameDir() }
+ }
+
+ @Test
+ override fun sameFile() {
+ runTest { super.sameFile() }
+ }
+
+ @Test
+ override fun removeDirWithContent() {
+ runTest { super.removeDirWithContent() }
+ }
+
+ @Test
+ override fun removeWrongType() {
+ runTest { super.removeWrongType() }
+ }
+
+ @Test
+ override fun removeFile() {
+ runTest { super.removeFile() }
+ }
+
+ @Test
+ override fun names() {
+ runTest { super.names() }
+ }
+
+ @Test
+ override fun openNonExistent() {
+ runTest { super.openNonExistent() }
+ }
+
+ @Test
+ override fun lastModified() {
+ runTest { super.lastModified() }
+ }
+
+ override fun supportSymlinks() = false
+
+ override fun idle() {
+ // Called on mainLooper while waiting for testLooper, should usually
+ // not happen as the events we wait for have often already happened
+ // but make sure to not busy loop.
+ Thread.sleep(1000)
+ }
+
+ override fun onMain(block: () -> Unit) {
+ mainHandler.post(block)
+ }
+
+ // TODO: Figure out how to do this with @Before and @After
+ private fun runTest(body: () -> Unit) {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val contentResolver = context.contentResolver
+ var bodyException: Throwable? = null
+
+ val fullPath = testDir.toString()
+ val index = fullPath.indexOf("/Download/")
+ val path = fullPath.substring(index + 1)
+ val initialUri = Uri.Builder()
+ .scheme("content")
+ .authority("com.android.externalstorage.documents")
+ .encodedPath("document/primary%3A" + path.replace("/", "%2F"))
+ .build()
+
+ Looper.prepare()
+ val testLooper = Looper.myLooper()!!
+
+ activityRule.scenario.onActivity { activity ->
+ mainHandler = Handler(Looper.getMainLooper())
+
+ activity.setReceiver { uri ->
+ contentResolver.takePersistableUriPermission(
+ uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ )
+
+ tree = DocumentTreeFactory.createModifiableTree(
+ contentResolver,
+ uri,
+ 1.seconds,
+ )
+
+ try {
+ body()
+ } catch (e: Throwable) {
+ // Exceptions here are not well handled, re-throw
+ // outside scenario
+ bodyException = e
+ }
+
+ // Quit the test loop when main looper is idle
+ Looper.getMainLooper().queue.addIdleHandler {
+ testLooper.quit()
+ false
+ }
+ }
+ activity.launcher.launch(initialUri)
+
+ val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ device.wait(
+ Until.hasObject(By.pkg("com.google.android.documentsui").depth(0)),
+ 10000,
+ )
+
+ device.findObject(By.text("USE THIS FOLDER").clazz("android.widget.Button")).clickAndWait(
+ Until.newWindow(),
+ 500,
+ )
+
+ device.findObject(By.text("ALLOW").clazz("android.widget.Button")).click()
+ }
+
+ Looper.loop()
+
+ bodyException?.let { throw it }
+ }
+}
diff --git a/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/test/TestActivity.kt b/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/test/TestActivity.kt
new file mode 100644
index 0000000..d6e60cb
--- /dev/null
+++ b/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/test/TestActivity.kt
@@ -0,0 +1,26 @@
+package org.the_jk.cleversync.documents.test
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.FragmentActivity
+
+class TestActivity : FragmentActivity() {
+ lateinit var launcher: ActivityResultLauncher<Uri?>
+ private var receiver: ((Uri) -> Unit)? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ launcher = registerForActivityResult(
+ ActivityResultContracts.OpenDocumentTree(),
+ ) { uri ->
+ if (uri != null) receiver?.invoke(uri)
+ }
+ }
+
+ fun setReceiver(receiver: (Uri) -> Unit) {
+ this.receiver = receiver
+ }
+}