Repository: gchq/sleeper
Issue: EMR instance types are invalid with the same weight for different types

### Description

In all the EMR instance types properties, weights for the different instance types are set in-line with the instance types.

A validation predicate has been added for those properties which ensures that duplicate instance types are not set, but it includes the weights in the duplication test. This means that you can't set the same weight for more than one instance type.

### Steps to reproduce

1. Open the admin client with EmrBulkImportStack deployed
2. Update table property `sleeper.table.bulk.import.emr.master.x86.instance.types` to `m5.xlarge,2,m6i.xlarge,2`
3. See the property change fails to validate

### Expected behaviour

We should be able to set the same weight for multiple instance types on any instance types property.

Changed files:
java/bulk-import/bulk-import-starter/src/main/java/sleeper/bulkimport/starter/executor/EmrInstanceFleets.java
java/configuration/src/main/java/sleeper/configuration/properties/table/TableProperty.java
java/configuration/src/main/java/sleeper/configuration/properties/instance/NonPersistentEMRProperty.java
java/configuration/src/main/java/sleeper/configuration/properties/instance/PersistentEMRProperty.java
java/configuration/src/main/java/sleeper/configuration/Utils.java
java/bulk-import/bulk-import-common/src/test/java/sleeper/bulkimport/configuration/EmrInstanceTypeConfigTest.java
java/bulk-import/bulk-import-common/src/main/java/sleeper/bulkimport/configuration/EmrInstanceTypeConfig.java
java/cdk/src/main/java/sleeper/cdk/stack/bulkimport/PersistentEmrBulkImportStack.java
java/configuration/src/test/java/sleeper/configuration/UtilsTest.java

diff --git a/java/bulk-import/bulk-import-starter/src/main/java/sleeper/bulkimport/starter/executor/EmrInstanceFleets.java b/java/bulk-import/bulk-import-starter/src/main/java/sleeper/bulkimport/starter/executor/EmrInstanceFleets.java
index 7577fbd3a..336074cda 100644
--- a/java/bulk-import/bulk-import-starter/src/main/java/sleeper/bulkimport/starter/executor/EmrInstanceFleets.java
+++ b/java/bulk-import/bulk-import-starter/src/main/java/sleeper/bulkimport/starter/executor/EmrInstanceFleets.java
@@ -35,7 +35,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
-import static sleeper.bulkimport.configuration.EmrInstanceTypeConfig.readInstanceTypes;
 import static sleeper.configuration.properties.instance.CommonProperty.SUBNETS;
 import static sleeper.configuration.properties.table.TableProperty.BULK_IMPORT_EMR_EXECUTOR_ARM_INSTANCE_TYPES;
 import static sleeper.configuration.properties.table.TableProperty.BULK_IMPORT_EMR_EXECUTOR_MARKET_TYPE;
@@ -45,6 +44,7 @@ import static sleeper.configuration.properties.table.TableProperty.BULK_IMPORT_E
 import static sleeper.configuration.properties.table.TableProperty.BULK_IMPORT_EMR_MASTER_ARM_INSTANCE_TYPES;
 import static sleeper.configuration.properties.table.TableProperty.BULK_IMPORT_EMR_MASTER_X86_INSTANCE_TYPES;
 import static sleeper.configuration.properties.table.TableProperty.BULK_IMPORT_EMR_MAX_EXECUTOR_CAPACITY;
