Untitled
unknown
plain_text
a month ago
12 kB
3
Indexable
import student.TestCase;
/**
* Extra direct tests for Hash to improve mutation coverage.
*
* @author Leguejou Awunganyi
* @author Ishita Punna
* @version 2026-02-08
*/
public class HashTest extends TestCase {
/**
* Test basic insert/find/remove behavior.
*/
public void testInsertFindRemoveBasics() {
Hash hash = new Hash("artist", 10);
GraphNode a = new GraphNode("A", true, 0);
Hash.InsertResult result = hash.insert("A", a);
assertTrue(result.inserted);
assertFalse(result.duplicate);
assertFalse(result.resized);
assertEquals(1, hash.size());
assertSame(a, (GraphNode)hash.find("A"));
GraphNode removed = (GraphNode)hash.remove("A");
assertSame(a, removed);
assertEquals(0, hash.size());
assertNull(hash.find("A"));
}
/**
* Test duplicate detection and that original node is preserved.
*/
public void testDuplicateInsert() {
Hash hash = new Hash("artist", 10);
GraphNode first = new GraphNode("A", true, 0);
GraphNode second = new GraphNode("A-again", true, 1);
Hash.InsertResult firstResult = hash.insert("A", first);
Hash.InsertResult secondResult = hash.insert("A", second);
assertTrue(firstResult.inserted);
assertFalse(firstResult.duplicate);
assertFalse(secondResult.inserted);
assertTrue(secondResult.duplicate);
assertEquals(1, hash.size());
assertSame(first, (GraphNode)hash.find("A"));
}
/**
* Test that searching continues past a tombstone and insert reuses tombstone.
*/
public void testTombstoneSearchAndReuse() {
Hash hash = new Hash("artist", 10);
GraphNode a = new GraphNode("A", true, 0);
GraphNode k = new GraphNode("K", true, 1);
GraphNode u = new GraphNode("U", true, 2);
hash.insert("A", a);
hash.insert("K", k);
assertSame(a, (GraphNode)hash.find("A"));
assertSame(k, (GraphNode)hash.find("K"));
GraphNode removed = (GraphNode)hash.remove("A");
assertSame(a, removed);
assertNull(hash.find("A"));
assertSame(k, (GraphNode)hash.find("K"));
Hash.InsertResult reuse = hash.insert("U", u);
assertTrue(reuse.inserted);
assertFalse(reuse.duplicate);
assertFuzzyEquals(
"5: |U|\r\n"
+ "6: |K|\r\n"
+ "total artists: 2\r\n",
hash.print());
assertSame(u, (GraphNode)hash.find("U"));
assertSame(k, (GraphNode)hash.find("K"));
}
/**
* Test resize and rehash preserve active entries.
*/
public void testResizeRehashPreservesEntries() {
Hash hash = new Hash("song", 2);
GraphNode s1 = new GraphNode("Song1", false, 0);
GraphNode s2 = new GraphNode("Song2", false, 1);
GraphNode s3 = new GraphNode("Song3", false, 2);
Hash.InsertResult r1 = hash.insert("Song1", s1);
Hash.InsertResult r2 = hash.insert("Song2", s2);
assertFalse(r1.resized);
assertTrue(r2.resized);
assertEquals(4, hash.capacity());
assertSame(s1, (GraphNode)hash.find("Song1"));
assertSame(s2, (GraphNode)hash.find("Song2"));
Hash.InsertResult r3 = hash.insert("Song3", s3);
assertTrue(r3.inserted);
assertSame(s3, (GraphNode)hash.find("Song3"));
assertEquals(3, hash.size());
}
/**
* Test print shows tombstone and active entries after removal.
*/
public void testPrintWithTombstone() {
Hash hash = new Hash("artist", 10);
GraphNode a = new GraphNode("A", true, 0);
GraphNode k = new GraphNode("K", true, 1);
hash.insert("A", a);
hash.insert("K", k);
hash.remove("A");
assertFuzzyEquals(
"5: TOMBSTONE\r\n"
+ "6: |K|\r\n"
+ "total artists: 1\r\n",
hash.print());
}
/**
* Test hash function is deterministic and stays in range.
*/
public void testHashFunctionDeterministicAndInRange() {
Hash hash = new Hash("artist", 10);
int value1 = hash.h("Blind Lemon Jefferson", 10);
int value2 = hash.h("Blind Lemon Jefferson", 10);
assertEquals(value1, value2);
assertTrue(value1 >= 0);
assertTrue(value1 < 10);
}
/**
* Test that resize rehashing handles collisions in placeEntry().
*/
public void testResizeRehashCollisionPath() {
Hash hash = new Hash("artist", 4);
GraphNode a = new GraphNode("A", true, 0);
GraphNode i = new GraphNode("I", true, 1);
GraphNode b = new GraphNode("B", true, 2);
// A and I both hash to 1 mod 4, and also to 1 mod 8.
// That means when resize happens, placeEntry() must probe
// while reinserting the old entries.
Hash.InsertResult r1 = hash.insert("A", a);
Hash.InsertResult r2 = hash.insert("I", i);
assertTrue(r1.inserted);
assertFalse(r1.resized);
assertTrue(r2.inserted);
assertFalse(r2.resized);
assertEquals(2, hash.size());
assertEquals(4, hash.capacity());
// Third insert triggers resize from 4 to 8.
Hash.InsertResult r3 = hash.insert("B", b);
assertTrue(r3.inserted);
assertTrue(r3.resized);
assertEquals(8, hash.capacity());
assertEquals(3, hash.size());
// Both old entries must still be present after rehash collision handling.
assertSame(a, (GraphNode)hash.find("A"));
assertSame(i, (GraphNode)hash.find("I"));
assertSame(b, (GraphNode)hash.find("B"));
}
/**
* Test tombstone search across multiple probes after a resize.
*/
public void testFindPastTombstoneAfterResize() {
Hash hash = new Hash("artist", 4);
GraphNode a = new GraphNode("A", true, 0);
GraphNode i = new GraphNode("I", true, 1);
GraphNode q = new GraphNode("Q", true, 2);
hash.insert("A", a);
hash.insert("I", i);
// Trigger resize to 8, keeping a collision cluster alive.
Hash.InsertResult resizeInsert = hash.insert("Q", q);
assertTrue(resizeInsert.resized);
assertEquals(8, hash.capacity());
// Remove the first key in that cluster.
GraphNode removed = (GraphNode)hash.remove("A");
assertSame(a, removed);
assertNull(hash.find("A"));
// Search must continue past the tombstone and still find the others.
assertSame(i, (GraphNode)hash.find("I"));
assertSame(q, (GraphNode)hash.find("Q"));
assertEquals(2, hash.size());
}
/**
* Tests that insertion reuses the first tombstone in the probe path,
* not a later tombstone.
*/
public void testInsertUsesFirstTombstone() {
Hash hash = new Hash("artist", 7);
hash.insert("a", "A");
hash.insert("h", "H");
hash.insert("o", "O");
assertEquals("A", hash.remove("a"));
assertEquals("H", hash.remove("h"));
hash.insert("v", "V");
String output = hash.print();
assertTrue(output.contains("6: |v|"));
assertTrue(output.contains("0: TOMBSTONE"));
assertEquals("V", hash.find("v"));
assertNull(hash.find("a"));
assertNull(hash.find("h"));
}
/**
* Tests that quadratic probing uses home + i * i.
*/
public void testQuadraticProbeUsesSquareStep() {
Hash hash = new Hash("artist", 7);
hash.insert("a", "A");
hash.insert("h", "H");
hash.insert("o", "O");
String output = hash.print();
assertTrue(output.contains("6: |a|"));
assertTrue(output.contains("0: |h|"));
assertTrue(output.contains("3: |o|"));
}
/**
* Tests that resize keeps active entries but does not bring back tombstones.
*/
public void testResizeDoesNotReinsertTombstones() {
Hash hash = new Hash("artist", 4);
hash.insert("a", "A");
hash.insert("b", "B");
assertEquals("A", hash.remove("a"));
hash.insert("c", "C");
hash.insert("d", "D");
assertEquals(8, hash.capacity());
assertEquals(3, hash.size());
assertNull(hash.find("a"));
assertEquals("B", hash.find("b"));
assertEquals("C", hash.find("c"));
assertEquals("D", hash.find("d"));
String output = hash.print();
assertFalse(output.contains("|a|"));
}
/**
* Tests the sfold hash math directly.
*/
public void testHashSfoldMath() {
Hash hash = new Hash("artist", 10);
assertEquals(62, hash.h("abc", 101));
assertEquals(57, hash.h("abcd", 101));
assertEquals(57, hash.h("abcde", 101));
}
/**
* Tests the case where quadratic probing cycles through only active slots.
* For capacity 8, keys A, I, Q, and Y all have home slot 1.
* The probe sequence from slot 1 only reaches 1, 2, and 5, so after
* A, I, and Q are inserted, Y cannot find a null slot in that probe cycle.
* This targets the final firstTomb check in insert().
*/
public void testInsertFailsWhenProbeCycleHasNoTombstone() {
Hash hash = new Hash("artist", 8);
assertTrue(hash.insert("A", "A").inserted);
assertTrue(hash.insert("I", "I").inserted);
assertTrue(hash.insert("Q", "Q").inserted);
assertEquals(3, hash.size());
assertEquals("A", hash.find("A"));
assertEquals("I", hash.find("I"));
assertEquals("Q", hash.find("Q"));
Hash.InsertResult result = hash.insert("Y", "Y");
assertFalse(result.inserted);
assertFalse(result.duplicate);
assertFalse(result.resized);
assertEquals(3, hash.size());
assertNull(hash.find("Y"));
}
/**
* Tests that several old entries are reinserted during resize.
* This targets the loop in placeEntry(). If the loop condition is mutated
* to false, the old records are lost during resize.
*/
public void testResizePreservesSeveralOldEntries() {
Hash hash = new Hash("artist", 4);
GraphNode a = new GraphNode("A", true, 0);
GraphNode b = new GraphNode("B", true, 1);
GraphNode c = new GraphNode("C", true, 2);
assertTrue(hash.insert("A", a).inserted);
assertTrue(hash.insert("B", b).inserted);
assertEquals(2, hash.size());
assertEquals(4, hash.capacity());
// Third insert triggers resize because count is already half full.
Hash.InsertResult result = hash.insert("C", c);
assertTrue(result.resized);
assertTrue(result.inserted);
assertEquals(8, hash.capacity());
assertEquals(3, hash.size());
// These two assertions are the important mutation killers.
assertSame(a, hash.find("A"));
assertSame(b, hash.find("B"));
assertSame(c, hash.find("C"));
assertFuzzyEquals(
"1: |A|\r\n"
+ "2: |B|\r\n"
+ "3: |C|\r\n"
+ "total artists: 3\r\n",
hash.print());
}
}Editor is loading...
Leave a Comment