Merge pull request #3159 from vitorpamplona/claude/brave-lovelace-d1rap2

fix(commons): make commonTest compile for iOS targets
This commit is contained in:
Vitor Pamplona
2026-06-09 16:24:54 -04:00
committed by GitHub
14 changed files with 108 additions and 110 deletions
+14 -8
View File
@@ -227,16 +227,22 @@ jobs:
:quartz:compileTestKotlinIosArm64
# :commons gained iosArm64 + iosSimulatorArm64 targets in Phase 2 of the
# iOS plan. Compile-only for now — actual UI / lifecycle wiring will
# land with the iosApp module in Phase 3. The container that runs Claude
# Code can't extract the Kotlin/Native LLVM toolchain (sandbox limit),
# so this is the first place commonMain Compose code is actually
# type-checked against an Apple Native frontend.
- name: Compile Commons for iOS
# iOS plan. Actual UI / lifecycle wiring will land with the iosApp module
# in Phase 3, but the shared commonMain + commonTest sources are already
# built here against an Apple Native frontend. Same two-task shape as
# quartz above:
# - iosSimulatorArm64Test compiles AND runs the shared commonTest suite
# on the simulator. commonTest is built for every target, so a test
# reaching for a JVM-only API (JUnit, javaClass, @JvmStatic, or a
# jvmAndroid-only symbol) breaks the Apple build even though
# :commons:jvmTest stays green — this is the job that catches it.
# - compileTestKotlinIosArm64 catches device-only compile drift
# (iosArm64 = aarch64-apple-ios) without needing a physical device.
- name: Test Commons on iOS
run: |
./gradlew \
:commons:compileKotlinIosSimulatorArm64 \
:commons:compileKotlinIosArm64
:commons:iosSimulatorArm64Test \
:commons:compileTestKotlinIosArm64
- name: Upload iOS Test Reports
uses: actions/upload-artifact@v7
@@ -22,13 +22,11 @@ package android.util
class Log {
companion object {
@JvmStatic
fun isLoggable(
tag: String?,
msg: Int?,
): Boolean = true
@JvmStatic
fun d(
tag: String?,
msg: String?,
@@ -37,7 +35,6 @@ class Log {
return 0
}
@JvmStatic
fun i(
tag: String?,
msg: String?,
@@ -46,7 +43,6 @@ class Log {
return 0
}
@JvmStatic
fun w(
tag: String?,
msg: String?,
@@ -55,7 +51,6 @@ class Log {
return 0
}
@JvmStatic
fun w(
tag: String?,
msg: String?,
@@ -66,7 +61,6 @@ class Log {
return 0
}
@JvmStatic
fun e(
tag: String?,
msg: String?,
@@ -75,7 +69,6 @@ class Log {
return 0
}
@JvmStatic
fun e(
tag: String?,
msg: String?,
@@ -21,22 +21,22 @@
package com.vitorpamplona.amethyst.commons
import com.vitorpamplona.amethyst.commons.blurhash.Base83
import org.junit.Assert.assertEquals
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
class Base83Test {
@Test
fun testEncodeDecode() {
for (i in 0..820000) {
assertEquals("$i encode decode", i, Base83.decode(Base83.encode(i.toLong())))
assertEquals(i, Base83.decode(Base83.encode(i.toLong())), "$i encode decode")
}
}
@Test
fun testSingleDigits() {
for (i in 0..82) {
val expected: String = String(Base83.ALPHABET, i, 1)
assertEquals("$i encodes", expected, Base83.encode(i.toLong(), 1))
val expected: String = Base83.ALPHABET.concatToString(i, i + 1)
assertEquals(expected, Base83.encode(i.toLong(), 1), "$i encodes")
}
}
@@ -21,22 +21,22 @@
package com.vitorpamplona.amethyst.commons
import com.vitorpamplona.amethyst.commons.blurhash.SRGB
import org.junit.Assert.assertEquals
import org.junit.Test
import kotlin.math.round
import kotlin.test.Test
import kotlin.test.assertEquals
class SRGBTest {
@Test
fun testEncodeDecode() {
for (i in 0..255) {
assertEquals("$i encode decode", i, SRGB.linearToSrgb(SRGB.srgbToLinear(i)))
assertEquals(i, SRGB.linearToSrgb(SRGB.srgbToLinear(i)), "$i encode decode")
}
for (i in 0..100) {
val srgb = SRGB.linearToSrgb(i / 100.0f)
val linear = round(SRGB.srgbToLinear(srgb) * 100).toInt()
assertEquals("$i decode encode", i, linear)
assertEquals(i, linear, "$i decode encode")
}
}
}
@@ -22,12 +22,12 @@ package com.vitorpamplona.amethyst.commons
import com.vitorpamplona.amethyst.commons.thumbhash.ThumbHashDecoder
import com.vitorpamplona.amethyst.commons.thumbhash.ThumbHashEncoder
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.math.abs
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
class ThumbHashTest {
@Test
@@ -37,20 +37,20 @@ class ThumbHashTest {
val pixels = IntArray(w * h) { 0xFFFF8040.toInt() } // opaque warm orange
val hashBytes = ThumbHashEncoder.encode(pixels, w, h)
assertTrue("hash should have at least header bytes", hashBytes.size >= 5)
assertTrue(hashBytes.size >= 5, "hash should have at least header bytes")
val decoded = ThumbHashDecoder.decode(hashBytes)
assertNotNull(decoded)
decoded!!
assertTrue("decoded width should be positive", decoded.width > 0)
assertTrue("decoded height should be positive", decoded.height > 0)
assertTrue(decoded.width > 0, "decoded width should be positive")
assertTrue(decoded.height > 0, "decoded height should be positive")
val originalRatio = w.toFloat() / h.toFloat()
val decodedRatio = decoded.width.toFloat() / decoded.height.toFloat()
// ThumbHash loses some precision, but landscape vs portrait should be preserved.
assertTrue(
"decoded ratio ($decodedRatio) should be on the same side of 1 as original ($originalRatio)",
(originalRatio > 1f) == (decodedRatio > 1f) || originalRatio == decodedRatio,
"decoded ratio ($decodedRatio) should be on the same side of 1 as original ($originalRatio)",
)
}
@@ -69,8 +69,8 @@ class ThumbHashTest {
}
val encoded = ThumbHashEncoder.encodeToBase64(pixels, w, h)
assertTrue("base64 string should be non-empty", encoded.isNotEmpty())
assertTrue("base64 string should not contain padding", !encoded.contains('='))
assertTrue(encoded.isNotEmpty(), "base64 string should be non-empty")
assertTrue(!encoded.contains('='), "base64 string should not contain padding")
val viaBase64 = ThumbHashDecoder.decode(encoded)
assertNotNull(viaBase64)
@@ -80,8 +80,8 @@ class ThumbHashTest {
assertNotNull(viaBytes)
viaBytes!!
assertEquals("base64 path and raw path should agree on width", viaBytes.width, viaBase64.width)
assertEquals("base64 path and raw path should agree on height", viaBytes.height, viaBase64.height)
assertEquals(viaBytes.width, viaBase64.width, "base64 path and raw path should agree on width")
assertEquals(viaBytes.height, viaBase64.height, "base64 path and raw path should agree on height")
}
@Test
@@ -101,7 +101,7 @@ class ThumbHashTest {
decoded!!
for (p in decoded.pixels) {
val a = (p ushr 24) and 0xff
assertEquals("alpha should be 255 for opaque encode", 255, a)
assertEquals(255, a, "alpha should be 255 for opaque encode")
}
}
@@ -120,7 +120,7 @@ class ThumbHashTest {
val a = (p ushr 24) and 0xff
if (a > maxAlpha) maxAlpha = a
}
assertTrue("max alpha of all-transparent decode should be low; got $maxAlpha", maxAlpha <= 16)
assertTrue(maxAlpha <= 16, "max alpha of all-transparent decode should be low; got $maxAlpha")
}
@Test
@@ -150,9 +150,9 @@ class ThumbHashTest {
val avgB = sumB / count
// ThumbHash quantisation allows a handful of codepoints of drift.
assertTrue("avg R drift: expected ${target[0]}, got $avgR", abs(avgR - target[0]) < 8)
assertTrue("avg G drift: expected ${target[1]}, got $avgG", abs(avgG - target[1]) < 8)
assertTrue("avg B drift: expected ${target[2]}, got $avgB", abs(avgB - target[2]) < 8)
assertTrue(abs(avgR - target[0]) < 8, "avg R drift: expected ${target[0]}, got $avgR")
assertTrue(abs(avgG - target[1]) < 8, "avg G drift: expected ${target[1]}, got $avgG")
assertTrue(abs(avgB - target[2]) < 8, "avg B drift: expected ${target[2]}, got $avgB")
}
@Test
@@ -163,7 +163,7 @@ class ThumbHashTest {
val hash = ThumbHashEncoder.encode(pixels, w, h)
val ratio = ThumbHashDecoder.aspectRatio(hash)
assertNotNull(ratio)
assertTrue("landscape ratio should be > 1, got $ratio", ratio!! > 1f)
assertTrue(ratio!! > 1f, "landscape ratio should be > 1, got $ratio")
}
@Test
@@ -174,11 +174,11 @@ class ThumbHashTest {
val hash = ThumbHashEncoder.encode(pixels, w, h)
val ratio = ThumbHashDecoder.aspectRatio(hash)
assertNotNull(ratio)
assertTrue("portrait ratio should be < 1, got $ratio", ratio!! < 1f)
assertTrue(ratio!! < 1f, "portrait ratio should be < 1, got $ratio")
}
@Test
fun `repeated decodes produce identical output (cosine cache determinism)`() {
fun `repeated decodes produce identical output - cosine cache determinism`() {
val w = 40
val h = 30
val pixels =
@@ -222,8 +222,8 @@ class ThumbHashTest {
// Chop off half the AC payload.
val truncated = fullHash.copyOfRange(0, 5 + (fullHash.size - 5) / 4)
assertNull(
"hash with insufficient AC bytes should be rejected",
ThumbHashDecoder.decode(truncated),
"hash with insufficient AC bytes should be rejected",
)
}
@@ -239,13 +239,13 @@ class ThumbHashTest {
assertNotNull(decoded)
decoded!!
assertTrue(
"expected output to fit in 32x32, got ${decoded.width}x${decoded.height}",
decoded.width in 1..32 && decoded.height in 1..32,
"expected output to fit in 32x32, got ${decoded.width}x${decoded.height}",
)
assertEquals(
"pixel buffer size must match dimensions",
decoded.width * decoded.height,
decoded.pixels.size,
"pixel buffer size must match dimensions",
)
}
}
@@ -20,9 +20,9 @@
*/
package com.vitorpamplona.amethyst.commons.emojicoder
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class EmojiCoderTest {
companion object {
@@ -68,7 +68,7 @@ class EmojiCoderTest {
val encoded = EmojiCoder.encode(emoji, sentence)
val decoded = EmojiCoder.decode(encoded)
assertEquals(sentence, decoded)
assertTrue("Failed sentence for emoji $emoji with sentence `$sentence`: `$encoded`", EmojiCoder.isCoded(encoded))
assertTrue(EmojiCoder.isCoded(encoded), "Failed sentence for emoji $emoji with sentence `$sentence`: `$encoded`")
}
}
}
@@ -21,8 +21,8 @@
package com.vitorpamplona.amethyst.commons.model
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import org.junit.Assert.assertEquals
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
/**
* Lock in the descending-by-usage ordering and equal-count preservation of
@@ -23,12 +23,12 @@ package com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.namecoin
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumxServer
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinBackend
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinCoreRpcConfig
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
class NamecoinSettingsTest {
// ── Server string parsing ──────────────────────────────────────────
@@ -21,10 +21,10 @@
package com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class OwnedEmojiPackTest {
private val publicEmoji = EmojiUrlTag("public_one", "https://example.com/public.png")
@@ -21,9 +21,9 @@
package com.vitorpamplona.amethyst.commons.richtext
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class RichTextParserMultibyteTest {
@Test
@@ -42,20 +42,20 @@ class RichTextParserMultibyteTest {
// user@example.com should be EmailSegment
assertTrue(
"user@example.com should be EmailSegment",
allSegments.any { it is EmailSegment && it.segmentText == "user@example.com" },
"user@example.com should be EmailSegment",
)
// user@example.com should NOT be a LinkSegment
assertTrue(
"user@example.com should not be a LinkSegment",
allSegments.none { it is LinkSegment && it.segmentText == "user@example.com" },
"user@example.com should not be a LinkSegment",
)
// user@example.com should not be in urlSet
assertTrue(
"user@example.com should not be in urlSet",
!state.urlSet.withScheme.contains("user@example.com") && !state.urlSet.withoutScheme.contains("user@example.com"),
"user@example.com should not be in urlSet",
)
}
@@ -145,8 +145,8 @@ class RichTextParserMultibyteTest {
val state = RichTextParser().parseText(text, EmptyTagList, null)
val allSegments = state.paragraphs.flatMap { it.words }
assertTrue(
"user@example.com should be EmailSegment",
allSegments.any { it is EmailSegment && it.segmentText == "user@example.com" },
"user@example.com should be EmailSegment",
)
}
@@ -158,11 +158,11 @@ class RichTextParserMultibyteTest {
val allSegments = state.paragraphs.flatMap { it.words }
val urlSegments = allSegments.filterIsInstance<SchemelessUrlSegment>()
assertTrue("Should have SchemelessUrlSegment", urlSegments.isNotEmpty())
assertTrue("URL should be example.com", urlSegments.any { it.segmentText == "example.com" })
assertTrue(urlSegments.isNotEmpty(), "Should have SchemelessUrlSegment")
assertTrue(urlSegments.any { it.segmentText == "example.com" }, "URL should be example.com")
val textSegments = allSegments.filterIsInstance<RegularTextSegment>()
assertTrue("Should have prefix ああ", textSegments.any { it.segmentText == "ああ" })
assertTrue(textSegments.any { it.segmentText == "ああ" }, "Should have prefix ああ")
}
@Test
@@ -173,11 +173,11 @@ class RichTextParserMultibyteTest {
val allSegments = state.paragraphs.flatMap { it.words }
val urlSegments = allSegments.filterIsInstance<SchemelessUrlSegment>()
assertTrue("Should have SchemelessUrlSegment", urlSegments.isNotEmpty())
assertTrue("URL should be example.com", urlSegments.any { it.segmentText == "example.com" })
assertTrue(urlSegments.isNotEmpty(), "Should have SchemelessUrlSegment")
assertTrue(urlSegments.any { it.segmentText == "example.com" }, "URL should be example.com")
val textSegments = allSegments.filterIsInstance<RegularTextSegment>()
assertTrue("Should have suffix ああ", textSegments.any { it.segmentText == "ああ" })
assertTrue(textSegments.any { it.segmentText == "ああ" }, "Should have suffix ああ")
}
@Test
@@ -188,12 +188,12 @@ class RichTextParserMultibyteTest {
val allSegments = state.paragraphs.flatMap { it.words }
val emailSegment = allSegments.filterIsInstance<EmailSegment>()
assertTrue("Should have EmailSegment", emailSegment.isNotEmpty())
assertTrue("Email should be user@example.com", emailSegment.any { it.segmentText == "user@example.com" })
assertTrue(emailSegment.isNotEmpty(), "Should have EmailSegment")
assertTrue(emailSegment.any { it.segmentText == "user@example.com" }, "Email should be user@example.com")
val textSegments = allSegments.filterIsInstance<RegularTextSegment>()
assertTrue("Should have prefix ほむほむ", textSegments.any { it.segmentText == "ほむほむ" })
assertTrue("Should have suffix ほげほげ", textSegments.any { it.segmentText == "ほげほげ" })
assertTrue(textSegments.any { it.segmentText == "ほむほむ" }, "Should have prefix ほむほむ")
assertTrue(textSegments.any { it.segmentText == "ほげほげ" }, "Should have suffix ほげほげ")
}
@Test
@@ -204,10 +204,10 @@ class RichTextParserMultibyteTest {
val allSegments = state.paragraphs.flatMap { it.words }
val emailSegment = allSegments.filterIsInstance<EmailSegment>()
assertTrue("Should have EmailSegment", emailSegment.isNotEmpty())
assertTrue("Email should be user@example.com", emailSegment.any { it.segmentText == "user@example.com" })
assertTrue(emailSegment.isNotEmpty(), "Should have EmailSegment")
assertTrue(emailSegment.any { it.segmentText == "user@example.com" }, "Email should be user@example.com")
val textSegments = allSegments.filterIsInstance<RegularTextSegment>()
assertTrue("Should have suffix ふがふが", textSegments.any { it.segmentText == "ふがふが" })
assertTrue(textSegments.any { it.segmentText == "ふがふが" }, "Should have suffix ふがふが")
}
}
File diff suppressed because one or more lines are too long
@@ -30,10 +30,10 @@ import com.vitorpamplona.quartz.nip01Core.core.Address
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
class ReplyContextTest {
private val parentEventId = "b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c"
@@ -20,9 +20,9 @@
*/
package com.vitorpamplona.amethyst.commons.util
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
class CodePointsTest {
// ---- codePointCharCount ----
@@ -44,30 +44,30 @@ class CodePointsTest {
@Test
fun toCharsRoundTripsAscii() {
assertArrayEquals(charArrayOf('A'), codePointToChars(0x0041))
assertContentEquals(charArrayOf('A'), codePointToChars(0x0041))
}
@Test
fun toCharsRoundTripsLastBmp() {
assertArrayEquals(charArrayOf('￿'), codePointToChars(0xFFFF))
assertContentEquals(charArrayOf('￿'), codePointToChars(0xFFFF))
}
@Test
fun toCharsProducesSurrogatePairForGrinningFace() {
// U+1F600 (😀) is encoded as the surrogate pair (0xD83D, 0xDE00).
assertArrayEquals(charArrayOf('\uD83D', '\uDE00'), codePointToChars(0x1F600))
assertContentEquals(charArrayOf('\uD83D', '\uDE00'), codePointToChars(0x1F600))
}
@Test
fun toCharsProducesSurrogatePairForFirstSupplementary() {
// U+10000 -> (0xD800, 0xDC00).
assertArrayEquals(charArrayOf('\uD800', '\uDC00'), codePointToChars(0x10000))
assertContentEquals(charArrayOf('\uD800', '\uDC00'), codePointToChars(0x10000))
}
@Test
fun toCharsProducesSurrogatePairForLastCodePoint() {
// U+10FFFF -> (0xDBFF, 0xDFFF).
assertArrayEquals(charArrayOf('\uDBFF', '\uDFFF'), codePointToChars(0x10FFFF))
assertContentEquals(charArrayOf('\uDBFF', '\uDFFF'), codePointToChars(0x10FFFF))
}
// ---- String.codePointAtKmp ----
@@ -141,11 +141,11 @@ class CodePointsTest {
for (cp in samples) {
val chars = codePointToChars(cp)
val asString = chars.concatToString()
assertEquals("round-trip code point U+${cp.toString(16).uppercase()}", cp, asString.codePointAtKmp(0))
assertEquals(cp, asString.codePointAtKmp(0), "round-trip code point U+${cp.toString(16).uppercase()}")
assertEquals(
"char count for U+${cp.toString(16).uppercase()}",
chars.size,
codePointCharCount(cp),
"char count for U+${cp.toString(16).uppercase()}",
)
}
}