jyothsnakonisa commented on code in PR #238:
URL: 
https://github.com/apache/cassandra-analytics/pull/238#discussion_r4077978962


##########
cassandra-analytics-common/src/main/java/org/apache/cassandra/spark/data/ReplicationFactor.java:
##########
@@ -188,13 +359,173 @@ public boolean equals(Object other)
 
         ReplicationFactor that = (ReplicationFactor) other;
         return this.replicationStrategy == that.replicationStrategy
-               && java.util.Objects.equals(this.options, that.options);
+               && Objects.equals(this.replication, that.replication);
     }
 
     @Override
     public int hashCode()
     {
-        return Objects.hash(replicationStrategy, options);
+        return Objects.hash(replicationStrategy, replication);
+    }
+
+    /**
+     * {@link #serialVersionUID} is pinned, so an instance serialized before 
the per-datacenter counts were combined
+     * deserializes with a {@code null} {@code replication}. Only reachable 
under driver/executor version skew, which
+     * is not a supported configuration, so this reports the mismatch rather 
than silently losing the counts.
+     *
+     * @return this instance
+     * @throws InvalidObjectException when deserialized from an incompatible 
older form
+     */
+    private Object readResolve() throws InvalidObjectException
+    {
+        if (replication == null)
+        {
+            throw new InvalidObjectException(
+            "ReplicationFactor was serialized by an incompatible version: 
per-datacenter replica counts are absent. "
+            + "Driver and executors must run the same version.");
+        }
+        // Rebuild through the canonical constructor so the derived fields, 
which are transient, are populated
+        return new ReplicationFactor(replicationStrategy, replication, 
replication);
+    }
+
+    /**
+     * Replica counts for a single datacenter. Cassandra accepts either {@code 
<replicas>} or
+     * {@code <replicas>/<transient>}; transient replication predates mutation 
tracking, and witness replicas
+     * (CEP-46) reuse the same form. See {@code 
org.apache.cassandra.locator.ReplicationFactor} in Cassandra.
+     */
+    public static final class ReplicaCounts implements Serializable
+    {
+        private static final long serialVersionUID = 2026091500000000001L;
+        private static final String TRANSIENT_SEPARATOR = "/";
+
+        private final int allReplicas;
+        private final int transientReplicas;
+
+        private ReplicaCounts(int allReplicas, int transientReplicas)
+        {
+            this.allReplicas = allReplicas;
+            this.transientReplicas = transientReplicas;
+        }
+
+        /**
+         * @param datacenter        datacenter name, used only in error 
messages, may be {@code null}
+         * @param allReplicas       total replicas
+         * @param transientReplicas transient (witness) replicas
+         * @return validated counts
+         * @throws IllegalArgumentException when the counts are inconsistent
+         */
+        public static ReplicaCounts of(String datacenter, int allReplicas, int 
transientReplicas)
+        {
+            validate(datacenter, allReplicas, transientReplicas);
+            return new ReplicaCounts(allReplicas, transientReplicas);
+        }
+
+        /**
+         * @return total replicas, including transient (witness) replicas
+         */
+        public int allReplicas()
+        {
+            return allReplicas;
+        }
+
+        /**
+         * @return replicas holding the full data set
+         */
+        public int fullReplicas()
+        {
+            return allReplicas - transientReplicas;
+        }
+
+        /**
+         * @return transient (witness) replicas, {@code 0} when none are 
configured
+         */
+        public int transientReplicas()
+        {
+            return transientReplicas;
+        }
+
+        /**
+         * @param value the raw replication value, e.g. {@code "3"} or {@code 
"3/1"}
+         * @return the parsed replica counts
+         * @throws NumberFormatException    when either component is not an 
integer
+         * @throws IllegalArgumentException when the value is malformed or the 
counts are inconsistent
+         */
+        static ReplicaCounts parse(@NotNull String value)
+        {
+            String trimmed = value.trim();
+            int separator = trimmed.indexOf(TRANSIENT_SEPARATOR);
+            if (separator < 0)
+            {
+                return of(null, Integer.parseInt(trimmed), 0);
+            }
+
+            if (trimmed.indexOf(TRANSIENT_SEPARATOR, separator + 1) >= 0)
+            {
+                throw new IllegalArgumentException(String.format(
+                "Replication factor format is <replicas> or 
<replicas>/<transient>, found '%s'", value));
+            }
+
+            return of(null,
+                      Integer.parseInt(trimmed.substring(0, separator).trim()),
+                      Integer.parseInt(trimmed.substring(separator + 
1).trim()));
+        }
+
+        /**
+         * Mirrors the constraints Cassandra enforces in {@code 
ReplicationFactor.validate}: transient replicas must
+         * be non-negative and strictly fewer than the total, so at least one 
full replica always exists.
+         *
+         * @param datacenter        datacenter name for the error message, may 
be {@code null}
+         * @param allReplicas       total replicas
+         * @param transientReplicas transient (witness) replicas
+         */
+        static void validate(String datacenter, int allReplicas, int 
transientReplicas)

Review Comment:
   ReplicaCounts class doesn't need `datacenter` at all and the datacenter 
argument fit here in this method. I see that you are passing this for logging 
can u try moving logging out of this class



##########
cassandra-analytics-common/src/main/java/org/apache/cassandra/spark/data/ReplicationFactor.java:
##########
@@ -188,13 +359,173 @@ public boolean equals(Object other)
 
         ReplicationFactor that = (ReplicationFactor) other;
         return this.replicationStrategy == that.replicationStrategy
-               && java.util.Objects.equals(this.options, that.options);
+               && Objects.equals(this.replication, that.replication);
     }
 
     @Override
     public int hashCode()
     {
-        return Objects.hash(replicationStrategy, options);
+        return Objects.hash(replicationStrategy, replication);
+    }
+
+    /**
+     * {@link #serialVersionUID} is pinned, so an instance serialized before 
the per-datacenter counts were combined
+     * deserializes with a {@code null} {@code replication}. Only reachable 
under driver/executor version skew, which
+     * is not a supported configuration, so this reports the mismatch rather 
than silently losing the counts.
+     *
+     * @return this instance
+     * @throws InvalidObjectException when deserialized from an incompatible 
older form
+     */
+    private Object readResolve() throws InvalidObjectException
+    {
+        if (replication == null)
+        {
+            throw new InvalidObjectException(
+            "ReplicationFactor was serialized by an incompatible version: 
per-datacenter replica counts are absent. "
+            + "Driver and executors must run the same version.");
+        }
+        // Rebuild through the canonical constructor so the derived fields, 
which are transient, are populated
+        return new ReplicationFactor(replicationStrategy, replication, 
replication);
+    }
+
+    /**
+     * Replica counts for a single datacenter. Cassandra accepts either {@code 
<replicas>} or
+     * {@code <replicas>/<transient>}; transient replication predates mutation 
tracking, and witness replicas
+     * (CEP-46) reuse the same form. See {@code 
org.apache.cassandra.locator.ReplicationFactor} in Cassandra.
+     */
+    public static final class ReplicaCounts implements Serializable
+    {
+        private static final long serialVersionUID = 2026091500000000001L;
+        private static final String TRANSIENT_SEPARATOR = "/";
+
+        private final int allReplicas;
+        private final int transientReplicas;
+
+        private ReplicaCounts(int allReplicas, int transientReplicas)
+        {
+            this.allReplicas = allReplicas;
+            this.transientReplicas = transientReplicas;
+        }
+
+        /**
+         * @param datacenter        datacenter name, used only in error 
messages, may be {@code null}
+         * @param allReplicas       total replicas
+         * @param transientReplicas transient (witness) replicas
+         * @return validated counts
+         * @throws IllegalArgumentException when the counts are inconsistent
+         */
+        public static ReplicaCounts of(String datacenter, int allReplicas, int 
transientReplicas)

Review Comment:
   ReplicaCounts class doesn't need datacenter at all and the datacenter 
argument fit here in this method. I see that you are passing this for logging 
can u try moving logging out of this class



##########
cassandra-analytics-common/src/main/java/org/apache/cassandra/spark/data/ReplicationFactor.java:
##########
@@ -203,25 +534,29 @@ public static class Serializer extends 
com.esotericsoftware.kryo.Serializer<Repl
         public void write(Kryo kryo, Output out, ReplicationFactor 
replicationFactor)
         {
             out.writeByte(replicationFactor.replicationStrategy.value);
-            out.writeByte(replicationFactor.options.size());
-            for (Map.Entry<String, Integer> entry : 
replicationFactor.options.entrySet())
+            out.writeByte(replicationFactor.replication.size());
+            for (Map.Entry<String, ReplicaCounts> entry : 
replicationFactor.replication.entrySet())
             {
                 out.writeString(entry.getKey());
-                out.writeByte(entry.getValue());
+                out.writeByte(entry.getValue().allReplicas());
+                out.writeByte(entry.getValue().transientReplicas());

Review Comment:
   This adds a new field to the Kryo wire format (1 byte/DC → 2 bytes/DC) with 
no version marker, so an old-format ReplicationFactor read by this new code 
would crash or silently produce wrong RF values. 
   
   CassandraRing solves this exact problem for its own serialization with a 
version byte, and this is actually the primary path Spark uses (its JDK 
fallback is just a fallback), so it seems worth the same protection here.



##########
cassandra-analytics-common/src/test/java/org/apache/cassandra/spark/data/ReplicationFactorTests.java:
##########
@@ -111,4 +118,357 @@ public void testEquality()
         assertThat(replicationFactor1).isEqualTo(replicationFactor2);
         
assertThat(replicationFactor1.hashCode()).isEqualTo(replicationFactor2.hashCode());
     }
+
+    // Transient / witness replicas: the <replicas>/<transient> form, reused 
by witness replicas under
+    // mutation tracking (CEP-45/CEP-46)
+
+    @Test
+    public void testNoTransientReplicasByDefault()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3"));
+        assertThat(replicationFactor.hasTransientReplicas()).isFalse();
+        assertThat(replicationFactor.getTransientOptions()).isEmpty();
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(3);
+        
assertThat(replicationFactor.getTransientReplicationFactor()).isEqualTo(0);
+        
assertThat(replicationFactor.getFullReplicas("datacenter1")).isEqualTo(3);
+        
assertThat(replicationFactor.getTransientReplicas("datacenter1")).isEqualTo(0);
+    }
+
+    @Test
+    public void testTransientReplicasSingleDatacenter()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1"));
+        assertThat(replicationFactor.hasTransientReplicas()).isTrue();
+        // total keeps its original meaning: all replicas, witnesses included
+        
assertThat(replicationFactor.getOptions().get("datacenter1")).isEqualTo(Integer.valueOf(3));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(2);
+        
assertThat(replicationFactor.getTransientReplicationFactor()).isEqualTo(1);
+        
assertThat(replicationFactor.getFullReplicas("datacenter1")).isEqualTo(2);
+        
assertThat(replicationFactor.getTransientReplicas("datacenter1")).isEqualTo(1);
+    }
+
+    @Test
+    public void testTransientReplicasMultipleDatacenters()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1",
+        "datacenter2", "3/1"));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(6);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(4);
+        
assertThat(replicationFactor.getTransientReplicationFactor()).isEqualTo(2);
+    }
+
+    @Test
+    public void testMixedTransientAndFullDatacenters()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1",
+        "datacenter2", "3"));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(6);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(5);
+        
assertThat(replicationFactor.getTransientReplicas("datacenter1")).isEqualTo(1);
+        
assertThat(replicationFactor.getTransientReplicas("datacenter2")).isEqualTo(0);
+        
assertThat(replicationFactor.getFullReplicas("datacenter2")).isEqualTo(3);
+        // datacenter2 has no transient replicas, so it must be absent rather 
than mapped to zero
+        
assertThat(replicationFactor.getTransientOptions()).containsOnlyKeys("datacenter1");
+    }
+
+    @Test
+    public void testTransientReplicasSimpleStrategy()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "SimpleStrategy",
+        "replication_factor", "3/1"));
+        assertThat(replicationFactor.getReplicationStrategy())
+        .isEqualTo(ReplicationFactor.ReplicationStrategy.SimpleStrategy);
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(2);
+    }
+
+    @Test
+    public void testTransientReplicasWithWhitespace()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", " 3 / 1 "));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(2);
+    }
+
+    @Test
+    public void testTransientEqualToTotalIsRejected()
+    {
+        // Cassandra requires at least one full replica, so 3/3 is invalid and 
must not be accepted silently
+        assertThatThrownBy(() -> new ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/3")))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("datacenter1");
+    }
+
+    @Test
+    public void testMalformedReplicationValuesAreRejected()
+    {
+        for (String malformed : new String[]{"3/", "/1", "3/1/1", "3/x", 
"x/1", "3/-1", ""})
+        {
+            assertThatThrownBy(() -> new ReplicationFactor(ImmutableMap.of(
+            "class", "NetworkTopologyStrategy",
+            "datacenter1", malformed)))
+            .as("malformed replication value '%s' should be rejected", 
malformed)
+            .isInstanceOf(IllegalArgumentException.class);
+        }
+    }
+
+    @Test
+    public void testGetFullReplicasUnknownDatacenter()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1"));
+        assertThatThrownBy(() -> replicationFactor.getFullReplicas("nosuchdc"))
+        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testTransientOptionsForUnknownDatacenterRejected()
+    {
+        assertThatThrownBy(() -> new ReplicationFactor(
+        ReplicationFactor.ReplicationStrategy.NetworkTopologyStrategy,
+        ImmutableMap.of("datacenter1", 3),
+        ImmutableMap.of("datacenter2", 1)))
+        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testEqualityConsidersTransientReplicas()
+    {
+        ReplicationFactor full = new ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3"));
+        ReplicationFactor withTransient = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1"));
+        assertThat(full).isNotEqualTo(withTransient);
+        assertThat(full.hashCode()).isNotEqualTo(withTransient.hashCode());
+    }
+
+    @Test
+    public void testNegativeReplicationFactorIsRejected()
+    {
+        assertThatThrownBy(() -> new ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "-3")))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("datacenter1");
+    }
+
+    @Test
+    public void testZeroReplicationFactorIsAllowed()
+    {
+        // RF 0 is legitimate for NetworkTopologyStrategy: the keyspace is 
simply not replicated to that datacenter
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3",
+        "datacenter2", "0"));
+        
assertThat(replicationFactor.getOptions().get("datacenter2")).isEqualTo(Integer.valueOf(0));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+    }
+
+    @Test
+    public void testKryoSerializationRoundTripWithTransientReplicas() throws 
Exception
+    {
+        ReplicationFactor original = new ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1",
+        "datacenter2", "3"));
+
+        Kryo kryo = new Kryo();
+        kryo.register(ReplicationFactor.class, new 
ReplicationFactor.Serializer());
+
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        try (Output out = new Output(bytes))
+        {
+            kryo.writeObject(out, original);
+        }
+        ReplicationFactor deserialized;
+        try (Input in = new Input(new 
ByteArrayInputStream(bytes.toByteArray())))
+        {
+            deserialized = kryo.readObject(in, ReplicationFactor.class);
+        }
+
+        assertThat(deserialized).isEqualTo(original);
+        assertThat(deserialized.getTotalReplicationFactor()).isEqualTo(6);
+        assertThat(deserialized.getFullReplicationFactor()).isEqualTo(5);
+        
assertThat(deserialized.getTransientReplicas("datacenter1")).isEqualTo(1);
+        
assertThat(deserialized.getTransientReplicas("datacenter2")).isEqualTo(0);
+    }
+
+    @Test
+    public void testJdkSerializationRoundTripWithTransientReplicas() throws 
Exception

