IJPL-207762 allow having several converters for the same target class

GitOrigin-RevId: 551e73f7cdb77aada2d16b5ee02e690e0b5edd59
This commit is contained in:
Max Medvedev
2026-02-06 20:04:49 +00:00
committed by intellij-monorepo-bot
parent 1463232969
commit 6de3013465
4 changed files with 133 additions and 190 deletions
@@ -29,22 +29,28 @@ abstract class ExtensionPointSerializer<Target : Any, Descriptor : Any>(
) : KSerializer<Descriptor> {
fun toDescriptor(target: Target): Descriptor? {
val fqn = target.javaClass.name
val helper = findSerializerHelper(fqn) ?: return null
return helper.converter.toDescriptor(target)
val targetFqn = target.javaClass.name
return findAllImpl { bean -> bean.implementationClass == targetFqn }
.firstNotNullOfOrNull { helper -> helper.converter.toDescriptor(target) }
}
private fun findNotNullSerializerHelper(fqn: String): SerializationHelper<Target, Descriptor> =
findSerializerHelper(fqn) ?: error("Cannot find serializer for $fqn")
private fun findSerializer(fqn: String): KSerializer<Any> {
// we don't care if fqn is Target or Descriptor. We just want to find any helper that is capable of loading its class.
val helpers = findAllImpl { bean ->
bean.descriptor == fqn || bean.implementationClass == fqn
}
val helper = helpers.firstOrNull() ?: error("Cannot find serializer for $fqn")
return helper.serializer
}
private fun findSerializerHelper(fqn: String): SerializationHelper<Target, Descriptor>? {
private fun findAllImpl(filter: (bean: ExtensionPointSerializerBean) -> Boolean): Sequence<SerializationHelper<Target, Descriptor>> {
// todo maybe cache serializers?
val bean = epName.extensionList.find { bean ->
bean.implementationClass == fqn || bean.descriptor == fqn
} ?: return null
@Suppress("UNCHECKED_CAST")
return bean.instance as SerializationHelper<Target, Descriptor>
return epName.extensionList.asSequence()
.filter(filter)
.map { bean ->
@Suppress("UNCHECKED_CAST")
bean.instance as SerializationHelper<Target, Descriptor>
}
}
private val json: Json // TODO investigate if we need caching here
@@ -53,7 +59,8 @@ abstract class ExtensionPointSerializer<Target : Any, Descriptor : Any>(
encodeDefaults = true
serializersModule = SerializersModule {
polymorphic(descriptorClass, this@ExtensionPointSerializer) {
epName.extensionList.map { bean ->
epName.extensionList.forEach { bean ->
@Suppress("UNCHECKED_CAST")
val actualDescriptorClass = bean.instance.serializableClass.kotlin as KClass<Descriptor>
subclass(actualDescriptorClass, this@ExtensionPointSerializer)
}
@@ -68,7 +75,7 @@ abstract class ExtensionPointSerializer<Target : Any, Descriptor : Any>(
override fun serialize(encoder: Encoder, value: Descriptor) {
val fqn = value.javaClass.name
val (serializer, _) = findNotNullSerializerHelper(fqn)
val serializer = findSerializer(fqn)
val element = json.encodeToJsonElement(serializer, value)
val wrapper = SerializerWrapper(fqn, element)
encoder.encodeSerializableValue(SerializerWrapper.serializer(), wrapper)
@@ -76,11 +83,10 @@ abstract class ExtensionPointSerializer<Target : Any, Descriptor : Any>(
override fun deserialize(decoder: Decoder): Descriptor {
val wrapper = decoder.decodeSerializableValue(SerializerWrapper.serializer())
val (serializer, _) = findNotNullSerializerHelper(wrapper.fqn)
val serializer = findSerializer(wrapper.fqn)
@Suppress("UNCHECKED_CAST")
return json.decodeFromJsonElement(serializer, wrapper.value) as Descriptor
}
//}
}
@Serializable
+2
View File
@@ -47,6 +47,8 @@ jvm_library(
"//platform/lang-api:lang",
"//platform/testFramework",
"//platform/testFramework:testFramework_test_lib",
"//platform/testFramework/junit5",
"//platform/testFramework/junit5:junit5_test_lib",
"//libraries/kotlinx/serialization/json",
"@lib//:junit5",
]
@@ -46,6 +46,7 @@
<orderEntry type="module" module-name="intellij.platform.ide.rpc" />
<orderEntry type="module" module-name="intellij.platform.lang" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.kotlinx.serialization.json" scope="TEST" />
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
</component>
@@ -2,20 +2,21 @@
package com.intellij.platform.completion.common.protocol
import com.intellij.patterns.StandardPatterns
import com.intellij.testFramework.junit5.TestApplication
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertNotNull
/**
* Tests for [RpcRestartPrefixCondition] serialization and deserialization.
*
* Tests use converters directly to create descriptors from patterns, avoiding the need for
* extension points while still testing the full conversion pipeline.
* Tests use [toRpc] to convert patterns to RPC format, validating the full conversion pipeline
* including extension point-based converter selection.
*/
@TestApplication
class RpcRestartPrefixConditionTest {
// ============================================================================
@@ -33,234 +34,182 @@ class RpcRestartPrefixConditionTest {
}
// ============================================================================
// Tests for full pattern → converter → descriptor → fromRpc roundtrip
// Tests for full pattern -> toRpc -> fromRpc roundtrip
// ============================================================================
@Test
fun `test AlwaysTrue roundtrip via converter`() {
fun `test AlwaysTrue roundtrip via toRpc`() {
val original = StandardPatterns.string()
val converter = AlwaysTrueConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(AlwaysTrueDescriptor::class.java, descriptor)
assertSerializedDescriptor<AlwaysTrueDescriptor>(rpc)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor!!)
val reconstructed = rpc.fromRpc()
assertTrue(reconstructed.accepts("any string"))
assertTrue(reconstructed.accepts(""))
}
@Test
fun `test LongerThan roundtrip via converter`() {
fun `test LongerThan roundtrip via toRpc`() {
val original = StandardPatterns.string().longerThan(5)
val converter = LongerThanConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(LongerThanDescriptor::class.java, descriptor)
assertEquals(5, (descriptor as LongerThanDescriptor).minLength)
val descriptor = assertSerializedDescriptor<LongerThanDescriptor>(rpc)
assertEquals(5, descriptor.minLength)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "a", "ab", "abc", "abcd", "abcde", "abcdef")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test ShorterThan roundtrip via converter`() {
fun `test ShorterThan roundtrip via toRpc`() {
val original = StandardPatterns.string().shorterThan(3)
val converter = ShorterThanConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(ShorterThanDescriptor::class.java, descriptor)
assertEquals(3, (descriptor as ShorterThanDescriptor).maxLength)
val descriptor = assertSerializedDescriptor<ShorterThanDescriptor>(rpc)
assertEquals(3, descriptor.maxLength)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "a", "ab", "abc", "abcd")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test WithLength roundtrip via converter`() {
fun `test WithLength roundtrip via toRpc`() {
val original = StandardPatterns.string().withLength(5)
val converter = WithLengthConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(WithLengthDescriptor::class.java, descriptor)
assertEquals(5, (descriptor as WithLengthDescriptor).length)
val descriptor = assertSerializedDescriptor<WithLengthDescriptor>(rpc)
assertEquals(5, descriptor.length)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "a", "1234", "12345", "123456")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test StartsWith roundtrip via converter`() {
fun `test StartsWith roundtrip via toRpc`() {
val original = StandardPatterns.string().startsWith("foo")
val converter = StartsWithConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(StartsWithDescriptor::class.java, descriptor)
assertEquals("foo", (descriptor as StartsWithDescriptor).prefix)
val descriptor = assertSerializedDescriptor<StartsWithDescriptor>(rpc)
assertEquals("foo", descriptor.prefix)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "foo", "foobar", "barfoo", "FOO")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test EndsWith roundtrip via converter`() {
fun `test EndsWith roundtrip via toRpc`() {
val original = StandardPatterns.string().endsWith("-")
val converter = EndsWithConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(EndsWithDescriptor::class.java, descriptor)
assertEquals("-", (descriptor as EndsWithDescriptor).suffix)
val descriptor = assertSerializedDescriptor<EndsWithDescriptor>(rpc)
assertEquals("-", descriptor.suffix)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "-", "foo-", "-foo", "foo")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test Contains roundtrip via converter`() {
fun `test Contains roundtrip via toRpc`() {
val original = StandardPatterns.string().contains("bar")
val converter = ContainsConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(ContainsDescriptor::class.java, descriptor)
assertEquals("bar", (descriptor as ContainsDescriptor).substring)
val descriptor = assertSerializedDescriptor<ContainsDescriptor>(rpc)
assertEquals("bar", descriptor.substring)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "bar", "foobar", "barbaz", "baz")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test Matches roundtrip via converter`() {
fun `test Matches roundtrip via toRpc`() {
val original = StandardPatterns.string().matches("foo.*bar")
val converter = MatchesConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(MatchesDescriptor::class.java, descriptor)
assertEquals("foo.*bar", (descriptor as MatchesDescriptor).regex)
val descriptor = assertSerializedDescriptor<MatchesDescriptor>(rpc)
assertEquals("foo.*bar", descriptor.regex)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "foobar", "foo123bar", "barfoo", "foobaz")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test OneOf roundtrip via converter`() {
fun `test OneOf roundtrip via toRpc`() {
val original = StandardPatterns.string().oneOf("get", "set")
val converter = OneOfConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(OneOfDescriptor::class.java, descriptor)
assertEquals(listOf("get", "set"), (descriptor as OneOfDescriptor).values)
val descriptor = assertSerializedDescriptor<OneOfDescriptor>(rpc)
assertEquals(listOf("get", "set"), descriptor.values)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "get", "set", "GET", "put", "getter")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test OneOfIgnoreCase roundtrip via converter`() {
fun `test OneOfIgnoreCase roundtrip via toRpc`() {
val original = StandardPatterns.string().oneOfIgnoreCase("GET", "SET")
val converter = OneOfIgnoreCaseConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(OneOfIgnoreCaseDescriptor::class.java, descriptor)
assertEquals(listOf("GET", "SET"), (descriptor as OneOfIgnoreCaseDescriptor).values)
val descriptor = assertSerializedDescriptor<OneOfIgnoreCaseDescriptor>(rpc)
assertEquals(listOf("GET", "SET"), descriptor.values)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "get", "GET", "Get", "set", "SET", "Set", "put")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test EqualTo roundtrip via converter`() {
fun `test EqualTo roundtrip via toRpc`() {
val original = StandardPatterns.string().equalTo("exact")
val converter = EqualToConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(EqualToDescriptor::class.java, descriptor)
assertEquals("exact", (descriptor as EqualToDescriptor).value)
val descriptor = assertSerializedDescriptor<EqualToDescriptor>(rpc)
assertEquals("exact", descriptor.value)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "exact", "EXACT", "exacty", "exac")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test EndsWithUppercaseLetter roundtrip via converter`() {
fun `test EndsWithUppercaseLetter roundtrip via toRpc`() {
val original = StandardPatterns.string().endsWithUppercaseLetter()
val converter = EndsWithUppercaseLetterConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(EndsWithUppercaseLetterDescriptor::class.java, descriptor)
assertSerializedDescriptor<EndsWithUppercaseLetterDescriptor>(rpc)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor!!)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "a", "A", "abc", "abC", "ABC", "ab1", "ab!", "getA", "getAbc")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test AfterNonJavaIdentifierPart roundtrip via converter`() {
fun `test AfterNonJavaIdentifierPart roundtrip via toRpc`() {
val original = StandardPatterns.string().afterNonJavaIdentifierPart()
val converter = AfterNonJavaIdentifierPartConditionConverter()
val descriptor = converter.toDescriptor(original)
val rpc = original.toRpc()
assertNotNull(descriptor)
assertInstanceOf(AfterNonJavaIdentifierPartDescriptor::class.java, descriptor)
assertSerializedDescriptor<AfterNonJavaIdentifierPartDescriptor>(rpc)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor!!)
val reconstructed = rpc.fromRpc()
// Pattern matches when: length > 1 AND second-to-last char is NOT a Java identifier part
for (input in listOf(
"", // too short - false
@@ -279,42 +228,33 @@ class RpcRestartPrefixConditionTest {
}
@Test
fun `test Or descriptor roundtrip`() {
// OrPatternConverter uses extension points for inner patterns, so we test the descriptor directly
val descriptor = OrDescriptor(listOf(
EndsWithDescriptor("-"),
EndsWithDescriptor("_")
))
fun `test Or roundtrip via toRpc`() {
val original = StandardPatterns.or(
StandardPatterns.string().endsWith("-"),
StandardPatterns.string().endsWith("_")
)
val rpc = original.toRpc()
val descriptor = assertSerializedDescriptor<OrDescriptor>(rpc)
assertEquals(2, descriptor.conditions.size)
assertInstanceOf(EndsWithDescriptor::class.java, descriptor.conditions[0])
assertInstanceOf(EndsWithDescriptor::class.java, descriptor.conditions[1])
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "foo-", "bar_", "baz", "-", "_")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test Not descriptor roundtrip`() {
// NotPatternConverter uses extension points for inner pattern, so we test the descriptor directly
val descriptor = NotDescriptor(StartsWithDescriptor("x"))
fun `test Not roundtrip via toRpc`() {
val original = StandardPatterns.not(StandardPatterns.string().startsWith("x"))
val rpc = original.toRpc()
val descriptor = assertSerializedDescriptor<NotDescriptor>(rpc)
assertInstanceOf(StartsWithDescriptor::class.java, descriptor.condition)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "foo", "xfoo", "x", "X")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
@@ -325,95 +265,86 @@ class RpcRestartPrefixConditionTest {
// ============================================================================
@Test
fun `test nested Or with multiple conditions roundtrip`() {
// For nested patterns, we need to manually build the descriptor since converters
// use the extension point for inner patterns
val descriptor = OrDescriptor(listOf(
StartsWithDescriptor("get"),
StartsWithDescriptor("set"),
StartsWithDescriptor("is")
))
fun `test nested Or with multiple conditions roundtrip via toRpc`() {
val original = StandardPatterns.or(
StandardPatterns.string().startsWith("get"),
StandardPatterns.string().startsWith("set"),
StandardPatterns.string().startsWith("is")
)
val rpc = original.toRpc()
val descriptor = assertSerializedDescriptor<OrDescriptor>(rpc)
assertEquals(3, descriptor.conditions.size)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "getValue", "setValue", "isEnabled", "doSomething")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test Not with LongerThan roundtrip`() {
val descriptor = NotDescriptor(LongerThanDescriptor(5))
fun `test Not with LongerThan roundtrip via toRpc`() {
val original = StandardPatterns.not(StandardPatterns.string().longerThan(5))
val rpc = original.toRpc()
val descriptor = assertSerializedDescriptor<NotDescriptor>(rpc)
assertInstanceOf(LongerThanDescriptor::class.java, descriptor.condition)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "a", "abcde", "abcdef", "abcdefg")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test And descriptor roundtrip`() {
val descriptor = AndDescriptor(AlwaysTrueDescriptor, EndsWithDescriptor("-"))
fun `test And roundtrip via toRpc`() {
val original = StandardPatterns.string().and(StandardPatterns.string().endsWith("-"))
val rpc = original.toRpc()
val descriptor = assertSerializedDescriptor<AndDescriptor>(rpc)
assertInstanceOf(AlwaysTrueDescriptor::class.java, descriptor.base)
assertInstanceOf(EndsWithDescriptor::class.java, descriptor.combined)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "a", "a-", "-", "test-", "test")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test And with afterNonJavaIdentifierPart and Or roundtrip`() {
fun `test And with afterNonJavaIdentifierPart and Or roundtrip via toRpc`() {
// This is the pattern used by endsWithOneOf()
val descriptor = AndDescriptor(
AfterNonJavaIdentifierPartDescriptor,
OrDescriptor(listOf(EndsWithDescriptor("abc"), EndsWithDescriptor("def")))
)
val original = StandardPatterns.string()
.afterNonJavaIdentifierPart()
.and(StandardPatterns.or(
StandardPatterns.string().endsWith("abc"),
StandardPatterns.string().endsWith("def")
))
val rpc = original.toRpc()
val descriptor = assertSerializedDescriptor<AndDescriptor>(rpc)
assertInstanceOf(AfterNonJavaIdentifierPartDescriptor::class.java, descriptor.base)
assertInstanceOf(OrDescriptor::class.java, descriptor.combined)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "a", " abc", ".def", "xabc", "x.abc", " x")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
}
@Test
fun `test endsWithOneOf pattern roundtrip`() {
fun `test endsWithOneOf pattern roundtrip via toRpc`() {
// endsWithOneOf internally uses: .and(StandardPatterns.or(endsWith patterns...))
// Build descriptor manually (converter uses extension points which need app context)
val descriptor = AndDescriptor(
AfterNonJavaIdentifierPartDescriptor,
OrDescriptor(listOf(EndsWithDescriptor("key1"), EndsWithDescriptor("key2")))
)
val original = StandardPatterns.string()
.afterNonJavaIdentifierPart()
.endsWithOneOf(listOf("key1", "key2"))
val rpc = original.toRpc()
val descriptor = assertSerializedDescriptor<AndDescriptor>(rpc)
assertInstanceOf(AfterNonJavaIdentifierPartDescriptor::class.java, descriptor.base)
assertInstanceOf(OrDescriptor::class.java, descriptor.combined)
val rpc = RpcRestartPrefixCondition.Serialized(descriptor)
val reconstructed = rpc.fromRpc()
for (input in listOf("", "key1", " key1", ".key2", "xkey1", "x.key1")) {
assertEquals(original.accepts(input), reconstructed.accepts(input), "Mismatch for '$input'")
}
@@ -424,26 +355,29 @@ class RpcRestartPrefixConditionTest {
// ============================================================================
@Test
fun `test LongerThan converter rejects pattern with multiple conditions`() {
fun `test pattern with multiple incompatible conditions returns AlwaysTrue`() {
// A pattern that no single converter can handle should fall back to AlwaysTrue
// (This tests the fallback behavior when no converter matches)
val pattern = StandardPatterns.string().longerThan(5).shorterThan(10)
val converter = LongerThanConditionConverter()
val rpc = pattern.toRpc()
assertNull(converter.toDescriptor(pattern), "Converter should reject patterns with multiple conditions")
// Since no single converter handles this combination, it falls back to AlwaysTrue
assertInstanceOf(RpcRestartPrefixCondition.AlwaysTrue::class.java, rpc)
}
@Test
fun `test StartsWith converter rejects EndsWith pattern`() {
val pattern = StandardPatterns.string().endsWith("foo")
val converter = StartsWithConditionConverter()
// ============================================================================
// Helper methods
// ============================================================================
assertNull(converter.toDescriptor(pattern), "Converter should reject incompatible patterns")
}
@Test
fun `test AlwaysTrue converter rejects pattern with conditions`() {
val pattern = StandardPatterns.string().longerThan(3)
val converter = AlwaysTrueConditionConverter()
assertNull(converter.toDescriptor(pattern), "Converter should reject patterns with conditions")
private inline fun <reified T> assertSerializedDescriptor(rpc: RpcRestartPrefixCondition): T {
assertInstanceOf(RpcRestartPrefixCondition.Serialized::class.java, rpc,
"Expected RpcRestartPrefixCondition.Serialized but got ${rpc::class.simpleName}")
val serialized = rpc as RpcRestartPrefixCondition.Serialized
val descriptor = serialized.descriptor
assertNotNull(descriptor, "Descriptor should not be null")
assertInstanceOf(T::class.java, descriptor,
"Expected ${T::class.simpleName} but got ${descriptor::class.simpleName}")
@Suppress("UNCHECKED_CAST")
return descriptor as T
}
}