diff --git a/core-api/src/main/java/com/optimizely/ab/config/Cmab.java b/core-api/src/main/java/com/optimizely/ab/config/Cmab.java
new file mode 100644
index 000000000..738864e58
--- /dev/null
+++ b/core-api/src/main/java/com/optimizely/ab/config/Cmab.java
@@ -0,0 +1,72 @@
+/**
+ *
+ * Copyright 2025 Optimizely and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.optimizely.ab.config;
+
+import java.util.List;
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * Represents the Optimizely Traffic Allocation configuration.
+ *
+ * @see Project JSON
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Cmab {
+
+ private final List attributeIds;
+ private final int trafficAllocation;
+
+ @JsonCreator
+ public Cmab(@JsonProperty("attributeIds") List attributeIds,
+ @JsonProperty("trafficAllocation") int trafficAllocation) {
+ this.attributeIds = attributeIds;
+ this.trafficAllocation = trafficAllocation;
+ }
+
+ public List getAttributeIds() {
+ return attributeIds;
+ }
+
+ public int getTrafficAllocation() {
+ return trafficAllocation;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ Cmab cmab = (Cmab) obj;
+ return trafficAllocation == cmab.trafficAllocation &&
+ Objects.equals(attributeIds, cmab.attributeIds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(attributeIds, trafficAllocation);
+ }
+
+ @Override
+ public String toString() {
+ return "Cmab{" +
+ "attributeIds=" + attributeIds +
+ ", trafficAllocation=" + trafficAllocation +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/core-api/src/main/java/com/optimizely/ab/config/Experiment.java b/core-api/src/main/java/com/optimizely/ab/config/Experiment.java
index 4201d7db7..7d687e9e9 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/Experiment.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/Experiment.java
@@ -41,6 +41,7 @@ public class Experiment implements ExperimentCore {
private final String status;
private final String layerId;
private final String groupId;
+ private final Cmab cmab;
private final List audienceIds;
private final Condition audienceConditions;
@@ -71,7 +72,25 @@ public String toString() {
@VisibleForTesting
public Experiment(String id, String key, String layerId) {
- this(id, key, null, layerId, Collections.emptyList(), null, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), "");
+ this(id, key, null, layerId, Collections.emptyList(), null, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), "", null);
+ }
+
+ @VisibleForTesting
+ public Experiment(String id, String key, String status, String layerId,
+ List audienceIds, Condition audienceConditions,
+ List variations, Map userIdToVariationKeyMap,
+ List trafficAllocation, String groupId) {
+ this(id, key, status, layerId, audienceIds, audienceConditions, variations,
+ userIdToVariationKeyMap, trafficAllocation, groupId, null); // Default cmab=null
+ }
+
+ @VisibleForTesting
+ public Experiment(String id, String key, String status, String layerId,
+ List audienceIds, Condition audienceConditions,
+ List variations, Map userIdToVariationKeyMap,
+ List trafficAllocation) {
+ this(id, key, status, layerId, audienceIds, audienceConditions, variations,
+ userIdToVariationKeyMap, trafficAllocation, "", null); // Default groupId="" and cmab=null
}
@JsonCreator
@@ -83,8 +102,9 @@ public Experiment(@JsonProperty("id") String id,
@JsonProperty("audienceConditions") Condition audienceConditions,
@JsonProperty("variations") List variations,
@JsonProperty("forcedVariations") Map userIdToVariationKeyMap,
- @JsonProperty("trafficAllocation") List trafficAllocation) {
- this(id, key, status, layerId, audienceIds, audienceConditions, variations, userIdToVariationKeyMap, trafficAllocation, "");
+ @JsonProperty("trafficAllocation") List trafficAllocation,
+ @JsonProperty("cmab") Cmab cmab) {
+ this(id, key, status, layerId, audienceIds, audienceConditions, variations, userIdToVariationKeyMap, trafficAllocation, "", cmab);
}
public Experiment(@Nonnull String id,
@@ -96,7 +116,8 @@ public Experiment(@Nonnull String id,
@Nonnull List variations,
@Nonnull Map userIdToVariationKeyMap,
@Nonnull List trafficAllocation,
- @Nonnull String groupId) {
+ @Nonnull String groupId,
+ @Nullable Cmab cmab) {
this.id = id;
this.key = key;
this.status = status == null ? ExperimentStatus.NOT_STARTED.toString() : status;
@@ -109,6 +130,7 @@ public Experiment(@Nonnull String id,
this.userIdToVariationKeyMap = userIdToVariationKeyMap;
this.variationKeyToVariationMap = ProjectConfigUtils.generateNameMapping(variations);
this.variationIdToVariationMap = ProjectConfigUtils.generateIdMapping(variations);
+ this.cmab = cmab;
}
public String getId() {
@@ -159,6 +181,10 @@ public String getGroupId() {
return groupId;
}
+ public Cmab getCmab() {
+ return cmab;
+ }
+
public boolean isActive() {
return status.equals(ExperimentStatus.RUNNING.toString()) ||
status.equals(ExperimentStatus.LAUNCHED.toString());
@@ -185,6 +211,7 @@ public String toString() {
", variationKeyToVariationMap=" + variationKeyToVariationMap +
", userIdToVariationKeyMap=" + userIdToVariationKeyMap +
", trafficAllocation=" + trafficAllocation +
+ ", cmab=" + cmab +
'}';
}
}
diff --git a/core-api/src/main/java/com/optimizely/ab/config/Group.java b/core-api/src/main/java/com/optimizely/ab/config/Group.java
index afb068be4..d0d9ff364 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/Group.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/Group.java
@@ -62,7 +62,8 @@ public Group(@JsonProperty("id") String id,
experiment.getVariations(),
experiment.getUserIdToVariationKeyMap(),
experiment.getTrafficAllocation(),
- id
+ id,
+ experiment.getCmab()
);
}
this.experiments.add(experiment);
diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java b/core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java
index 97cf5b521..624f9f159 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java
@@ -24,15 +24,8 @@
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import com.optimizely.ab.bucketing.DecisionService;
-import com.optimizely.ab.config.Experiment;
-import com.optimizely.ab.config.Holdout;
+import com.optimizely.ab.config.*;
import com.optimizely.ab.config.Experiment.ExperimentStatus;
-import com.optimizely.ab.config.Holdout.HoldoutStatus;
-import com.optimizely.ab.config.FeatureFlag;
-import com.optimizely.ab.config.FeatureVariable;
-import com.optimizely.ab.config.FeatureVariableUsageInstance;
-import com.optimizely.ab.config.TrafficAllocation;
-import com.optimizely.ab.config.Variation;
import com.optimizely.ab.config.audience.AudienceIdCondition;
import com.optimizely.ab.config.audience.Condition;
import com.optimizely.ab.internal.ConditionUtils;
@@ -120,6 +113,27 @@ static Condition parseAudienceConditions(JsonObject experimentJson) {
}
+ static Cmab parseCmab(JsonObject cmabJson, JsonDeserializationContext context) {
+ if (cmabJson == null) {
+ return null;
+ }
+
+ JsonArray attributeIdsJson = cmabJson.getAsJsonArray("attributeIds");
+ List attributeIds = new ArrayList<>();
+ if (attributeIdsJson != null) {
+ for (JsonElement attributeIdElement : attributeIdsJson) {
+ attributeIds.add(attributeIdElement.getAsString());
+ }
+ }
+
+ int trafficAllocation = 0;
+ if (cmabJson.has("trafficAllocation")) {
+ trafficAllocation = cmabJson.get("trafficAllocation").getAsInt();
+ }
+
+ return new Cmab(attributeIds, trafficAllocation);
+ }
+
static Experiment parseExperiment(JsonObject experimentJson, String groupId, JsonDeserializationContext context) {
String id = experimentJson.get("id").getAsString();
String key = experimentJson.get("key").getAsString();
@@ -145,8 +159,17 @@ static Experiment parseExperiment(JsonObject experimentJson, String groupId, Jso
List trafficAllocations =
parseTrafficAllocation(experimentJson.getAsJsonArray("trafficAllocation"));
+ Cmab cmab = null;
+ if (experimentJson.has("cmab")) {
+ JsonElement cmabElement = experimentJson.get("cmab");
+ if (!cmabElement.isJsonNull()) {
+ JsonObject cmabJson = cmabElement.getAsJsonObject();
+ cmab = parseCmab(cmabJson, context);
+ }
+ }
+
return new Experiment(id, key, status, layerId, audienceIds, conditions, variations, userIdToVariationKeyMap,
- trafficAllocations, groupId);
+ trafficAllocations, groupId, cmab);
}
static Experiment parseExperiment(JsonObject experimentJson, JsonDeserializationContext context) {
diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java
index e3552f490..10ca9685f 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java
@@ -173,8 +173,14 @@ private List parseExperiments(JSONArray experimentJson, String group
List trafficAllocations =
parseTrafficAllocation(experimentObject.getJSONArray("trafficAllocation"));
+ Cmab cmab = null;
+ if (experimentObject.has("cmab")) {
+ JSONObject cmabObject = experimentObject.optJSONObject("cmab");
+ cmab = parseCmab(cmabObject);
+ }
+
experiments.add(new Experiment(id, key, status, layerId, audienceIds, conditions, variations, userIdToVariationKeyMap,
- trafficAllocations, groupId));
+ trafficAllocations, groupId, cmab));
}
return experiments;
@@ -332,6 +338,23 @@ private List parseTrafficAllocation(JSONArray trafficAllocati
return trafficAllocation;
}
+ private Cmab parseCmab(JSONObject cmabObject) {
+ if (cmabObject == null) {
+ return null;
+ }
+
+ JSONArray attributeIdsJson = cmabObject.optJSONArray("attributeIds");
+ List attributeIds = new ArrayList();
+ if (attributeIdsJson != null) {
+ for (int i = 0; i < attributeIdsJson.length(); i++) {
+ attributeIds.add(attributeIdsJson.getString(i));
+ }
+ }
+
+ int trafficAllocation = cmabObject.optInt("trafficAllocation", 0);
+ return new Cmab(attributeIds, trafficAllocation);
+ }
+
private List parseAttributes(JSONArray attributeJson) {
List attributes = new ArrayList(attributeJson.length());
diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java
index 419d59995..56215acc3 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java
@@ -180,8 +180,17 @@ private List parseExperiments(JSONArray experimentJson, String group
List trafficAllocations =
parseTrafficAllocation((JSONArray) experimentObject.get("trafficAllocation"));
- experiments.add(new Experiment(id, key, status, layerId, audienceIds, conditions, variations, userIdToVariationKeyMap,
- trafficAllocations, groupId));
+ // Add cmab parsing
+ Cmab cmab = null;
+ if (experimentObject.containsKey("cmab")) {
+ JSONObject cmabObject = (JSONObject) experimentObject.get("cmab");
+ if (cmabObject != null) {
+ cmab = parseCmab(cmabObject);
+ }
+ }
+
+ experiments.add(new Experiment(id, key, status, layerId, audienceIds, conditions, variations,
+ userIdToVariationKeyMap, trafficAllocations, groupId, cmab));
}
return experiments;
@@ -465,6 +474,26 @@ private List parseIntegrations(JSONArray integrationsJson) {
return integrations;
}
+ private Cmab parseCmab(JSONObject cmabObject) {
+ if (cmabObject == null) {
+ return null;
+ }
+
+ JSONArray attributeIdsJson = (JSONArray) cmabObject.get("attributeIds");
+ List attributeIds = new ArrayList<>();
+ if (attributeIdsJson != null) {
+ for (Object idObj : attributeIdsJson) {
+ attributeIds.add((String) idObj);
+ }
+ }
+
+ Object trafficAllocationObj = cmabObject.get("trafficAllocation");
+ int trafficAllocation = trafficAllocationObj != null ?
+ ((Long) trafficAllocationObj).intValue() : 0;
+
+ return new Cmab(attributeIds, trafficAllocation);
+ }
+
@Override
public String toJson(Object src) {
return JSONValue.toJSONString(src);
diff --git a/core-api/src/test/java/com/optimizely/ab/cmab/CmabTest.java b/core-api/src/test/java/com/optimizely/ab/cmab/CmabTest.java
new file mode 100644
index 000000000..40f1340b7
--- /dev/null
+++ b/core-api/src/test/java/com/optimizely/ab/cmab/CmabTest.java
@@ -0,0 +1,176 @@
+/*
+ *
+ * Copyright 2025 Optimizely and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.optimizely.ab.cmab;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+import com.optimizely.ab.config.Cmab;
+
+/**
+ * Tests for {@link Cmab} configuration object.
+ */
+public class CmabTest {
+
+ @Test
+ public void testCmabConstructorWithValidData() {
+ List attributeIds = Arrays.asList("attr1", "attr2", "attr3");
+ int trafficAllocation = 4000;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+
+ assertEquals("AttributeIds should match", attributeIds, cmab.getAttributeIds());
+ assertEquals("TrafficAllocation should match", trafficAllocation, cmab.getTrafficAllocation());
+ }
+
+ @Test
+ public void testCmabConstructorWithEmptyAttributeIds() {
+ List attributeIds = Collections.emptyList();
+ int trafficAllocation = 2000;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+
+ assertEquals("AttributeIds should be empty", attributeIds, cmab.getAttributeIds());
+ assertTrue("AttributeIds should be empty list", cmab.getAttributeIds().isEmpty());
+ assertEquals("TrafficAllocation should match", trafficAllocation, cmab.getTrafficAllocation());
+ }
+
+ @Test
+ public void testCmabConstructorWithSingleAttributeId() {
+ List attributeIds = Collections.singletonList("single_attr");
+ int trafficAllocation = 3000;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+
+ assertEquals("AttributeIds should match", attributeIds, cmab.getAttributeIds());
+ assertEquals("Should have one attribute", 1, cmab.getAttributeIds().size());
+ assertEquals("Single attribute should match", "single_attr", cmab.getAttributeIds().get(0));
+ assertEquals("TrafficAllocation should match", trafficAllocation, cmab.getTrafficAllocation());
+ }
+
+ @Test
+ public void testCmabConstructorWithZeroTrafficAllocation() {
+ List attributeIds = Arrays.asList("attr1", "attr2");
+ int trafficAllocation = 0;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+
+ assertEquals("AttributeIds should match", attributeIds, cmab.getAttributeIds());
+ assertEquals("TrafficAllocation should be zero", 0, cmab.getTrafficAllocation());
+ }
+
+ @Test
+ public void testCmabConstructorWithMaxTrafficAllocation() {
+ List attributeIds = Arrays.asList("attr1");
+ int trafficAllocation = 10000;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+
+ assertEquals("AttributeIds should match", attributeIds, cmab.getAttributeIds());
+ assertEquals("TrafficAllocation should be 10000", 10000, cmab.getTrafficAllocation());
+ }
+
+ @Test
+ public void testCmabEqualsAndHashCode() {
+ List attributeIds1 = Arrays.asList("attr1", "attr2");
+ List attributeIds2 = Arrays.asList("attr1", "attr2");
+ List attributeIds3 = Arrays.asList("attr1", "attr3");
+
+ Cmab cmab1 = new Cmab(attributeIds1, 4000);
+ Cmab cmab2 = new Cmab(attributeIds2, 4000);
+ Cmab cmab3 = new Cmab(attributeIds3, 4000);
+ Cmab cmab4 = new Cmab(attributeIds1, 5000);
+
+ // Test equals
+ assertEquals("CMAB with same data should be equal", cmab1, cmab2);
+ assertNotEquals("CMAB with different attributeIds should not be equal", cmab1, cmab3);
+ assertNotEquals("CMAB with different trafficAllocation should not be equal", cmab1, cmab4);
+
+ // Test reflexivity
+ assertEquals("CMAB should equal itself", cmab1, cmab1);
+
+ // Test null comparison
+ assertNotEquals("CMAB should not equal null", cmab1, null);
+
+ // Test hashCode consistency
+ assertEquals("Equal objects should have same hashCode", cmab1.hashCode(), cmab2.hashCode());
+ }
+
+ @Test
+ public void testCmabToString() {
+ List attributeIds = Arrays.asList("attr1", "attr2");
+ int trafficAllocation = 4000;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+ String result = cmab.toString();
+
+ assertNotNull("toString should not return null", result);
+ assertTrue("toString should contain attributeIds", result.contains("attributeIds"));
+ assertTrue("toString should contain trafficAllocation", result.contains("trafficAllocation"));
+ assertTrue("toString should contain attr1", result.contains("attr1"));
+ assertTrue("toString should contain attr2", result.contains("attr2"));
+ assertTrue("toString should contain 4000", result.contains("4000"));
+ }
+
+ @Test
+ public void testCmabToStringWithEmptyAttributeIds() {
+ List attributeIds = Collections.emptyList();
+ int trafficAllocation = 2000;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+ String result = cmab.toString();
+
+ assertNotNull("toString should not return null", result);
+ assertTrue("toString should contain attributeIds", result.contains("attributeIds"));
+ assertTrue("toString should contain trafficAllocation", result.contains("trafficAllocation"));
+ assertTrue("toString should contain 2000", result.contains("2000"));
+ }
+
+ @Test
+ public void testCmabWithDuplicateAttributeIds() {
+ List attributeIds = Arrays.asList("attr1", "attr2", "attr1", "attr3");
+ int trafficAllocation = 4000;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+
+ assertEquals("AttributeIds should match exactly (including duplicates)",
+ attributeIds, cmab.getAttributeIds());
+ assertEquals("Should have 4 elements (including duplicate)", 4, cmab.getAttributeIds().size());
+ }
+
+ @Test
+ public void testCmabWithRealWorldAttributeIds() {
+ // Test with realistic attribute IDs from Optimizely
+ List attributeIds = Arrays.asList("808797688", "808797689", "10401066117");
+ int trafficAllocation = 4000;
+
+ Cmab cmab = new Cmab(attributeIds, trafficAllocation);
+
+ assertEquals("AttributeIds should match", attributeIds, cmab.getAttributeIds());
+ assertEquals("TrafficAllocation should match", trafficAllocation, cmab.getTrafficAllocation());
+ assertTrue("Should contain first attribute ID", cmab.getAttributeIds().contains("808797688"));
+ assertTrue("Should contain second attribute ID", cmab.getAttributeIds().contains("808797689"));
+ assertTrue("Should contain third attribute ID", cmab.getAttributeIds().contains("10401066117"));
+ }
+}
\ No newline at end of file
diff --git a/core-api/src/test/java/com/optimizely/ab/cmab/parser/CmabParsingTest.java b/core-api/src/test/java/com/optimizely/ab/cmab/parser/CmabParsingTest.java
new file mode 100644
index 000000000..4a6ed8f20
--- /dev/null
+++ b/core-api/src/test/java/com/optimizely/ab/cmab/parser/CmabParsingTest.java
@@ -0,0 +1,249 @@
+/**
+ *
+ * Copyright 2025 Optimizely and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.optimizely.ab.cmab.parser;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+import com.optimizely.ab.config.Cmab;
+import com.optimizely.ab.config.Experiment;
+import com.optimizely.ab.config.Group;
+import com.optimizely.ab.config.ProjectConfig;
+import com.optimizely.ab.config.parser.ConfigParseException;
+import com.optimizely.ab.config.parser.ConfigParser;
+import com.optimizely.ab.config.parser.GsonConfigParser;
+import com.optimizely.ab.config.parser.JacksonConfigParser;
+import com.optimizely.ab.config.parser.JsonConfigParser;
+import com.optimizely.ab.config.parser.JsonSimpleConfigParser;
+
+/**
+ * Tests CMAB parsing across all config parsers using real datafile
+ */
+@RunWith(Parameterized.class)
+public class CmabParsingTest {
+
+ @Parameterized.Parameters(name = "{index}: {0}")
+ public static Collection