Review Comment:
   Same here, this test Is identical to 
testPrecomputedValuesSurviveJdkSerialization, WDYT of merging them



##########
cassandra-analytics-common/src/test/java/org/apache/cassandra/spark/data/ReplicationFactorTests.java:
##########
@@ -111,4 +118,357 @@ public void testEquality()
         assertThat(replicationFactor1).isEqualTo(replicationFactor2);
         
assertThat(replicationFactor1.hashCode()).isEqualTo(replicationFactor2.hashCode());
     }
+
+    // Transient / witness replicas: the <replicas>/<transient> form, reused 
by witness replicas under
+    // mutation tracking (CEP-45/CEP-46)
+
+    @Test
+    public void testNoTransientReplicasByDefault()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3"));
+        assertThat(replicationFactor.hasTransientReplicas()).isFalse();
+        assertThat(replicationFactor.getTransientOptions()).isEmpty();
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(3);
+        
assertThat(replicationFactor.getTransientReplicationFactor()).isEqualTo(0);
+        
assertThat(replicationFactor.getFullReplicas("datacenter1")).isEqualTo(3);
+        
assertThat(replicationFactor.getTransientReplicas("datacenter1")).isEqualTo(0);
+    }
+
+    @Test
+    public void testTransientReplicasSingleDatacenter()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1"));
+        assertThat(replicationFactor.hasTransientReplicas()).isTrue();
+        // total keeps its original meaning: all replicas, witnesses included
+        
assertThat(replicationFactor.getOptions().get("datacenter1")).isEqualTo(Integer.valueOf(3));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(2);
+        
assertThat(replicationFactor.getTransientReplicationFactor()).isEqualTo(1);
+        
assertThat(replicationFactor.getFullReplicas("datacenter1")).isEqualTo(2);
+        
assertThat(replicationFactor.getTransientReplicas("datacenter1")).isEqualTo(1);
+    }
+
+    @Test
+    public void testTransientReplicasMultipleDatacenters()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1",
+        "datacenter2", "3/1"));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(6);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(4);
+        
assertThat(replicationFactor.getTransientReplicationFactor()).isEqualTo(2);
+    }
+
+    @Test
+    public void testMixedTransientAndFullDatacenters()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1",
+        "datacenter2", "3"));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(6);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(5);
+        
assertThat(replicationFactor.getTransientReplicas("datacenter1")).isEqualTo(1);
+        
assertThat(replicationFactor.getTransientReplicas("datacenter2")).isEqualTo(0);
+        
assertThat(replicationFactor.getFullReplicas("datacenter2")).isEqualTo(3);
+        // datacenter2 has no transient replicas, so it must be absent rather 
than mapped to zero
+        
assertThat(replicationFactor.getTransientOptions()).containsOnlyKeys("datacenter1");
+    }
+
+    @Test
+    public void testTransientReplicasSimpleStrategy()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "SimpleStrategy",
+        "replication_factor", "3/1"));
+        assertThat(replicationFactor.getReplicationStrategy())
+        .isEqualTo(ReplicationFactor.ReplicationStrategy.SimpleStrategy);
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(2);
+    }
+
+    @Test
+    public void testTransientReplicasWithWhitespace()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", " 3 / 1 "));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+        assertThat(replicationFactor.getFullReplicationFactor()).isEqualTo(2);
+    }
+
+    @Test
+    public void testTransientEqualToTotalIsRejected()
+    {
+        // Cassandra requires at least one full replica, so 3/3 is invalid and 
must not be accepted silently
+        assertThatThrownBy(() -> new ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/3")))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("datacenter1");
+    }
+
+    @Test
+    public void testMalformedReplicationValuesAreRejected()
+    {
+        for (String malformed : new String[]{"3/", "/1", "3/1/1", "3/x", 
"x/1", "3/-1", ""})
+        {
+            assertThatThrownBy(() -> new ReplicationFactor(ImmutableMap.of(
+            "class", "NetworkTopologyStrategy",
+            "datacenter1", malformed)))
+            .as("malformed replication value '%s' should be rejected", 
malformed)
+            .isInstanceOf(IllegalArgumentException.class);
+        }
+    }
+
+    @Test
+    public void testGetFullReplicasUnknownDatacenter()
+    {
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1"));
+        assertThatThrownBy(() -> replicationFactor.getFullReplicas("nosuchdc"))
+        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testTransientOptionsForUnknownDatacenterRejected()
+    {
+        assertThatThrownBy(() -> new ReplicationFactor(
+        ReplicationFactor.ReplicationStrategy.NetworkTopologyStrategy,
+        ImmutableMap.of("datacenter1", 3),
+        ImmutableMap.of("datacenter2", 1)))
+        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testEqualityConsidersTransientReplicas()
+    {
+        ReplicationFactor full = new ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3"));
+        ReplicationFactor withTransient = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3/1"));
+        assertThat(full).isNotEqualTo(withTransient);
+        assertThat(full.hashCode()).isNotEqualTo(withTransient.hashCode());
+    }
+
+    @Test
+    public void testNegativeReplicationFactorIsRejected()
+    {
+        assertThatThrownBy(() -> new ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "-3")))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("datacenter1");
+    }
+
+    @Test
+    public void testZeroReplicationFactorIsAllowed()
+    {
+        // RF 0 is legitimate for NetworkTopologyStrategy: the keyspace is 
simply not replicated to that datacenter
+        ReplicationFactor replicationFactor = new 
ReplicationFactor(ImmutableMap.of(
+        "class", "NetworkTopologyStrategy",
+        "datacenter1", "3",
+        "datacenter2", "0"));
+        
assertThat(replicationFactor.getOptions().get("datacenter2")).isEqualTo(Integer.valueOf(0));
+        assertThat(replicationFactor.getTotalReplicationFactor()).isEqualTo(3);
+    }
+
+    @Test
+    public void testKryoSerializationRoundTripWithTransientReplicas() throws 
Exception

Review Comment:
   This test is almost identical to 
testPrecomputedValuesSurviveKryoSerialization, WDYT of merging them?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to