From 04d70984839473b874a0e3e7f6525d351a5f1b05 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sat, 21 Feb 2026 02:08:12 +0530 Subject: [PATCH 01/16] Fix export of list attributes with custom Stapler converters --- .../configurators/DataBoundConfigurator.java | 49 +++++++++++- .../DataBoundConfiguratorTest.java | 79 +++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java b/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java index dae7fa9d72..d62098c2f2 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java @@ -11,6 +11,7 @@ import io.jenkins.plugins.casc.impl.attributes.DescribableAttribute; import io.jenkins.plugins.casc.model.CNode; import io.jenkins.plugins.casc.model.Mapping; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -301,8 +302,52 @@ public CNode describe(T instance, ConfigurationContext context) throws Exception if (a != null) { Object value = a.getValue(instance); if (value != null) { - Object converted = Stapler.CONVERT_UTILS.convert(value, a.getType()); - if (converted instanceof Collection || p.getType().isArray() || !a.isMultiple()) { + Object converted; + Class targetType = a.getType(); + + boolean targetIsCollectionOrArray = + Collection.class.isAssignableFrom(targetType) || targetType.isArray(); + + if (!targetIsCollectionOrArray + && (value instanceof Collection || value.getClass().isArray())) { + + Iterable iterable; + if (value instanceof Collection) { + iterable = (Collection) value; + } else { + int length = Array.getLength(value); + List list = new ArrayList<>(length); + for (int j = 0; j < length; j++) { + list.add(Array.get(value, j)); + } + iterable = list; + } + + List convertedList = new ArrayList<>(); + for (Object item : iterable) { + if (item != null) { + convertedList.add(Stapler.CONVERT_UTILS.convert(item, targetType)); + } else { + convertedList.add(null); + } + } + converted = convertedList; + + } else { + converted = Stapler.CONVERT_UTILS.convert(value, targetType); + } + if (p.getType().isArray() && converted instanceof Collection col) { + Class component = p.getType().getComponentType(); + Object array = Array.newInstance(component, col.size()); + int idx = 0; + for (Object o : col) { + Array.set(array, idx++, o); + } + args[i] = array; + + } else if (Set.class.isAssignableFrom(p.getType()) && converted instanceof Collection) { + args[i] = new HashSet<>((Collection) converted); + } else if (converted instanceof Collection || !a.isMultiple()) { args[i] = converted; } else if (Set.class.isAssignableFrom(p.getType())) { args[i] = Collections.singleton(converted); diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 06fdedeba2..4aaec21795 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -30,12 +30,16 @@ import io.jenkins.plugins.casc.model.Sequence; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.PostConstruct; +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.beanutils.Converter; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -426,4 +430,79 @@ public ArrayConstructor(Foo[] anArray) { this.anArray = anArray; } } + + @SuppressWarnings("ClassCanBeRecord") + public static class CustomItem { + private final String value; + + @DataBoundConstructor + public CustomItem(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @SuppressWarnings("unused") + public static class StaplerConverterImpl implements Converter { + @SuppressWarnings("unchecked") + @Override + public T convert(Class type, Object value) { + if (type == String.class && value instanceof CustomItem) { + return (T) ("converted-" + ((CustomItem) value).getValue()); + } + if (type == CustomItem.class && value instanceof String) { + return (T) new CustomItem(((String) value).replace("converted-", "")); + } + if (type.isInstance(value)) { + return (T) value; + } + throw new ConversionException( + "Unsupported conversion from " + value.getClass().getName() + " to " + type.getName()); + } + } + } + + @SuppressWarnings("ClassCanBeRecord") + public static class CustomItemListHolder { + private final List items; + + @DataBoundConstructor + public CustomItemListHolder(List items) { + this.items = items; + } + + @SuppressWarnings("unused") + public List getItems() { + return items; + } + } + + @Test + @Issue("https://github.com/jenkinsci/configuration-as-code-plugin/issues/2346") + void exportWithCustomConverterIteratesOverList() throws Exception { + List list = Arrays.asList(new CustomItem("A"), new CustomItem("B")); + CustomItemListHolder holder = new CustomItemListHolder(list); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + final Configurator c = registry.lookupOrFail(CustomItemListHolder.class); + final ConfigurationContext context = new ConfigurationContext(registry); + + CNode node = c.describe(holder, context); + + assertNotNull(node); + assertInstanceOf(Mapping.class, node); + Mapping map = (Mapping) node; + + Sequence items = map.get("items").asSequence(); + + assertEquals(2, items.size()); + + Mapping first = items.get(0).asMapping(); + Mapping second = items.get(1).asMapping(); + + assertEquals("A", first.get("value").toString()); + assertEquals("B", second.get("value").toString()); + } } From fbd5282be15a49a412cb64d89d74a7436dd39e98 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sat, 21 Feb 2026 15:24:25 +0530 Subject: [PATCH 02/16] Improve code coverage by addming more tests --- plugin/pom.xml | 8 ++ .../DataBoundConfiguratorTest.java | 107 +++++++++++++++++- 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/plugin/pom.xml b/plugin/pom.xml index 8e21865fa3..2c05fadea2 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -111,6 +111,14 @@ false + + org.apache.maven.plugins + maven-compiler-plugin + + 25 + 25 + + diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 4aaec21795..e0c7909f1a 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -495,14 +496,108 @@ void exportWithCustomConverterIteratesOverList() throws Exception { assertInstanceOf(Mapping.class, node); Mapping map = (Mapping) node; - Sequence items = map.get("items").asSequence(); + assertEquals( + "- value: \"A\"\n- value: \"B\"", + Util.toYamlString(map.get("items")).trim()); + } + + @SuppressWarnings("ClassCanBeRecord") + public static class CustomItemSetHolder { + private final Set items; + + @DataBoundConstructor + public CustomItemSetHolder(Set items) { + this.items = items; + } + + @SuppressWarnings("unused") + public Set getItems() { + return items; + } + } + + @Test + void exportWithCustomConverterIteratesOverSet() throws Exception { + Set set = new HashSet<>(); + set.add(new CustomItem("A")); + set.add(new CustomItem("B")); + + CustomItemSetHolder holder = new CustomItemSetHolder(set); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + Configurator c = registry.lookupOrFail(CustomItemSetHolder.class); + + CNode node = c.describe(holder, new ConfigurationContext(registry)); + Mapping map = (Mapping) node; + + String yaml = + Util.toYamlString(Objects.requireNonNull(map).get("items")).trim(); + + assertTrue(yaml.contains("value: \"A\"")); + assertTrue(yaml.contains("value: \"B\"")); + } + + @SuppressWarnings("ClassCanBeRecord") + public static class CustomItemArrayHolder { + private final CustomItem[] items; + + @DataBoundConstructor + public CustomItemArrayHolder(CustomItem[] items) { + this.items = items; + } + + @SuppressWarnings("unused") + public CustomItem[] getItems() { + return items; + } + } + + @Test + void exportWithCustomConverterIteratesOverArray() throws Exception { + CustomItem[] array = {new CustomItem("A"), new CustomItem("B")}; + + CustomItemArrayHolder holder = new CustomItemArrayHolder(array); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + Configurator c = registry.lookupOrFail(CustomItemArrayHolder.class); + + CNode node = c.describe(holder, new ConfigurationContext(registry)); + Mapping map = (Mapping) node; + + assertEquals( + "- value: \"A\"\n- value: \"B\"", + Util.toYamlString(Objects.requireNonNull(map).get("items")).trim()); + } + + @Test + void exportWithSingleElementList() throws Exception { + CustomItemListHolder holder = new CustomItemListHolder(List.of(new CustomItem("A"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + Configurator c = registry.lookupOrFail(CustomItemListHolder.class); + + CNode node = c.describe(holder, new ConfigurationContext(registry)); + Mapping map = (Mapping) node; + + assertEquals( + "- value: \"A\"", + Util.toYamlString(Objects.requireNonNull(map).get("items")).trim()); + } + + @Test + void exportWithCustomConverterIteratesOverListWithNull() throws Exception { + List list = Arrays.asList(new CustomItem("A"), null); + CustomItemListHolder holder = new CustomItemListHolder(list); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + Configurator c = registry.lookupOrFail(CustomItemListHolder.class); - assertEquals(2, items.size()); + CNode node = c.describe(holder, new ConfigurationContext(registry)); - Mapping first = items.get(0).asMapping(); - Mapping second = items.get(1).asMapping(); + assertNotNull(node, "Node should not be null"); + assertInstanceOf(Mapping.class, node, "Node should be exported as a Mapping"); - assertEquals("A", first.get("value").toString()); - assertEquals("B", second.get("value").toString()); + String yaml = Util.toYamlString(node); + assertTrue(yaml.contains("A"), "The valid item 'A' should be present in the exported YAML"); } } From d1701869dbff4b6cd364f58b8952d9bbc65427b1 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sat, 21 Feb 2026 15:28:48 +0530 Subject: [PATCH 03/16] Remove unnecessary additions --- plugin/pom.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugin/pom.xml b/plugin/pom.xml index 2c05fadea2..8e21865fa3 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -111,14 +111,6 @@ false - - org.apache.maven.plugins - maven-compiler-plugin - - 25 - 25 - - From 1125720cb7f73679a2d196e1e4846f4b97941e70 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sat, 21 Feb 2026 21:49:35 +0530 Subject: [PATCH 04/16] Added more tests --- .../DataBoundConfiguratorTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index e0c7909f1a..307e33e33a 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -600,4 +600,59 @@ void exportWithCustomConverterIteratesOverListWithNull() throws Exception { String yaml = Util.toYamlString(node); assertTrue(yaml.contains("A"), "The valid item 'A' should be present in the exported YAML"); } + + @Test + void configureIteratesOverList() { + Mapping config = new Mapping(); + Sequence items = new Sequence(); + + Mapping itemA = new Mapping(); + itemA.put("value", "A"); + items.add(itemA); + + Mapping itemB = new Mapping(); + itemB.put("value", "B"); + items.add(itemB); + + config.put("items", items); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CustomItemListHolder configured = (CustomItemListHolder) registry.lookupOrFail(CustomItemListHolder.class) + .configure(config, new ConfigurationContext(registry)); + + assertNotNull(configured); + assertEquals(2, configured.getItems().size()); + assertEquals("A", configured.getItems().get(0).getValue()); + assertEquals("B", configured.getItems().get(1).getValue()); + } + + @Test + void configureConvertsListToSet() { + Mapping config = new Mapping(); + Sequence items = new Sequence(); + + Mapping itemA = new Mapping(); + itemA.put("value", "A"); + items.add(itemA); + + Mapping itemB = new Mapping(); + itemB.put("value", "B"); + items.add(itemB); + + config.put("items", items); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CustomItemSetHolder configured = + (CustomItemSetHolder) registry.lookupOrFail(CustomItemSetHolder.class) + .configure(config, new ConfigurationContext(registry)); + + assertNotNull(configured); + assertEquals(2, configured.getItems().size()); + assertNotNull(configured.getItems()); + + assertTrue( + configured.getItems().stream().anyMatch(i -> "A".equals(i.getValue()))); + assertTrue( + configured.getItems().stream().anyMatch(i -> "B".equals(i.getValue()))); + } } From 164cfd7e01369a20f4223f24fdf4ddee63225f69 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sat, 21 Feb 2026 22:00:27 +0530 Subject: [PATCH 05/16] Fixed formatting errors --- .../configurators/DataBoundConfiguratorTest.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 307e33e33a..bd47247bea 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -617,8 +617,8 @@ void configureIteratesOverList() { config.put("items", items); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CustomItemListHolder configured = (CustomItemListHolder) registry.lookupOrFail(CustomItemListHolder.class) - .configure(config, new ConfigurationContext(registry)); + CustomItemListHolder configured = (CustomItemListHolder) + registry.lookupOrFail(CustomItemListHolder.class).configure(config, new ConfigurationContext(registry)); assertNotNull(configured); assertEquals(2, configured.getItems().size()); @@ -642,17 +642,14 @@ void configureConvertsListToSet() { config.put("items", items); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CustomItemSetHolder configured = - (CustomItemSetHolder) registry.lookupOrFail(CustomItemSetHolder.class) - .configure(config, new ConfigurationContext(registry)); + CustomItemSetHolder configured = (CustomItemSetHolder) + registry.lookupOrFail(CustomItemSetHolder.class).configure(config, new ConfigurationContext(registry)); assertNotNull(configured); assertEquals(2, configured.getItems().size()); assertNotNull(configured.getItems()); - assertTrue( - configured.getItems().stream().anyMatch(i -> "A".equals(i.getValue()))); - assertTrue( - configured.getItems().stream().anyMatch(i -> "B".equals(i.getValue()))); + assertTrue(configured.getItems().stream().anyMatch(i -> "A".equals(i.getValue()))); + assertTrue(configured.getItems().stream().anyMatch(i -> "B".equals(i.getValue()))); } } From 45cc362a8c15745ad79c228f213c1816fc032874 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sun, 22 Feb 2026 10:14:22 +0530 Subject: [PATCH 06/16] Remove unused code --- .../DataBoundConfiguratorTest.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index bd47247bea..f0f02f1a25 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -39,8 +39,6 @@ import java.util.logging.Logger; import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.PostConstruct; -import org.apache.commons.beanutils.ConversionException; -import org.apache.commons.beanutils.Converter; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -444,25 +442,6 @@ public CustomItem(String value) { public String getValue() { return value; } - - @SuppressWarnings("unused") - public static class StaplerConverterImpl implements Converter { - @SuppressWarnings("unchecked") - @Override - public T convert(Class type, Object value) { - if (type == String.class && value instanceof CustomItem) { - return (T) ("converted-" + ((CustomItem) value).getValue()); - } - if (type == CustomItem.class && value instanceof String) { - return (T) new CustomItem(((String) value).replace("converted-", "")); - } - if (type.isInstance(value)) { - return (T) value; - } - throw new ConversionException( - "Unsupported conversion from " + value.getClass().getName() + " to " + type.getName()); - } - } } @SuppressWarnings("ClassCanBeRecord") From 1be85e6e34ca00272669568baa148f982710f0ab Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sun, 22 Feb 2026 10:18:23 +0530 Subject: [PATCH 07/16] Remove unused annotations --- .../casc/impl/configurators/DataBoundConfiguratorTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index f0f02f1a25..04da8cfc3c 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -453,7 +453,6 @@ public CustomItemListHolder(List items) { this.items = items; } - @SuppressWarnings("unused") public List getItems() { return items; } @@ -489,7 +488,6 @@ public CustomItemSetHolder(Set items) { this.items = items; } - @SuppressWarnings("unused") public Set getItems() { return items; } From 2b860c078e000ef0c6fe27eafd133b16417b6bad Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sun, 22 Feb 2026 16:11:48 +0530 Subject: [PATCH 08/16] Remove unreachable code --- .../configurators/DataBoundConfigurator.java | 36 ++-------- .../DataBoundConfiguratorTest.java | 68 +++++++++++++++++++ 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java b/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java index d62098c2f2..10ab030dd0 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java @@ -302,40 +302,10 @@ public CNode describe(T instance, ConfigurationContext context) throws Exception if (a != null) { Object value = a.getValue(instance); if (value != null) { - Object converted; Class targetType = a.getType(); - boolean targetIsCollectionOrArray = - Collection.class.isAssignableFrom(targetType) || targetType.isArray(); + Object converted = Stapler.CONVERT_UTILS.convert(value, targetType); - if (!targetIsCollectionOrArray - && (value instanceof Collection || value.getClass().isArray())) { - - Iterable iterable; - if (value instanceof Collection) { - iterable = (Collection) value; - } else { - int length = Array.getLength(value); - List list = new ArrayList<>(length); - for (int j = 0; j < length; j++) { - list.add(Array.get(value, j)); - } - iterable = list; - } - - List convertedList = new ArrayList<>(); - for (Object item : iterable) { - if (item != null) { - convertedList.add(Stapler.CONVERT_UTILS.convert(item, targetType)); - } else { - convertedList.add(null); - } - } - converted = convertedList; - - } else { - converted = Stapler.CONVERT_UTILS.convert(value, targetType); - } if (p.getType().isArray() && converted instanceof Collection col) { Class component = p.getType().getComponentType(); Object array = Array.newInstance(component, col.size()); @@ -347,7 +317,9 @@ public CNode describe(T instance, ConfigurationContext context) throws Exception } else if (Set.class.isAssignableFrom(p.getType()) && converted instanceof Collection) { args[i] = new HashSet<>((Collection) converted); - } else if (converted instanceof Collection || !a.isMultiple()) { + } else if (converted instanceof Collection + || (converted != null && converted.getClass().isArray()) + || !a.isMultiple()) { args[i] = converted; } else if (Set.class.isAssignableFrom(p.getType())) { args[i] = Collections.singleton(converted); diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 04da8cfc3c..eb034e0f83 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -12,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.kohsuke.stapler.Stapler.CONVERT_UTILS; import hudson.util.Secret; import io.jenkins.plugins.casc.ConfigurationAsCode; @@ -39,6 +40,7 @@ import java.util.logging.Logger; import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.PostConstruct; +import org.apache.commons.beanutils.Converter; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -629,4 +631,70 @@ void configureConvertsListToSet() { assertTrue(configured.getItems().stream().anyMatch(i -> "A".equals(i.getValue()))); assertTrue(configured.getItems().stream().anyMatch(i -> "B".equals(i.getValue()))); } + + @SuppressWarnings("ClassCanBeRecord") + public static class StaplerOnlyItem { + private final String value; + + public StaplerOnlyItem(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + @SuppressWarnings("ClassCanBeRecord") + public static class StaplerOnlyItemListHolder { + private final List items; + + @DataBoundConstructor + public StaplerOnlyItemListHolder(List items) { + this.items = items; + } + + public List getItems() { + return items; + } + } + + public static class StaplerOnlyItemConverter implements Converter { + @Override + public T convert(Class type, Object value) { + assertFalse(value instanceof List, "Converter must be called per item, not per list"); + if (value == null) return null; + + return type.cast(new StaplerOnlyItem("converted-by-stapler-" + value)); + } + } + + @Test + void shouldConvertListItemsUsingStaplerConverter() { + CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + + try { + Mapping config = new Mapping(); + Sequence items = new Sequence(); + + items.add(new Scalar("ItemA")); + items.add(new Scalar("ItemB")); + config.put("items", items); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + StaplerOnlyItemListHolder configured = + (StaplerOnlyItemListHolder) registry.lookupOrFail(StaplerOnlyItemListHolder.class) + .configure(config, new ConfigurationContext(registry)); + + assertNotNull(configured); + assertEquals(2, configured.getItems().size()); + assertEquals( + "converted-by-stapler-ItemA", configured.getItems().get(0).getValue()); + assertEquals( + "converted-by-stapler-ItemB", configured.getItems().get(1).getValue()); + + } finally { + CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } } From c2ec615cf6654f05d3911538be01a0278ffa4f3b Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sun, 22 Feb 2026 16:39:32 +0530 Subject: [PATCH 09/16] Added brancket in if condition --- .../casc/impl/configurators/DataBoundConfiguratorTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index eb034e0f83..aa8bf6d0ee 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -663,7 +663,9 @@ public static class StaplerOnlyItemConverter implements Converter { @Override public T convert(Class type, Object value) { assertFalse(value instanceof List, "Converter must be called per item, not per list"); - if (value == null) return null; + if (value == null) { + return null; + } return type.cast(new StaplerOnlyItem("converted-by-stapler-" + value)); } From 073d56f187dc2656a43690eb83ee03dbe458fe8d Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sun, 22 Feb 2026 16:55:16 +0530 Subject: [PATCH 10/16] Increase code coverage --- .../configurators/DataBoundConfigurator.java | 18 ++++++++++- .../DataBoundConfiguratorTest.java | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java b/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java index 10ab030dd0..201312bbb8 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java @@ -303,8 +303,24 @@ public CNode describe(T instance, ConfigurationContext context) throws Exception Object value = a.getValue(instance); if (value != null) { Class targetType = a.getType(); + Object converted; - Object converted = Stapler.CONVERT_UTILS.convert(value, targetType); + if (a.isMultiple() && value instanceof Collection) { + List list = new ArrayList<>(); + for (Object o : (Collection) value) { + list.add(Stapler.CONVERT_UTILS.convert(o, targetType)); + } + converted = list; + } else if (a.isMultiple() && value.getClass().isArray()) { + List list = new ArrayList<>(); + int len = Array.getLength(value); + for (int j = 0; j < len; j++) { + list.add(Stapler.CONVERT_UTILS.convert(Array.get(value, j), targetType)); + } + converted = list; + } else { + converted = Stapler.CONVERT_UTILS.convert(value, targetType); + } if (p.getType().isArray() && converted instanceof Collection col) { Class component = p.getType().getComponentType(); diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index aa8bf6d0ee..2f65727762 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -699,4 +699,35 @@ void shouldConvertListItemsUsingStaplerConverter() { CONVERT_UTILS.deregister(StaplerOnlyItem.class); } } + + public static class ArrayMismatch { + private final String[] items; + + @DataBoundConstructor + public ArrayMismatch(String[] items) { + this.items = items; + } + + @SuppressWarnings("unused") + public List getItems() { + return items == null ? null : Arrays.asList(items); + } + } + + @Test + public void describe_shouldHandleArrayConstructorWithListGetter() throws Exception { + DataBoundConfigurator configurator = new DataBoundConfigurator<>(ArrayMismatch.class); + ArrayMismatch instance = new ArrayMismatch(new String[] {"value1", "value2"}); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + ConfigurationContext context = new ConfigurationContext(registry); + + CNode node = configurator.describe(instance, context); + + assertNotNull(node); + assertInstanceOf(Mapping.class, node); + Mapping mapping = (Mapping) node; + assertTrue(mapping.containsKey("items")); + assertEquals(2, mapping.get("items").asSequence().size()); + } } From 7ac085a08c3bb3ae1a6ff1d122f290c8c75af37c Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sun, 22 Feb 2026 18:14:27 +0530 Subject: [PATCH 11/16] Added more tests --- .../DataBoundConfiguratorTest.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 2f65727762..a1a5a905ee 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -32,6 +32,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -50,6 +51,7 @@ import org.jvnet.hudson.test.junit.jupiter.WithJenkins; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.Stapler; /** * @author Nicolas De Loof @@ -730,4 +732,123 @@ public void describe_shouldHandleArrayConstructorWithListGetter() throws Excepti assertTrue(mapping.containsKey("items")); assertEquals(2, mapping.get("items").asSequence().size()); } + + @SuppressWarnings("ClassCanBeRecord") + public static class ListGetterMultiCtor { + private final List items; + + @DataBoundConstructor + public ListGetterMultiCtor(List items) { + this.items = items; + } + + @SuppressWarnings("unused") + public List getItems() { + return items; + } + } + + @Test + void describe_iteratesCollectionAndConvertsEachItem() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + ListGetterMultiCtor obj = new ListGetterMultiCtor(Collections.singletonList(new StaplerOnlyItem("A"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = + registry.lookupOrFail(ListGetterMultiCtor.class).describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + + @SuppressWarnings("ClassCanBeRecord") + public static class ArrayGetterMultiCtor { + private final StaplerOnlyItem[] items; + + @DataBoundConstructor + public ArrayGetterMultiCtor(StaplerOnlyItem[] items) { + this.items = items; + } + + @SuppressWarnings("unused") + public StaplerOnlyItem[] getItems() { + return items; + } + } + + @Test + void describe_iteratesArrayAndConvertsEachItem() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + StaplerOnlyItem[] array = new StaplerOnlyItem[] {new StaplerOnlyItem("A")}; + ArrayGetterMultiCtor obj = new ArrayGetterMultiCtor(array); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = + registry.lookupOrFail(ArrayGetterMultiCtor.class).describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + + public static class ListGetterArrayCtor { + private final String[] items; + + @DataBoundConstructor + public ListGetterArrayCtor(String[] items) { + this.items = items; + } + + @SuppressWarnings("unused") + public List getItems() { + return Arrays.asList(items); + } + } + + @Test + void describe_convertsCollectionToArrayForConstructor() throws Exception { + ListGetterArrayCtor obj = new ListGetterArrayCtor(new String[] {"A", "B"}); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(ListGetterArrayCtor.class).describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + assertInstanceOf(Mapping.class, node); + + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } + + public static class ListGetterSetCtor { + + @DataBoundConstructor + @SuppressWarnings("unused") + public ListGetterSetCtor(Set items) { + // intentionally unused: required to exercise Set to Collection conversion + } + + @SuppressWarnings("unused") + public List getItems() { + return List.of("A", "B"); + } + } + + @Test + void describe_convertsCollectionToSetForConstructor() throws Exception { + ListGetterSetCtor obj = new ListGetterSetCtor(Set.of("A", "B")); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(ListGetterSetCtor.class).describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + assertInstanceOf(Mapping.class, node); + + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } } From d2a0cdf93c33fe0d5eab89c5c552d8be9792c68c Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Sun, 22 Feb 2026 21:18:46 +0530 Subject: [PATCH 12/16] Added code coverage focused test --- .../DataBoundConfiguratorTest.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index a1a5a905ee..2d59782781 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -32,6 +32,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -851,4 +852,129 @@ void describe_convertsCollectionToSetForConstructor() throws Exception { Mapping mapping = (Mapping) node; assertEquals(2, mapping.get("items").asSequence().size()); } + + @SuppressWarnings("ClassCanBeRecord") + public static class CollectionCtorCollectionGetter { + private final List items; + + @DataBoundConstructor + public CollectionCtorCollectionGetter(List items) { + this.items = items; + } + + @SuppressWarnings("unused") + public List getItems() { + return items; + } + } + + @Test + void describe_convertsCollectionWhenCtorIsMultiple() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + CollectionCtorCollectionGetter obj = new CollectionCtorCollectionGetter(List.of(new StaplerOnlyItem("A"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(CollectionCtorCollectionGetter.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + + @SuppressWarnings("ClassCanBeRecord") + public static class CollectionCtorArrayGetter { + private final List items; + + @DataBoundConstructor + public CollectionCtorArrayGetter(List items) { + this.items = items; + } + + @SuppressWarnings("unused") + public StaplerOnlyItem[] getItems() { + return items.toArray(new StaplerOnlyItem[0]); + } + } + + @Test + void describe_convertsArrayValueWhenCtorIsCollection() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + CollectionCtorArrayGetter obj = new CollectionCtorArrayGetter(List.of(new StaplerOnlyItem("A"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(CollectionCtorArrayGetter.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + + Mapping mapping = (Mapping) node; + assertEquals(1, mapping.get("items").asSequence().size()); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + + @SuppressWarnings("ClassCanBeRecord") + public static class ArrayCtorCollectionGetter { + private final String[] items; + + @DataBoundConstructor + public ArrayCtorCollectionGetter(String[] items) { + this.items = items; + } + + @SuppressWarnings("unused") + public List getItems() { + return Arrays.asList(items); + } + } + + @Test + void describe_convertsCollectionToArrayWhenCtorIsArray() throws Exception { + ArrayCtorCollectionGetter obj = new ArrayCtorCollectionGetter(new String[] {"A", "B"}); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(ArrayCtorCollectionGetter.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + assertInstanceOf(Mapping.class, node); + + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } + + @SuppressWarnings("ClassCanBeRecord") + public static class SetCtorListGetter { + @SuppressWarnings({"unused", "FieldCanBeLocal"}) + private final Set items; + + @DataBoundConstructor + public SetCtorListGetter(Set items) { + this.items = items; + } + + @SuppressWarnings("unused") + public Collection getItems() { + return Arrays.asList("A", "B"); + } + } + + @Test + void describe_convertsCollectionToHashSetWhenCtorIsSet() throws Exception { + SetCtorListGetter obj = new SetCtorListGetter(Set.of("ignored")); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(SetCtorListGetter.class).describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + assertInstanceOf(Mapping.class, node); + + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } } From 7873454e0dc2e87a787ce3d0e56659d27371c70f Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Mon, 23 Feb 2026 01:49:33 +0530 Subject: [PATCH 13/16] Added more tests --- .../DataBoundConfiguratorTest.java | 237 ++++++------------ 1 file changed, 83 insertions(+), 154 deletions(-) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 2d59782781..2881e6c407 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -519,38 +519,6 @@ void exportWithCustomConverterIteratesOverSet() throws Exception { assertTrue(yaml.contains("value: \"B\"")); } - @SuppressWarnings("ClassCanBeRecord") - public static class CustomItemArrayHolder { - private final CustomItem[] items; - - @DataBoundConstructor - public CustomItemArrayHolder(CustomItem[] items) { - this.items = items; - } - - @SuppressWarnings("unused") - public CustomItem[] getItems() { - return items; - } - } - - @Test - void exportWithCustomConverterIteratesOverArray() throws Exception { - CustomItem[] array = {new CustomItem("A"), new CustomItem("B")}; - - CustomItemArrayHolder holder = new CustomItemArrayHolder(array); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - Configurator c = registry.lookupOrFail(CustomItemArrayHolder.class); - - CNode node = c.describe(holder, new ConfigurationContext(registry)); - Mapping map = (Mapping) node; - - assertEquals( - "- value: \"A\"\n- value: \"B\"", - Util.toYamlString(Objects.requireNonNull(map).get("items")).trim()); - } - @Test void exportWithSingleElementList() throws Exception { CustomItemListHolder holder = new CustomItemListHolder(List.of(new CustomItem("A"))); @@ -703,37 +671,6 @@ void shouldConvertListItemsUsingStaplerConverter() { } } - public static class ArrayMismatch { - private final String[] items; - - @DataBoundConstructor - public ArrayMismatch(String[] items) { - this.items = items; - } - - @SuppressWarnings("unused") - public List getItems() { - return items == null ? null : Arrays.asList(items); - } - } - - @Test - public void describe_shouldHandleArrayConstructorWithListGetter() throws Exception { - DataBoundConfigurator configurator = new DataBoundConfigurator<>(ArrayMismatch.class); - ArrayMismatch instance = new ArrayMismatch(new String[] {"value1", "value2"}); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - ConfigurationContext context = new ConfigurationContext(registry); - - CNode node = configurator.describe(instance, context); - - assertNotNull(node); - assertInstanceOf(Mapping.class, node); - Mapping mapping = (Mapping) node; - assertTrue(mapping.containsKey("items")); - assertEquals(2, mapping.get("items").asSequence().size()); - } - @SuppressWarnings("ClassCanBeRecord") public static class ListGetterMultiCtor { private final List items; @@ -765,66 +702,6 @@ void describe_iteratesCollectionAndConvertsEachItem() throws Exception { } } - @SuppressWarnings("ClassCanBeRecord") - public static class ArrayGetterMultiCtor { - private final StaplerOnlyItem[] items; - - @DataBoundConstructor - public ArrayGetterMultiCtor(StaplerOnlyItem[] items) { - this.items = items; - } - - @SuppressWarnings("unused") - public StaplerOnlyItem[] getItems() { - return items; - } - } - - @Test - void describe_iteratesArrayAndConvertsEachItem() throws Exception { - Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); - try { - StaplerOnlyItem[] array = new StaplerOnlyItem[] {new StaplerOnlyItem("A")}; - ArrayGetterMultiCtor obj = new ArrayGetterMultiCtor(array); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = - registry.lookupOrFail(ArrayGetterMultiCtor.class).describe(obj, new ConfigurationContext(registry)); - - assertNotNull(node); - } finally { - Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); - } - } - - public static class ListGetterArrayCtor { - private final String[] items; - - @DataBoundConstructor - public ListGetterArrayCtor(String[] items) { - this.items = items; - } - - @SuppressWarnings("unused") - public List getItems() { - return Arrays.asList(items); - } - } - - @Test - void describe_convertsCollectionToArrayForConstructor() throws Exception { - ListGetterArrayCtor obj = new ListGetterArrayCtor(new String[] {"A", "B"}); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(ListGetterArrayCtor.class).describe(obj, new ConfigurationContext(registry)); - - assertNotNull(node); - assertInstanceOf(Mapping.class, node); - - Mapping mapping = (Mapping) node; - assertEquals(2, mapping.get("items").asSequence().size()); - } - public static class ListGetterSetCtor { @DataBoundConstructor @@ -853,37 +730,6 @@ void describe_convertsCollectionToSetForConstructor() throws Exception { assertEquals(2, mapping.get("items").asSequence().size()); } - @SuppressWarnings("ClassCanBeRecord") - public static class CollectionCtorCollectionGetter { - private final List items; - - @DataBoundConstructor - public CollectionCtorCollectionGetter(List items) { - this.items = items; - } - - @SuppressWarnings("unused") - public List getItems() { - return items; - } - } - - @Test - void describe_convertsCollectionWhenCtorIsMultiple() throws Exception { - Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); - try { - CollectionCtorCollectionGetter obj = new CollectionCtorCollectionGetter(List.of(new StaplerOnlyItem("A"))); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(CollectionCtorCollectionGetter.class) - .describe(obj, new ConfigurationContext(registry)); - - assertNotNull(node); - } finally { - Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); - } - } - @SuppressWarnings("ClassCanBeRecord") public static class CollectionCtorArrayGetter { private final List items; @@ -977,4 +823,87 @@ void describe_convertsCollectionToHashSetWhenCtorIsSet() throws Exception { Mapping mapping = (Mapping) node; assertEquals(2, mapping.get("items").asSequence().size()); } + + @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) + public static class SetCtorListGetterMismatch { + private final Set items; + + @DataBoundConstructor + public SetCtorListGetterMismatch(Set items) { + this.items = items; + } + + @SuppressWarnings("unused") + public List getItems() { + return List.of("A", "B"); + } + } + + @Test + void describe_hits_set_rewrap_branch() throws Exception { + SetCtorListGetterMismatch obj = new SetCtorListGetterMismatch(Set.of("ignored")); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(SetCtorListGetterMismatch.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + assertInstanceOf(Mapping.class, node); + + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } + + @SuppressWarnings("ClassCanBeRecord") + public static class CollectionGetterMultiCtor { + private final List items; + + @DataBoundConstructor + public CollectionGetterMultiCtor(List items) { + this.items = items; + } + + @SuppressWarnings("unused") + public Collection getItems() { + return items; + } + } + + @Test + void describe_iteratesCollectionAndConvertsEachItem_usingCollectionGetter() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + CollectionGetterMultiCtor obj = + new CollectionGetterMultiCtor(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(CollectionGetterMultiCtor.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + + @Test + void describe_iteratesArrayAndConvertsEachItem() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + CollectionCtorArrayGetter obj = + new CollectionCtorArrayGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(CollectionCtorArrayGetter.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } } From ddfe52843e43375aafb9df4bfc3ed30ed2b2a2f2 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Mon, 23 Feb 2026 02:40:25 +0530 Subject: [PATCH 14/16] Added more tests --- .../DataBoundConfiguratorTest.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 2881e6c407..5beb681b5e 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -906,4 +906,104 @@ void describe_iteratesArrayAndConvertsEachItem() throws Exception { Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); } } + + @SuppressWarnings("ClassCanBeRecord") + public static class ListCtorCollectionGetter { + private final List items; + + @DataBoundConstructor + public ListCtorCollectionGetter(List items) { + this.items = items; + } + + @SuppressWarnings("unused") + public Collection getItems() { + return items; + } + } + + @Test + void describe_hits_collection_conversion_loop_exactly() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + ListCtorCollectionGetter obj = + new ListCtorCollectionGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(ListCtorCollectionGetter.class) + .describe(obj, new ConfigurationContext(registry)); + + Mapping mapping = (Mapping) node; + assertEquals( + 2, Objects.requireNonNull(mapping).get("items").asSequence().size()); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + + @SuppressWarnings("ClassCanBeRecord") + public static class ListCtorArrayGetter { + private final List items; + + @DataBoundConstructor + public ListCtorArrayGetter(List items) { + this.items = items; + } + + @SuppressWarnings("unused") + public StaplerOnlyItem[] getItems() { + return items.toArray(new StaplerOnlyItem[0]); + } + } + + @Test + void describe_hits_array_conversion_loop_exactly() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + ListCtorArrayGetter obj = + new ListCtorArrayGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = + registry.lookupOrFail(ListCtorArrayGetter.class).describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + + @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) + public static class SetCtorCollectionGetter { + private final Set items; + + @DataBoundConstructor + public SetCtorCollectionGetter(Set items) { + this.items = items; + } + + public Collection getItems() { + return List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B")); + } + } + + @Test + void describe_hits_set_constructor_collection_rewrap() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + SetCtorCollectionGetter obj = new SetCtorCollectionGetter(Set.of(new StaplerOnlyItem("ignored"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(SetCtorCollectionGetter.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } } From f323bdb68145465c0a3daf9206b20ab421bbe7de Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Mon, 23 Feb 2026 14:53:39 +0530 Subject: [PATCH 15/16] Deleted some duplicacy --- .../DataBoundConfiguratorTest.java | 182 +++++++++++++----- 1 file changed, 138 insertions(+), 44 deletions(-) diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 5beb681b5e..09176e4d72 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -38,6 +38,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.ParametersAreNonnullByDefault; @@ -854,12 +855,31 @@ void describe_hits_set_rewrap_branch() throws Exception { assertEquals(2, mapping.get("items").asSequence().size()); } + @Test + void describe_iteratesArrayAndConvertsEachItem() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + CollectionCtorArrayGetter obj = + new CollectionCtorArrayGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + CNode node = registry.lookupOrFail(CollectionCtorArrayGetter.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node); + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + @SuppressWarnings("ClassCanBeRecord") - public static class CollectionGetterMultiCtor { + public static class ListCtorCollectionGetter { private final List items; @DataBoundConstructor - public CollectionGetterMultiCtor(List items) { + public ListCtorCollectionGetter(List items) { this.items = items; } @@ -870,33 +890,46 @@ public Collection getItems() { } @Test - void describe_iteratesCollectionAndConvertsEachItem_usingCollectionGetter() throws Exception { + void describe_hits_collection_conversion_loop_exactly() throws Exception { Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); try { - CollectionGetterMultiCtor obj = - new CollectionGetterMultiCtor(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + ListCtorCollectionGetter obj = + new ListCtorCollectionGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(CollectionGetterMultiCtor.class) + CNode node = registry.lookupOrFail(ListCtorCollectionGetter.class) .describe(obj, new ConfigurationContext(registry)); - assertNotNull(node); Mapping mapping = (Mapping) node; - assertEquals(2, mapping.get("items").asSequence().size()); + assertEquals( + 2, Objects.requireNonNull(mapping).get("items").asSequence().size()); } finally { Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); } } + @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) + public static class SetCtorCollectionGetter { + private final Set items; + + @DataBoundConstructor + public SetCtorCollectionGetter(Set items) { + this.items = items; + } + + public Collection getItems() { + return List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B")); + } + } + @Test - void describe_iteratesArrayAndConvertsEachItem() throws Exception { + void describe_hits_set_constructor_collection_rewrap() throws Exception { Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); try { - CollectionCtorArrayGetter obj = - new CollectionCtorArrayGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + SetCtorCollectionGetter obj = new SetCtorCollectionGetter(Set.of(new StaplerOnlyItem("ignored"))); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(CollectionCtorArrayGetter.class) + CNode node = registry.lookupOrFail(SetCtorCollectionGetter.class) .describe(obj, new ConfigurationContext(registry)); assertNotNull(node); @@ -907,101 +940,162 @@ void describe_iteratesArrayAndConvertsEachItem() throws Exception { } } - @SuppressWarnings("ClassCanBeRecord") - public static class ListCtorCollectionGetter { + @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) + public static class ListCtorCollectionGetterFromStrings { private final List items; @DataBoundConstructor - public ListCtorCollectionGetter(List items) { + public ListCtorCollectionGetterFromStrings(List items) { this.items = items; } - @SuppressWarnings("unused") - public Collection getItems() { - return items; + public Collection getItems() { + return List.of("A", "B"); } } @Test - void describe_hits_collection_conversion_loop_exactly() throws Exception { + void describe_hits_collection_conversion_loop_from_collection_getter() throws Exception { Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); try { - ListCtorCollectionGetter obj = - new ListCtorCollectionGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + ListCtorCollectionGetterFromStrings obj = + new ListCtorCollectionGetterFromStrings( + List.of(new StaplerOnlyItem("ignored")) + ); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(ListCtorCollectionGetter.class) + CNode node = + registry.lookupOrFail(ListCtorCollectionGetterFromStrings.class) .describe(obj, new ConfigurationContext(registry)); + assertNotNull(node); Mapping mapping = (Mapping) node; - assertEquals( - 2, Objects.requireNonNull(mapping).get("items").asSequence().size()); + + assertEquals(2, mapping.get("items").asSequence().size()); + } finally { Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); } } - @SuppressWarnings("ClassCanBeRecord") - public static class ListCtorArrayGetter { + @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) + public static class ListCtorArrayGetterFromStrings { private final List items; @DataBoundConstructor - public ListCtorArrayGetter(List items) { + public ListCtorArrayGetterFromStrings(List items) { this.items = items; } - @SuppressWarnings("unused") - public StaplerOnlyItem[] getItems() { - return items.toArray(new StaplerOnlyItem[0]); + public String[] getItems() { + return new String[] {"X", "Y"}; } } @Test - void describe_hits_array_conversion_loop_exactly() throws Exception { + void describe_hits_array_conversion_loop_from_array_getter() throws Exception { Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); try { - ListCtorArrayGetter obj = - new ListCtorArrayGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); + ListCtorArrayGetterFromStrings obj = + new ListCtorArrayGetterFromStrings( + List.of(new StaplerOnlyItem("ignored")) + ); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); CNode node = - registry.lookupOrFail(ListCtorArrayGetter.class).describe(obj, new ConfigurationContext(registry)); + registry.lookupOrFail(ListCtorArrayGetterFromStrings.class) + .describe(obj, new ConfigurationContext(registry)); assertNotNull(node); Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size()); + } finally { Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); } } @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) - public static class SetCtorCollectionGetter { - private final Set items; + public static class ArrayCtorArrayGetterMismatch { + private final StaplerOnlyItem[] items; @DataBoundConstructor - public SetCtorCollectionGetter(Set items) { + public ArrayCtorArrayGetterMismatch(StaplerOnlyItem[] items) { this.items = items; } - public Collection getItems() { - return List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B")); + public String[] getItems() { + return new String[] { "A", "B" }; } } @Test - void describe_hits_set_constructor_collection_rewrap() throws Exception { - Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + void describe_converts_array_items_from_strings_using_stapler_converter_array_ctor_mismatch() throws Exception { + final AtomicInteger convertCount = new AtomicInteger(0); + + Converter converter = new Converter() { + @Override + public T convert(Class type, Object value) { + convertCount.incrementAndGet(); + if (value == null) { + return null; + } + return type.cast(new StaplerOnlyItem("converted-by-stapler-" + value)); + } + }; + + Stapler.CONVERT_UTILS.register(converter, StaplerOnlyItem.class); try { - SetCtorCollectionGetter obj = new SetCtorCollectionGetter(Set.of(new StaplerOnlyItem("ignored"))); + ArrayCtorArrayGetterMismatch obj = new ArrayCtorArrayGetterMismatch(new StaplerOnlyItem[] { new StaplerOnlyItem("ignored") }); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(SetCtorCollectionGetter.class) - .describe(obj, new ConfigurationContext(registry)); + CNode node = registry.lookupOrFail(ArrayCtorArrayGetterMismatch.class).describe(obj, new ConfigurationContext(registry)); assertNotNull(node); Mapping mapping = (Mapping) node; assertEquals(2, mapping.get("items").asSequence().size()); + + assertTrue(convertCount.get() >= 2, "Stapler converter should have been called at least twice"); + + } finally { + Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); + } + } + + @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) + public static class SetCtorStaplerOnlyItemHolder { + private final Set items; + + @DataBoundConstructor + public SetCtorStaplerOnlyItemHolder(Set items) { + this.items = items; + } + + public Set getItems() { + return items; + } + } + + @Test + void describe_converts_collection_to_set_via_stapler_converter() throws Exception { + Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); + try { + SetCtorStaplerOnlyItemHolder obj = new SetCtorStaplerOnlyItemHolder( + Set.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B")) + ); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + + CNode node = registry.lookupOrFail(SetCtorStaplerOnlyItemHolder.class) + .describe(obj, new ConfigurationContext(registry)); + + assertNotNull(node, "configured instance should not be null"); + assertInstanceOf(Mapping.class, node); + + Mapping mapping = (Mapping) node; + assertEquals(2, mapping.get("items").asSequence().size(), "items set should contain 2 elements"); + } finally { Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); } From 8c91784b4ca6472921d5029b80214182555220e2 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Tue, 24 Feb 2026 01:23:06 +0530 Subject: [PATCH 16/16] Remove dead codes --- .../configurators/DataBoundConfigurator.java | 19 +- .../DataBoundConfiguratorTest.java | 256 +++--------------- 2 files changed, 36 insertions(+), 239 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java b/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java index 201312bbb8..ead17eaff8 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfigurator.java @@ -303,24 +303,7 @@ public CNode describe(T instance, ConfigurationContext context) throws Exception Object value = a.getValue(instance); if (value != null) { Class targetType = a.getType(); - Object converted; - - if (a.isMultiple() && value instanceof Collection) { - List list = new ArrayList<>(); - for (Object o : (Collection) value) { - list.add(Stapler.CONVERT_UTILS.convert(o, targetType)); - } - converted = list; - } else if (a.isMultiple() && value.getClass().isArray()) { - List list = new ArrayList<>(); - int len = Array.getLength(value); - for (int j = 0; j < len; j++) { - list.add(Stapler.CONVERT_UTILS.convert(Array.get(value, j), targetType)); - } - converted = list; - } else { - converted = Stapler.CONVERT_UTILS.convert(value, targetType); - } + Object converted = Stapler.CONVERT_UTILS.convert(value, targetType); if (p.getType().isArray() && converted instanceof Collection col) { Class component = p.getType().getComponentType(); diff --git a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java index 09176e4d72..fe83a3ab61 100644 --- a/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java +++ b/test-harness/src/test/java/io/jenkins/plugins/casc/impl/configurators/DataBoundConfiguratorTest.java @@ -33,7 +33,6 @@ import java.io.StringWriter; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -672,37 +671,6 @@ void shouldConvertListItemsUsingStaplerConverter() { } } - @SuppressWarnings("ClassCanBeRecord") - public static class ListGetterMultiCtor { - private final List items; - - @DataBoundConstructor - public ListGetterMultiCtor(List items) { - this.items = items; - } - - @SuppressWarnings("unused") - public List getItems() { - return items; - } - } - - @Test - void describe_iteratesCollectionAndConvertsEachItem() throws Exception { - Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); - try { - ListGetterMultiCtor obj = new ListGetterMultiCtor(Collections.singletonList(new StaplerOnlyItem("A"))); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = - registry.lookupOrFail(ListGetterMultiCtor.class).describe(obj, new ConfigurationContext(registry)); - - assertNotNull(node); - } finally { - Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); - } - } - public static class ListGetterSetCtor { @DataBoundConstructor @@ -765,36 +733,6 @@ void describe_convertsArrayValueWhenCtorIsCollection() throws Exception { } } - @SuppressWarnings("ClassCanBeRecord") - public static class ArrayCtorCollectionGetter { - private final String[] items; - - @DataBoundConstructor - public ArrayCtorCollectionGetter(String[] items) { - this.items = items; - } - - @SuppressWarnings("unused") - public List getItems() { - return Arrays.asList(items); - } - } - - @Test - void describe_convertsCollectionToArrayWhenCtorIsArray() throws Exception { - ArrayCtorCollectionGetter obj = new ArrayCtorCollectionGetter(new String[] {"A", "B"}); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(ArrayCtorCollectionGetter.class) - .describe(obj, new ConfigurationContext(registry)); - - assertNotNull(node); - assertInstanceOf(Mapping.class, node); - - Mapping mapping = (Mapping) node; - assertEquals(2, mapping.get("items").asSequence().size()); - } - @SuppressWarnings("ClassCanBeRecord") public static class SetCtorListGetter { @SuppressWarnings({"unused", "FieldCanBeLocal"}) @@ -874,142 +812,37 @@ void describe_iteratesArrayAndConvertsEachItem() throws Exception { } } - @SuppressWarnings("ClassCanBeRecord") - public static class ListCtorCollectionGetter { - private final List items; - - @DataBoundConstructor - public ListCtorCollectionGetter(List items) { - this.items = items; - } - - @SuppressWarnings("unused") - public Collection getItems() { - return items; - } - } - - @Test - void describe_hits_collection_conversion_loop_exactly() throws Exception { - Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); - try { - ListCtorCollectionGetter obj = - new ListCtorCollectionGetter(List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(ListCtorCollectionGetter.class) - .describe(obj, new ConfigurationContext(registry)); - - Mapping mapping = (Mapping) node; - assertEquals( - 2, Objects.requireNonNull(mapping).get("items").asSequence().size()); - } finally { - Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); - } - } - @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) - public static class SetCtorCollectionGetter { + public static class SetCtorStaplerOnlyItemHolder { private final Set items; @DataBoundConstructor - public SetCtorCollectionGetter(Set items) { + public SetCtorStaplerOnlyItemHolder(Set items) { this.items = items; } - public Collection getItems() { - return List.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B")); + public Set getItems() { + return items; } } @Test - void describe_hits_set_constructor_collection_rewrap() throws Exception { + void describe_converts_collection_to_set_via_stapler_converter() throws Exception { Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); try { - SetCtorCollectionGetter obj = new SetCtorCollectionGetter(Set.of(new StaplerOnlyItem("ignored"))); + SetCtorStaplerOnlyItemHolder obj = + new SetCtorStaplerOnlyItemHolder(Set.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(SetCtorCollectionGetter.class) - .describe(obj, new ConfigurationContext(registry)); - - assertNotNull(node); - Mapping mapping = (Mapping) node; - assertEquals(2, mapping.get("items").asSequence().size()); - } finally { - Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); - } - } - - @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) - public static class ListCtorCollectionGetterFromStrings { - private final List items; - - @DataBoundConstructor - public ListCtorCollectionGetterFromStrings(List items) { - this.items = items; - } - public Collection getItems() { - return List.of("A", "B"); - } - } - - @Test - void describe_hits_collection_conversion_loop_from_collection_getter() throws Exception { - Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); - try { - ListCtorCollectionGetterFromStrings obj = - new ListCtorCollectionGetterFromStrings( - List.of(new StaplerOnlyItem("ignored")) - ); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = - registry.lookupOrFail(ListCtorCollectionGetterFromStrings.class) + CNode node = registry.lookupOrFail(SetCtorStaplerOnlyItemHolder.class) .describe(obj, new ConfigurationContext(registry)); - assertNotNull(node); - Mapping mapping = (Mapping) node; - - assertEquals(2, mapping.get("items").asSequence().size()); - - } finally { - Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); - } - } - - @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) - public static class ListCtorArrayGetterFromStrings { - private final List items; - - @DataBoundConstructor - public ListCtorArrayGetterFromStrings(List items) { - this.items = items; - } - - public String[] getItems() { - return new String[] {"X", "Y"}; - } - } - - @Test - void describe_hits_array_conversion_loop_from_array_getter() throws Exception { - Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); - try { - ListCtorArrayGetterFromStrings obj = - new ListCtorArrayGetterFromStrings( - List.of(new StaplerOnlyItem("ignored")) - ); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = - registry.lookupOrFail(ListCtorArrayGetterFromStrings.class) - .describe(obj, new ConfigurationContext(registry)); + assertNotNull(node, "configured instance should not be null"); + assertInstanceOf(Mapping.class, node); - assertNotNull(node); Mapping mapping = (Mapping) node; - - assertEquals(2, mapping.get("items").asSequence().size()); + assertEquals(2, mapping.get("items").asSequence().size(), "items set should contain 2 elements"); } finally { Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); @@ -1017,21 +850,21 @@ void describe_hits_array_conversion_loop_from_array_getter() throws Exception { } @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) - public static class ArrayCtorArrayGetterMismatch { - private final StaplerOnlyItem[] items; + public static class SetCtorFromStrings { + private final Set items; @DataBoundConstructor - public ArrayCtorArrayGetterMismatch(StaplerOnlyItem[] items) { + public SetCtorFromStrings(Set items) { this.items = items; } - public String[] getItems() { - return new String[] { "A", "B" }; + public Collection getItems() { + return List.of("A", "B"); } } @Test - void describe_converts_array_items_from_strings_using_stapler_converter_array_ctor_mismatch() throws Exception { + void configure_converts_sequence_to_set_via_stapler_converter_for_constructor() throws Exception { final AtomicInteger convertCount = new AtomicInteger(0); Converter converter = new Converter() { @@ -1047,57 +880,38 @@ public T convert(Class type, Object value) { Stapler.CONVERT_UTILS.register(converter, StaplerOnlyItem.class); try { - ArrayCtorArrayGetterMismatch obj = new ArrayCtorArrayGetterMismatch(new StaplerOnlyItem[] { new StaplerOnlyItem("ignored") }); + Mapping config = new Mapping(); + Sequence seq = new Sequence(); + seq.add(new Scalar("A")); + seq.add(new Scalar("B")); + config.put("items", seq); ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(ArrayCtorArrayGetterMismatch.class).describe(obj, new ConfigurationContext(registry)); - assertNotNull(node); - Mapping mapping = (Mapping) node; - assertEquals(2, mapping.get("items").asSequence().size()); + SetCtorFromStrings configured = (SetCtorFromStrings) registry.lookupOrFail(SetCtorFromStrings.class) + .configure(config, new ConfigurationContext(registry)); - assertTrue(convertCount.get() >= 2, "Stapler converter should have been called at least twice"); + assertNotNull(configured); + assertNotNull(configured.getItems()); + assertEquals(2, configured.getItems().size()); + assertTrue(convertCount.get() >= 2, "Stapler converter should have been called once per sequence element"); } finally { Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); } } - @SuppressWarnings({"ClassCanBeRecord", "unused", "FieldCanBeLocal"}) - public static class SetCtorStaplerOnlyItemHolder { - private final Set items; - - @DataBoundConstructor - public SetCtorStaplerOnlyItemHolder(Set items) { - this.items = items; - } - - public Set getItems() { - return items; - } - } - @Test - void describe_converts_collection_to_set_via_stapler_converter() throws Exception { - Stapler.CONVERT_UTILS.register(new StaplerOnlyItemConverter(), StaplerOnlyItem.class); - try { - SetCtorStaplerOnlyItemHolder obj = new SetCtorStaplerOnlyItemHolder( - Set.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B")) - ); + void describe_hits_set_wrap_branch_when_stapler_returns_collection() throws Exception { + SetCtorStaplerOnlyItemHolder obj = + new SetCtorStaplerOnlyItemHolder(Set.of(new StaplerOnlyItem("A"), new StaplerOnlyItem("B"))); - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - CNode node = registry.lookupOrFail(SetCtorStaplerOnlyItemHolder.class) + CNode node = registry.lookupOrFail(SetCtorStaplerOnlyItemHolder.class) .describe(obj, new ConfigurationContext(registry)); - assertNotNull(node, "configured instance should not be null"); - assertInstanceOf(Mapping.class, node); - - Mapping mapping = (Mapping) node; - assertEquals(2, mapping.get("items").asSequence().size(), "items set should contain 2 elements"); - - } finally { - Stapler.CONVERT_UTILS.deregister(StaplerOnlyItem.class); - } + assertNotNull(node); + assertInstanceOf(Mapping.class, node); } }