+import static sleeper.configuration.properties.validation.EmrInstanceTypeConfig.readInstanceTypes;
 
 public class EmrInstanceFleets implements EmrInstanceConfiguration {
 
diff --git a/java/cdk/src/main/java/sleeper/cdk/stack/bulkimport/PersistentEmrBulkImportStack.java b/java/cdk/src/main/java/sleeper/cdk/stack/bulkimport/PersistentEmrBulkImportStack.java
index aef3b527b..91239410c 100644
--- a/java/cdk/src/main/java/sleeper/cdk/stack/bulkimport/PersistentEmrBulkImportStack.java
+++ b/java/cdk/src/main/java/sleeper/cdk/stack/bulkimport/PersistentEmrBulkImportStack.java
@@ -47,7 +47,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
-import static sleeper.bulkimport.configuration.EmrInstanceTypeConfig.readInstanceTypes;
 import static sleeper.configuration.properties.instance.CommonProperty.ID;
 import static sleeper.configuration.properties.instance.CommonProperty.SUBNETS;
 import static sleeper.configuration.properties.instance.EMRProperty.BULK_IMPORT_EMR_EBS_VOLUMES_PER_INSTANCE;
@@ -68,6 +67,7 @@ import static sleeper.configuration.properties.instance.PersistentEMRProperty.BU
 import static sleeper.configuration.properties.instance.SystemDefinedInstanceProperty.BULK_IMPORT_PERSISTENT_EMR_CLUSTER_NAME;
 import static sleeper.configuration.properties.instance.SystemDefinedInstanceProperty.BULK_IMPORT_PERSISTENT_EMR_JOB_QUEUE_URL;
 import static sleeper.configuration.properties.instance.SystemDefinedInstanceProperty.BULK_IMPORT_PERSISTENT_EMR_MASTER_DNS;
+import static sleeper.configuration.properties.validation.EmrInstanceTypeConfig.readInstanceTypes;
 
 
 /**
diff --git a/java/configuration/src/main/java/sleeper/configuration/Utils.java b/java/configuration/src/main/java/sleeper/configuration/Utils.java
index 7753af077..414e43523 100644
--- a/java/configuration/src/main/java/sleeper/configuration/Utils.java
+++ b/java/configuration/src/main/java/sleeper/configuration/Utils.java
@@ -22,7 +22,6 @@ import sleeper.configuration.properties.SleeperProperties;
 import sleeper.configuration.properties.table.CompressionCodec;
 import sleeper.configuration.properties.validation.EmrInstanceArchitecture;
 
-import java.util.List;
 import java.util.Set;
 import java.util.function.DoublePredicate;
 import java.util.function.IntPredicate;
@@ -158,14 +157,6 @@ public class Utils {
                 .allMatch(architecture -> EnumUtils.isValidEnumIgnoreCase(EmrInstanceArchitecture.class, architecture));
     }
 
-    public static boolean isUniqueList(String input) {
-        if (input == null) {
-            return false;
-        }
-        List<String> inputList = SleeperProperties.readList(input);
-        return inputList.stream().distinct().count() == inputList.size();
-    }
-
     private static boolean parseAndCheckInteger(String string, IntPredicate check) {
         try {
             return check.test(Integer.parseInt(string));
diff --git a/java/configuration/src/main/java/sleeper/configuration/properties/instance/NonPersistentEMRProperty.java b/java/configuration/src/main/java/sleeper/configuration/properties/instance/NonPersistentEMRProperty.java
index 70824e5e8..e1291bd52 100644
--- a/java/configuration/src/main/java/sleeper/configuration/properties/instance/NonPersistentEMRProperty.java
+++ b/java/configuration/src/main/java/sleeper/configuration/properties/instance/NonPersistentEMRProperty.java
@@ -19,6 +19,7 @@ package sleeper.configuration.properties.instance;
 
 import sleeper.configuration.Utils;
 import sleeper.configuration.properties.SleeperPropertyIndex;
+import sleeper.configuration.properties.validation.EmrInstanceTypeConfig;
 
 import java.util.List;
 
@@ -38,32 +39,32 @@ public interface NonPersistentEMRProperty {
             .validationPredicate(Utils::isValidArchitecture)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT).build();
     UserDefinedInstanceProperty DEFAULT_BULK_IMPORT_EMR_MASTER_X86_INSTANCE_TYPES = Index.propertyBuilder("sleeper.default.bulk.import.emr.master.x86.instance.types")
-            .description("(Non-persistent EMR mode only) The default EC2 x86 instance types to be used for the master " +
-                    "node of the EMR cluster. " +
+            .description("(Non-persistent EMR mode only) The default EC2 x86_64 instance types and weights to be " +
+                    "used for the master node of the EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .defaultValue("m6i.xlarge")
-            .validationPredicate(Utils::isUniqueList)
+            .validationPredicate(EmrInstanceTypeConfig::isValidInstanceTypes)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT).build();
     UserDefinedInstanceProperty DEFAULT_BULK_IMPORT_EMR_EXECUTOR_X86_INSTANCE_TYPES = Index.propertyBuilder("sleeper.default.bulk.import.emr.executor.x86.instance.types")
-            .description("(Non-persistent EMR mode only) The default EC2 x86_64 instance types to be used for the executor " +
-                    "nodes of the EMR cluster. " +
+            .description("(Non-persistent EMR mode only) The default EC2 x86_64 instance types and weights to be " +
+                    "used for the executor nodes of the EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .defaultValue("m6i.4xlarge")
-            .validationPredicate(Utils::isUniqueList)
+            .validationPredicate(EmrInstanceTypeConfig::isValidInstanceTypes)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT).build();
     UserDefinedInstanceProperty DEFAULT_BULK_IMPORT_EMR_MASTER_ARM_INSTANCE_TYPES = Index.propertyBuilder("sleeper.default.bulk.import.emr.master.arm.instance.types")
-            .description("(Non-persistent EMR mode only) The default EC2 ARM64 instance types to be used for the master " +
-                    "node of the EMR cluster. " +
+            .description("(Non-persistent EMR mode only) The default EC2 ARM64 instance types and weights to be used " +
+                    "for the master node of the EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .defaultValue("m6g.xlarge")
-            .validationPredicate(Utils::isUniqueList)
+            .validationPredicate(EmrInstanceTypeConfig::isValidInstanceTypes)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT).build();
     UserDefinedInstanceProperty DEFAULT_BULK_IMPORT_EMR_EXECUTOR_ARM_INSTANCE_TYPES = Index.propertyBuilder("sleeper.default.bulk.import.emr.executor.arm.instance.types")
-            .description("(Non-persistent EMR mode only) The default EC2 ARM64 instance types to be used for the executor " +
-                    "nodes of the EMR cluster. " +
+            .description("(Non-persistent EMR mode only) The default EC2 ARM64 instance types and weights to be used " +
+                    "for the executor nodes of the EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .defaultValue("m6g.4xlarge")
-            .validationPredicate(Utils::isUniqueList)
+            .validationPredicate(EmrInstanceTypeConfig::isValidInstanceTypes)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT).build();
     UserDefinedInstanceProperty DEFAULT_BULK_IMPORT_EMR_EXECUTOR_MARKET_TYPE = Index.propertyBuilder("sleeper.default.bulk.import.emr.executor.market.type")
             .description("(Non-persistent EMR mode only) The default purchasing option to be used for the executor " +
diff --git a/java/configuration/src/main/java/sleeper/configuration/properties/instance/PersistentEMRProperty.java b/java/configuration/src/main/java/sleeper/configuration/properties/instance/PersistentEMRProperty.java
index c31c79c71..71e41cf29 100644
--- a/java/configuration/src/main/java/sleeper/configuration/properties/instance/PersistentEMRProperty.java
+++ b/java/configuration/src/main/java/sleeper/configuration/properties/instance/PersistentEMRProperty.java
@@ -19,6 +19,7 @@ package sleeper.configuration.properties.instance;
 
 import sleeper.configuration.Utils;
 import sleeper.configuration.properties.SleeperPropertyIndex;
+import sleeper.configuration.properties.validation.EmrInstanceTypeConfig;
 
 import java.util.List;
 
@@ -42,35 +43,35 @@ public interface PersistentEMRProperty {
             .validationPredicate(Utils::isValidArchitecture)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT).build();
     UserDefinedInstanceProperty BULK_IMPORT_PERSISTENT_EMR_MASTER_X86_INSTANCE_TYPES = Index.propertyBuilder("sleeper.bulk.import.persistent.emr.master.x86.instance.types")
-            .description("(Persistent EMR mode only) The EC2 x86 instance types used for the master node of the " +
-                    "persistent EMR cluster. " +
+            .description("(Persistent EMR mode only) The EC2 x86_64 instance types and weights used for the master " +
+                    "node of the persistent EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .defaultValue(DEFAULT_BULK_IMPORT_EMR_MASTER_X86_INSTANCE_TYPES.getDefaultValue())
-            .validationPredicate(Utils::isUniqueList)
+            .validationPredicate(EmrInstanceTypeConfig::isValidInstanceTypes)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT)
             .runCDKDeployWhenChanged(true).build();
     UserDefinedInstanceProperty BULK_IMPORT_PERSISTENT_EMR_EXECUTOR_X86_INSTANCE_TYPES = Index.propertyBuilder("sleeper.bulk.import.persistent.emr.executor.x86.instance.types")
-            .description("(Persistent EMR mode only) The EC2 x86 instance types used for the executor nodes of the " +
-                    "persistent EMR cluster. " +
+            .description("(Persistent EMR mode only) The EC2 x86_64 instance types and weights used for the executor " +
+                    "nodes of the persistent EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .defaultValue(DEFAULT_BULK_IMPORT_EMR_EXECUTOR_X86_INSTANCE_TYPES.getDefaultValue())
-            .validationPredicate(Utils::isUniqueList)
+            .validationPredicate(EmrInstanceTypeConfig::isValidInstanceTypes)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT)
             .runCDKDeployWhenChanged(true).build();
     UserDefinedInstanceProperty BULK_IMPORT_PERSISTENT_EMR_MASTER_ARM_INSTANCE_TYPES = Index.propertyBuilder("sleeper.bulk.import.persistent.emr.master.arm.instance.types")
-            .description("(Persistent EMR mode only) The EC2 ARM64 instance types used for the master node of the " +
-                    "persistent EMR cluster. " +
+            .description("(Persistent EMR mode only) The EC2 ARM64 instance types and weights used for the master " +
+                    "node of the persistent EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .defaultValue(DEFAULT_BULK_IMPORT_EMR_MASTER_ARM_INSTANCE_TYPES.getDefaultValue())
-            .validationPredicate(Utils::isUniqueList)
+            .validationPredicate(EmrInstanceTypeConfig::isValidInstanceTypes)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT)
             .runCDKDeployWhenChanged(true).build();
     UserDefinedInstanceProperty BULK_IMPORT_PERSISTENT_EMR_EXECUTOR_ARM_INSTANCE_TYPES = Index.propertyBuilder("sleeper.bulk.import.persistent.emr.executor.arm.instance.types")
-            .description("(Persistent EMR mode only) The EC2 ARM64 instance types used for the executor nodes of the " +
-                    "persistent EMR cluster. " +
+            .description("(Persistent EMR mode only) The EC2 ARM64 instance types and weights used for the executor " +
+                    "nodes of the persistent EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .defaultValue(DEFAULT_BULK_IMPORT_EMR_EXECUTOR_ARM_INSTANCE_TYPES.getDefaultValue())
-            .validationPredicate(Utils::isUniqueList)
+            .validationPredicate(EmrInstanceTypeConfig::isValidInstanceTypes)
             .propertyGroup(InstancePropertyGroup.BULK_IMPORT)
             .runCDKDeployWhenChanged(true).build();
     UserDefinedInstanceProperty BULK_IMPORT_PERSISTENT_EMR_USE_MANAGED_SCALING = Index.propertyBuilder("sleeper.bulk.import.persistent.emr.use.managed.scaling")
diff --git a/java/configuration/src/main/java/sleeper/configuration/properties/table/TableProperty.java b/java/configuration/src/main/java/sleeper/configuration/properties/table/TableProperty.java
index 46a2dc1e6..9812e7cd7 100644
--- a/java/configuration/src/main/java/sleeper/configuration/properties/table/TableProperty.java
+++ b/java/configuration/src/main/java/sleeper/configuration/properties/table/TableProperty.java
@@ -235,29 +235,29 @@ public interface TableProperty extends SleeperProperty {
             .build();
     TableProperty BULK_IMPORT_EMR_MASTER_X86_INSTANCE_TYPES = Index.propertyBuilder("sleeper.table.bulk.import.emr.master.x86.instance.types")
             .defaultProperty(DEFAULT_BULK_IMPORT_EMR_MASTER_X86_INSTANCE_TYPES)
-            .description("(Non-persistent EMR mode only) The EC2 x86_64 instance types to be used for the master node of the " +
-                    "EMR cluster. " +
+            .description("(Non-persistent EMR mode only) The EC2 x86_64 instance types and weights to be used for " +
+                    "the master node of the EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .propertyGroup(TablePropertyGroup.BULK_IMPORT)
             .build();
     TableProperty BULK_IMPORT_EMR_EXECUTOR_X86_INSTANCE_TYPES = Index.propertyBuilder("sleeper.table.bulk.import.emr.executor.x86.instance.types")
             .defaultProperty(DEFAULT_BULK_IMPORT_EMR_EXECUTOR_X86_INSTANCE_TYPES)
-            .description("(Non-persistent EMR mode only) The EC2 x86_64 instance types to be used for the executor nodes of " +
-                    "the EMR cluster. " +
+            .description("(Non-persistent EMR mode only) The EC2 x86_64 instance types and weights to be used for " +
+                    "the executor nodes of the EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .propertyGroup(TablePropertyGroup.BULK_IMPORT)
             .build();
     TableProperty BULK_IMPORT_EMR_MASTER_ARM_INSTANCE_TYPES = Index.propertyBuilder("sleeper.table.bulk.import.emr.master.arm.instance.types")
             .defaultProperty(DEFAULT_BULK_IMPORT_EMR_MASTER_ARM_INSTANCE_TYPES)
-            .description("(Non-persistent EMR mode only) The EC2 ARM64 instance types to be used for the master node of the " +
-                    "EMR cluster. " +
+            .description("(Non-persistent EMR mode only) The EC2 ARM64 instance types and weights to be used for the " +
+                    "master node of the EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .propertyGroup(TablePropertyGroup.BULK_IMPORT)
             .build();
     TableProperty BULK_IMPORT_EMR_EXECUTOR_ARM_INSTANCE_TYPES = Index.propertyBuilder("sleeper.table.bulk.import.emr.executor.arm.instance.types")
             .defaultProperty(DEFAULT_BULK_IMPORT_EMR_EXECUTOR_ARM_INSTANCE_TYPES)
-            .description("(Non-persistent EMR mode only) The EC2 ARM64 instance types to be used for the executor nodes of " +
-                    "the EMR cluster. " +
+            .description("(Non-persistent EMR mode only) The EC2 ARM64 instance types and weights to be used for the " +
+                    "executor nodes of the EMR cluster.\\n" +
                     "For more information, see the Bulk import using EMR - Instance types section in docs/05-ingest.md")
             .propertyGroup(TablePropertyGroup.BULK_IMPORT)
             .build();
diff --git a/java/bulk-import/bulk-import-common/src/main/java/sleeper/bulkimport/configuration/EmrInstanceTypeConfig.java b/java/configuration/src/main/java/sleeper/configuration/properties/validation/EmrInstanceTypeConfig.java
similarity index 87%
rename from java/bulk-import/bulk-import-common/src/main/java/sleeper/bulkimport/configuration/EmrInstanceTypeConfig.java
rename to java/configuration/src/main/java/sleeper/configuration/properties/validation/EmrInstanceTypeConfig.java
index 7a96ec920..73c852553 100644
--- a/java/bulk-import/bulk-import-common/src/main/java/sleeper/bulkimport/configuration/EmrInstanceTypeConfig.java
+++ b/java/configuration/src/main/java/sleeper/configuration/properties/validation/EmrInstanceTypeConfig.java
@@ -13,21 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package sleeper.bulkimport.configuration;
+package sleeper.configuration.properties.validation;
 
 import org.apache.commons.lang3.EnumUtils;
 
 import sleeper.configuration.properties.SleeperProperties;
 import sleeper.configuration.properties.instance.SleeperProperty;
-import sleeper.configuration.properties.validation.EmrInstanceArchitecture;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static sleeper.configuration.properties.SleeperProperties.readList;
 import static sleeper.configuration.properties.validation.EmrInstanceArchitecture.ARM64;
+import static sleeper.configuration.properties.validation.EmrInstanceArchitecture.X86_64;
 
 public class EmrInstanceTypeConfig {
     private final EmrInstanceArchitecture architecture;
@@ -72,6 +74,17 @@ public class EmrInstanceTypeConfig {
         return builders.stream().map(Builder::build);
     }
 
+    public static boolean isValidInstanceTypes(String value) {
+        try {
+            List<String> instanceTypes = readInstanceTypesProperty(readList(value), X86_64)
+                    .map(EmrInstanceTypeConfig::getInstanceType)
+                    .collect(Collectors.toUnmodifiableList());
+            return instanceTypes.size() == instanceTypes.stream().distinct().count();
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+
     public static Builder builder() {
         return new Builder();
     }
diff --git a/java/configuration/src/test/java/sleeper/configuration/UtilsTest.java b/java/configuration/src/test/java/sleeper/configuration/UtilsTest.java
index 1b2920d67..7d816dadd 100644
--- a/java/configuration/src/test/java/sleeper/configuration/UtilsTest.java
+++ b/java/configuration/src/test/java/sleeper/configuration/UtilsTest.java
@@ -24,22 +24,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 class UtilsTest {
 
-    @Nested
-    @DisplayName("Validate lists")
-    class ValidateLists {
-        @Test
-        void shouldValidateListWithUniqueElements() {
-            assertThat(Utils.isUniqueList("test-a,test-b,test-c"))
-                    .isTrue();
-        }
-
-        @Test
-        void shouldFailToValidateListWithDuplicates() {
-            assertThat(Utils.isUniqueList("test-a,test-b,test-a"))
-                    .isFalse();
-        }
-    }
-
     @Nested
     @DisplayName("Validate numbers")
     class ValidateNumbers {
diff --git a/java/bulk-import/bulk-import-common/src/test/java/sleeper/bulkimport/configuration/EmrInstanceTypeConfigTest.java b/java/configuration/src/test/java/sleeper/configuration/properties/validation/EmrInstanceTypeConfigTest.java
similarity index 92%
rename from java/bulk-import/bulk-import-common/src/test/java/sleeper/bulkimport/configuration/EmrInstanceTypeConfigTest.java
rename to java/configuration/src/test/java/sleeper/configuration/properties/validation/EmrInstanceTypeConfigTest.java
index a4a436d58..6231d6fa0 100644
--- a/java/bulk-import/bulk-import-common/src/test/java/sleeper/bulkimport/configuration/EmrInstanceTypeConfigTest.java
+++ b/java/configuration/src/test/java/sleeper/configuration/properties/validation/EmrInstanceTypeConfigTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package sleeper.bulkimport.configuration;
+package sleeper.configuration.properties.validation;
 
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
@@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test;
 
 import sleeper.configuration.properties.instance.InstanceProperties;
 import sleeper.configuration.properties.table.TableProperties;
-import sleeper.configuration.properties.validation.EmrInstanceArchitecture;
 
 import java.util.List;
 import java.util.stream.Stream;
@@ -189,6 +188,29 @@ public class EmrInstanceTypeConfigTest {
         }
     }
 
+    @Nested
+    @DisplayName("Validate values")
+    class ValidateValue {
+
+        @Test
+        void shouldFailValidationWhenSameInstanceTypeIsSpecifiedTwice() {
+            assertThat(EmrInstanceTypeConfig.isValidInstanceTypes("type-a,type-b,type-c,type-b"))
+                    .isFalse();
+        }
+
+        @Test
+        void shouldPassValidationWhenTwoInstanceTypesHaveSameWeight() {
+            assertThat(EmrInstanceTypeConfig.isValidInstanceTypes("type-a,1,type-b,2,type-c,2"))
+                    .isTrue();
+        }
+
+        @Test
+        void shouldFailValidationWhenWeightSpecifiedBeforeAnInstanceType() {
+            assertThat(EmrInstanceTypeConfig.isValidInstanceTypes("1,type-a"))
+                    .isFalse();
+        }
+    }
+
     public static Stream<EmrInstanceTypeConfig> readInstanceTypesProperty(List<String> instanceTypeEntries) {
         return EmrInstanceTypeConfig.readInstanceTypesProperty(instanceTypeEntries, EmrInstanceArchitecture.X86_64);
     }