@Test
public void testMerge()
{
// note: local counts aggregated; remote counts are reconciled (i.e. take max)
ContextState left = ContextState.allocate(0, 1, 3);
left.writeRemote(CounterId.fromInt(1), 1L, 1L);
left.writeRemote(CounterId.fromInt(2), 2L, 2L);
left.writeRemote(CounterId.fromInt(4), 6L, 3L);
left.writeLocal(CounterId.getLocalId(), 7L, 3L);
ContextState right = ContextState.allocate(0, 1, 2);
right.writeRemote(CounterId.fromInt(4), 4L, 4L);
right.writeRemote(CounterId.fromInt(5), 5L, 5L);
right.writeLocal(CounterId.getLocalId(), 2L, 9L);
ByteBuffer merged = cc.merge(left.context, right.context);
int hd = 4;
assertEquals(hd + 5 * stepLength, merged.remaining());
// local node id's counts are aggregated
assertTrue(Util.equalsCounterId(CounterId.getLocalId(), merged, hd + 4 * stepLength));
assertEquals(9L, merged.getLong(merged.position() + hd + 4 * stepLength + idLength));
assertEquals(12L, merged.getLong(merged.position() + hd + 4*stepLength + idLength + clockLength));
// remote node id counts are reconciled (i.e. take max)
assertTrue(Util.equalsCounterId(CounterId.fromInt(4), merged, hd + 2 * stepLength));
assertEquals(6L, merged.getLong(merged.position() + hd + 2 * stepLength + idLength));
assertEquals( 3L, merged.getLong(merged.position() + hd + 2*stepLength + idLength + clockLength));
assertTrue(Util.equalsCounterId(CounterId.fromInt(5), merged, hd + 3 * stepLength));
assertEquals(5L, merged.getLong(merged.position() + hd + 3 * stepLength + idLength));
assertEquals( 5L, merged.getLong(merged.position() + hd + 3*stepLength + idLength + clockLength));
assertTrue(Util.equalsCounterId(CounterId.fromInt(2), merged, hd + stepLength));
assertEquals(2L, merged.getLong(merged.position() + hd + stepLength + idLength));
assertEquals( 2L, merged.getLong(merged.position() + hd + stepLength + idLength + clockLength));
assertTrue(Util.equalsCounterId(CounterId.fromInt(1), merged, hd));
assertEquals( 1L, merged.getLong(merged.position() + hd + idLength));
assertEquals( 1L, merged.getLong(merged.position() + hd + idLength + clockLength));
//
// Test merging two exclusively global contexts
//
left = ContextState.allocate(3, 0, 0);
left.writeGlobal(CounterId.fromInt(1), 1L, 1L);
left.writeGlobal(CounterId.fromInt(2), 2L, 2L);
left.writeGlobal(CounterId.fromInt(3), 3L, 3L);
right = ContextState.allocate(3, 0, 0);
right.writeGlobal(CounterId.fromInt(3), 6L, 6L);
right.writeGlobal(CounterId.fromInt(4), 4L, 4L);
right.writeGlobal(CounterId.fromInt(5), 5L, 5L);
merged = cc.merge(left.context, right.context);
assertEquals(headerSizeLength + 5 * headerEltLength + 5 * stepLength, merged.remaining());
assertEquals(18L, cc.total(merged));
assertEquals(5, merged.getShort(merged.position()));
int headerLength = headerSizeLength + 5 * headerEltLength;
assertTrue(Util.equalsCounterId(CounterId.fromInt(1), merged, headerLength));
assertEquals(1L, merged.getLong(merged.position() + headerLength + idLength));
assertEquals(1L, merged.getLong(merged.position() + headerLength + idLength + clockLength));
assertTrue(Util.equalsCounterId(CounterId.fromInt(2), merged, headerLength + stepLength));
assertEquals(2L, merged.getLong(merged.position() + headerLength + stepLength + idLength));
assertEquals(2L, merged.getLong(merged.position() + headerLength + stepLength + idLength + clockLength));
// pick the global shard with the largest clock
assertTrue(Util.equalsCounterId(CounterId.fromInt(3), merged, headerLength + 2 * stepLength));
assertEquals(6L, merged.getLong(merged.position() + headerLength + 2 * stepLength + idLength));
assertEquals(6L, merged.getLong(merged.position() + headerLength + 2 * stepLength + idLength + clockLength));
assertTrue(Util.equalsCounterId(CounterId.fromInt(4), merged, headerLength + 3 * stepLength));
assertEquals(4L, merged.getLong(merged.position() + headerLength + 3 * stepLength + idLength));
assertEquals(4L, merged.getLong(merged.position() + headerLength + 3 * stepLength + idLength + clockLength));
assertTrue(Util.equalsCounterId(CounterId.fromInt(5), merged, headerLength + 4 * stepLength));
assertEquals(5L, merged.getLong(merged.position() + headerLength + 4 * stepLength + idLength));
assertEquals(5L, merged.getLong(merged.position() + headerLength + 4 * stepLength + idLength + clockLength));
//
// Test merging two global contexts w/ 'invalid shards'
//
left = ContextState.allocate(1, 0, 0);
left.writeGlobal(CounterId.fromInt(1), 10L, 20L);
right = ContextState.allocate(1, 0, 0);
right.writeGlobal(CounterId.fromInt(1), 10L, 30L);
merged = cc.merge(left.context, right.context);
headerLength = headerSizeLength + headerEltLength;
assertEquals(headerLength + stepLength, merged.remaining());
assertEquals(30L, cc.total(merged));
assertEquals(1, merged.getShort(merged.position()));
assertTrue(Util.equalsCounterId(CounterId.fromInt(1), merged, headerLength));
assertEquals(10L, merged.getLong(merged.position() + headerLength + idLength));
// with equal clock, we should pick the largest value
assertEquals(30L, merged.getLong(merged.position() + headerLength + idLength + clockLength));
//
// Test merging global w/ mixed contexts
//
left = ContextState.allocate(2, 0, 0);
left.writeGlobal(CounterId.fromInt(1), 1L, 1L);
left.writeGlobal(CounterId.fromInt(2), 1L, 1L);
right = ContextState.allocate(0, 1, 1);
right.writeLocal(CounterId.fromInt(1), 100L, 100L);
right.writeRemote(CounterId.fromInt(2), 100L, 100L);
// global shards should dominate local/remote, even with lower clock and value
merged = cc.merge(left.context, right.context);
headerLength = headerSizeLength + 2 * headerEltLength;
assertEquals(headerLength + 2 * stepLength, merged.remaining());