diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 6f10582645..1df4ccea2a 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -52,6 +52,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -67,6 +68,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.labkey.test.BaseWebDriverTest.ALL_ILLEGAL_QUERY_KEY_CHARACTERS; import static org.labkey.test.util.data.TestDataUtils.REALISTIC_ASSAY_FIELDS; @@ -188,7 +190,10 @@ else if (fieldDefinition.getType().equals(FieldDefinition.ColumnType.MultiValueT { FieldDefinition.TextChoiceValidator validator = (FieldDefinition.TextChoiceValidator) fieldDefinition.getValidators().getFirst(); - List values = shuffleSelect(validator.getValues()); + // Use i + 1 as a bitmask so each row gets a deterministic, non-empty subset of choices, + // consistent with how other field types (Integer, Boolean, TextChoice) use i for predictable data. + // Salt with queryName hash so different entity types produce distinct MVTC values at the same row index. + List values = bitmaskSelect(validator.getValues(), i + 1, queryName.hashCode()); entityData.put(key, values); } } @@ -1000,6 +1005,23 @@ public static List shuffleSelect(List allFields, int selectCount) return shuffled.subList(0, selectCount); } + /** + * Selects elements by index using {@code bitmask}: bit N set → include element N. Supports up to 32 elements. + * XORs the bitmask with {@code salt} so that callers with the same bitmask (e.g. same row index) produce + * different selections. + */ + public static List bitmaskSelect(List allElements, int bitmask, int salt) + { + int n = allElements.size(); + int validMask = n < 32 ? (1 << n) - 1 : Integer.MAX_VALUE; + int effective = (bitmask ^ salt) & validMask; + if (effective == 0) + effective = bitmask & validMask; + return BitSet.valueOf(new long[]{effective}).stream() + .mapToObj(allElements::get) + .collect(Collectors.toList()); + } + public static List randomSelect(List allOptions, int selectCount) { List selected = new ArrayList<>();