From a1b4f51ac175aef1ae59c09a6a516c0c401beb27 Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Tue, 29 Apr 2025 15:48:00 +0200 Subject: [PATCH 1/2] (WIP) add validation test --- .../pom.xml | 17 ++++++ .../kotlin/KotlinSerializableTest.java | 34 +++++++++++ .../targets/KotlinSerializableTarget.kt | 61 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/KotlinSerializableTest.java create mode 100644 org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinSerializableTarget.kt diff --git a/org.jacoco.core.test.validation.kotlin/pom.xml b/org.jacoco.core.test.validation.kotlin/pom.xml index 609325879e..0a4060c173 100644 --- a/org.jacoco.core.test.validation.kotlin/pom.xml +++ b/org.jacoco.core.test.validation.kotlin/pom.xml @@ -43,6 +43,11 @@ kotlinx-coroutines-core 1.8.0 + + org.jetbrains.kotlinx + kotlinx-serialization-core-jvm + 1.6.3 + @@ -60,6 +65,18 @@ + + + kotlinx-serialization + + + + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + + diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/KotlinSerializableTest.java b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/KotlinSerializableTest.java new file mode 100644 index 0000000000..1afc08cf5a --- /dev/null +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/KotlinSerializableTest.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2009, 2025 Mountainminds GmbH & Co. KG and Contributors + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.test.validation.kotlin; + +import org.jacoco.core.test.validation.ValidationTestBase; +import org.jacoco.core.test.validation.kotlin.targets.KotlinSerializableTarget; +import org.junit.Test; + +/** + * Test of code coverage in {@link KotlinSerializableTarget}. + */ +public class KotlinSerializableTest extends ValidationTestBase { + + public KotlinSerializableTest() { + super(KotlinSerializableTarget.class); + } + + @Test + public void test_method_count() { + assertMethodCount( + /* main + 3 constructors + 3 getters + 1 method in companion */8); + } + +} diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinSerializableTarget.kt b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinSerializableTarget.kt new file mode 100644 index 0000000000..d6eb2a5368 --- /dev/null +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinSerializableTarget.kt @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009, 2025 Mountainminds GmbH & Co. KG and Contributors + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.test.validation.kotlin.targets + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * Test target with [Serializable] class. + */ +object KotlinSerializableTarget { + + @Serializable // assertEmpty() + data class Example( // assertFullyCovered() + @SerialName("d") val data: String // assertFullyCovered() + ) // assertEmpty() + + @Serializable(with = CustomSerializer::class) // assertEmpty() + data class ExampleWithCustomSerializer( // assertFullyCovered() + val data: String // assertFullyCovered() + ) // assertEmpty() + + object CustomSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Example", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ExampleWithCustomSerializer) = encoder.encodeString(value.data) + override fun deserialize(decoder: Decoder): ExampleWithCustomSerializer = + ExampleWithCustomSerializer(decoder.decodeString()) + } + + data class ExampleWithHandWrittenCompanion( + val data: String + ) { + companion object { + fun serializer(): KSerializer = CustomSerializer // assertNotCovered() + } + } + + @JvmStatic + fun main(args: Array) { + Example("").data + ExampleWithCustomSerializer("").data + ExampleWithHandWrittenCompanion("") + } + +} From 0abbc622d7cfb4ba7b168ee66c2159ab17c6b90a Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Wed, 30 Apr 2025 11:32:16 +0200 Subject: [PATCH 2/2] (WIP) add unit tests --- .../filter/KotlinSerializableFilterTest.java | 172 ++++++++++++++++++ .../filter/KotlinSerializableFilter.java | 29 +++ 2 files changed, 201 insertions(+) create mode 100644 org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinSerializableFilterTest.java create mode 100644 org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinSerializableFilter.java diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinSerializableFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinSerializableFilterTest.java new file mode 100644 index 0000000000..41a1901115 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinSerializableFilterTest.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2009, 2025 Mountainminds GmbH & Co. KG and Contributors + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit test for {@link KotlinSerializableFilter}. + */ +public class KotlinSerializableFilterTest extends FilterTestBase { + + private final IFilter filter = new KotlinSerializableFilter(); + + /** + *
+	 * @kotlinx.serialization.Serializable
+	 * data class Example(val data: String)
+	 * 
+ */ + @Test + public void should_filter_synthetic_writeSelf_method() { + final MethodNode m = new MethodNode( + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC + | Opcodes.ACC_SYNTHETIC, + "write$Self$pkg", + "(Lpkg$Example;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V", + null, null); + m.visitInsn(Opcodes.NOP); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + /** + *
+	 * @kotlinx.serialization.Serializable
+	 * data class Example(val data: String)
+	 * 
+ */ + @Test + public void should_filter_synthetic_constructor() { + final MethodNode m = new MethodNode( + Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "", + "(ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V", + null, null); + m.visitInsn(Opcodes.NOP); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + /** + *
+	 * @kotlinx.serialization.Serializable // line 1
+	 * data class Example(val data: String)
+	 * 
+ */ + @Test + public void should_filter_generated_serializer_method() { + context.className = "Example$Companion"; + + final MethodNode initMethod = new MethodNode(Opcodes.ACC_PRIVATE, + "", "()V", null, null); + // no line numbers + filter.filter(initMethod, context, output); + + final MethodNode m = new MethodNode( + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, "serializer", + "()Lkotlinx/serialization/KSerializer;", + "()Lkotlinx/serialization/KSerializer;", null); + final Label label0 = new Label(); + m.visitLabel(label0); + m.visitLineNumber(1, label0); + m.visitFieldInsn(Opcodes.GETSTATIC, "Example$$serializer", "INSTANCE", + "LExample$$serializer;"); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlinx/serialization/KSerializer"); + m.visitInsn(Opcodes.ARETURN); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + /** + *
+	 * @kotlinx.serialization.Serializable
+	 * data class Example(val data: String) {
+	 *     companion object // line 2
+	 * }
+	 * 
+ */ + @Test + public void should_filter_generated_serializer_method_in_hand_written_companion() { + context.className = "Example$Companion"; + + final MethodNode initMethod = new MethodNode(Opcodes.ACC_PRIVATE, + "", "()V", null, null); + final Label initMethodLineNumberLabel = new Label(); + initMethod.visitLabel(initMethodLineNumberLabel); + initMethod.visitLineNumber(2, initMethodLineNumberLabel); + filter.filter(initMethod, context, output); + + final MethodNode m = new MethodNode( + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, "serializer", + "()Lkotlinx/serialization/KSerializer;", + "()Lkotlinx/serialization/KSerializer;", null); + final Label label0 = new Label(); + m.visitLabel(label0); + m.visitLineNumber(2, label0); + m.visitFieldInsn(Opcodes.GETSTATIC, "Example$$serializer", "INSTANCE", + "LExample$$serializer;"); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlinx/serialization/KSerializer"); + m.visitInsn(Opcodes.ARETURN); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + /** + *
+	 * data class Example(val data: String) {
+	 *     companion object { // line 2
+	 *         fun serializer(): KSerializer<Example> = CustomSerializer
+	 *     }
+	 * }
+	 * 
+ */ + @Test + public void should_not_filter_hand_written_serializer_method() { + context.className = "Example$Companion"; + + final MethodNode initMethod = new MethodNode(Opcodes.ACC_PRIVATE, + "", "()V", null, null); + final Label initMethodLineNumberLabel = new Label(); + initMethod.visitLabel(initMethodLineNumberLabel); + initMethod.visitLineNumber(2, initMethodLineNumberLabel); + filter.filter(initMethod, context, output); + + final MethodNode m = new MethodNode( + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, "serializer", + "()Lkotlinx/serialization/KSerializer;", + "()Lkotlinx/serialization/KSerializer;", null); + final Label label0 = new Label(); + m.visitLabel(label0); + m.visitLineNumber(3, label0); + m.visitFieldInsn(Opcodes.GETSTATIC, "CustomSerializer", "INSTANCE", + "LCustomSerializer;"); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlinx/serialization/KSerializer"); + m.visitInsn(Opcodes.ARETURN); + + filter.filter(m, context, output); + + assertIgnored(m); + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinSerializableFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinSerializableFilter.java new file mode 100644 index 0000000000..57baf85a7e --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinSerializableFilter.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2009, 2025 Mountainminds GmbH & Co. KG and Contributors + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters methods generated by Kotlin + * serialization compiler plugin. + */ +final class KotlinSerializableFilter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + // TODO + } + +}