field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

MediaOwnershipTests.kt (4638B)


      1 package tera.smoke
      2 
      3 import com.sun.jna.Library
      4 import com.sun.jna.Native
      5 import java.nio.file.Files
      6 import java.nio.file.Path
      7 import kotlin.test.Test
      8 import kotlin.test.assertEquals
      9 import kotlin.test.assertFailsWith
     10 import kotlin.test.assertNotNull
     11 import kotlin.test.assertTrue
     12 import kotlinx.coroutines.runBlocking
     13 import uniffi.tera_core.FfiConverterTypeFfiMediaFile
     14 import uniffi.tera_core.FfiMediaFile
     15 import uniffi.tera_core.TeraAppException
     16 
     17 internal interface SmokeLibC : Library {
     18     fun open(path: String, flags: Int): Int
     19     fun close(descriptor: Int): Int
     20     fun dup2(source: Int, target: Int): Int
     21     fun read(descriptor: Int, bytes: ByteArray, size: Long): Long
     22 }
     23 
     24 internal val LIBC = Native.load("c", SmokeLibC::class.java)
     25 
     26 internal class SmokeFile(path: Path) : AutoCloseable {
     27     val descriptor: Int = LIBC.open(path.toString(), 0).also { check(it >= 0) }
     28     private var open = true
     29 
     30     override fun close() {
     31         if (open) {
     32             open = false
     33             check(LIBC.close(descriptor) == 0)
     34         }
     35     }
     36 }
     37 
     38 class MediaOwnershipTests {
     39     @Test
     40     fun callerCloseBeforeGeneratedConversionPreservesTheAdmittedBytes(): Unit = runBlocking {
     41         SmokeFixture().use { fixture ->
     42             val runtime = fixture.runtime()
     43             try {
     44                 SmokeFile(fixture.original).use { original ->
     45                     FfiMediaFile(original.descriptor.toULong(), fixture.bytes.size.toULong()).use { file ->
     46                         original.close()
     47                         val saved = runtime.phase1SaveDraft(
     48                             "31".repeat(16), fixture.draft(listOf(fixture.media(file))), UNIX_S, null, UNIX_MS,
     49                         )
     50                         assertEquals(fixture.sha256, assertNotNull(saved.form).media.single().sha256)
     51                     }
     52                 }
     53             } finally {
     54                 runtime.shutdown()
     55                 runtime.close()
     56             }
     57         }
     58     }
     59 
     60     @Test
     61     fun recycledCallerSlotAndDisposedSourceWrapperCannotSubstituteBytes(): Unit = runBlocking {
     62         SmokeFixture().use { fixture ->
     63             val runtime = fixture.runtime()
     64             val replacementPath = fixture.root.resolve("replacement.png")
     65             Files.write(replacementPath, ByteArray(fixture.bytes.size))
     66             try {
     67                 SmokeFile(fixture.original).use { original ->
     68                     SmokeFile(replacementPath).use { replacement ->
     69                         val source = FfiMediaFile(original.descriptor.toULong(), fixture.bytes.size.toULong())
     70                         // Clone while the source wrapper is live. Raw generated pointer
     71                         // converters must never be called on an already disposed wrapper.
     72                         val retained = FfiConverterTypeFfiMediaFile.lift(FfiConverterTypeFfiMediaFile.lower(source))
     73                         source.close()
     74                         source.close()
     75                         retained.use { file ->
     76                             assertEquals(original.descriptor, LIBC.dup2(replacement.descriptor, original.descriptor))
     77                             val substituted = ByteArray(fixture.bytes.size)
     78                             assertEquals(substituted.size.toLong(), LIBC.read(original.descriptor, substituted, substituted.size.toLong()))
     79                             assertTrue(substituted.all { it == 0.toByte() })
     80                             val saved = runtime.phase1SaveDraft(
     81                                 "32".repeat(16), fixture.draft(listOf(fixture.media(file))), UNIX_S, null, UNIX_MS,
     82                             )
     83                             assertEquals(fixture.sha256, assertNotNull(saved.form).media.single().sha256)
     84                         }
     85                     }
     86                 }
     87             } finally {
     88                 runtime.shutdown()
     89                 runtime.close()
     90             }
     91         }
     92     }
     93 
     94     @Test
     95     fun admissionRejectsInvalidDescriptorsAndWrongTypesSynchronously() {
     96         assertFailsWith<TeraAppException.Failure> { FfiMediaFile(ULong.MAX_VALUE, 24uL) }
     97         SmokeFixture().use { fixture ->
     98             SmokeFile(fixture.root).use { directory ->
     99                 assertFailsWith<TeraAppException.Failure> { FfiMediaFile(directory.descriptor.toULong(), 24uL) }
    100             }
    101             SmokeFile(fixture.original).use { original ->
    102                 val failure = assertFailsWith<TeraAppException.Failure> {
    103                     FfiMediaFile(original.descriptor.toULong(), fixture.bytes.size.toULong() + 1uL)
    104                 }
    105                 assertEquals("media_size_mismatch", failure.report.code)
    106             }
    107         }
    108     }
    109 }