lbry-database-java/src/test/java/com/lbry/database/tests/RevertibleOperationStackTest.java
Ben van Hartingsveldt 047df3a149
Fix database rows
2024-09-21 00:03:04 +02:00

190 lines
No EOL
7.4 KiB
Java

package com.lbry.database.tests;
import com.lbry.database.keys.ClaimToTXOKey;
import com.lbry.database.revert.*;
import com.lbry.database.rows.ClaimToTXOPrefixRow;
import com.lbry.database.util.MapHelper;
import com.lbry.database.values.ClaimToTXOValue;
import java.util.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class RevertibleOperationStackTest {
private Map<byte[],byte[]> fakeDatabase;
private RevertibleOperationStack stack;
@BeforeAll
public void setUp(){
class FakeDB extends HashMap<byte[],byte[]> implements Map<byte[],byte[]>{
public Optional<byte[]> get2(byte[] key){
for(Map.Entry<byte[],byte[]> e : this.entrySet()){
if(Arrays.equals(e.getKey(),key)){
return Optional.of(e.getValue());
}
}
return Optional.empty();
}
public Iterable<Optional<byte[]>> multiGet(List<byte[]> keys){
List<Optional<byte[]>> values = new ArrayList<>();
for(byte[] key : keys){
values.add(this.get2(key));
}
return values;
}
}
this.fakeDatabase = new FakeDB();
this.stack = new RevertibleOperationStack(((FakeDB)this.fakeDatabase)::get2,((FakeDB)this.fakeDatabase)::multiGet);
}
@AfterAll
public void tearDown(){
this.stack.clear();
this.fakeDatabase.clear();
}
public void processStack(){
for(RevertibleOperation operation : this.stack.iterate()){
if(operation.isPut()){
byte[] savedKey = MapHelper.getKey(this.fakeDatabase,operation.getKey());
MapHelper.remove(this.fakeDatabase,savedKey);
this.fakeDatabase.put(savedKey!=null?savedKey:operation.getKey(),operation.getValue());
}else{
MapHelper.remove(this.fakeDatabase,operation.getKey());
}
}
this.stack.clear();
}
public void update(byte[] key1,byte[] value1,byte[] key2,byte[] value2){
this.stack.appendOperation(new RevertibleDelete(key1,value1));
this.stack.appendOperation(new RevertiblePut(key2,value2));
}
@Test
public void testSimplify(){
ClaimToTXOKey k1 = new ClaimToTXOKey();
k1.claim_hash = new byte[20];
Arrays.fill(k1.claim_hash,(byte) 0x01);
byte[] key1 = new ClaimToTXOPrefixRow(null,null,null).packKey(k1);
ClaimToTXOKey k2 = new ClaimToTXOKey();
k2.claim_hash = new byte[20];
Arrays.fill(k2.claim_hash,(byte) 0x02);
byte[] key2 = new ClaimToTXOPrefixRow(null,null,null).packKey(k2);
// ClaimToTXOKey k3 = new ClaimToTXOKey();
// k3.claim_hash = new byte[20];
// Arrays.fill(k3.claim_hash,(byte) 0x03);
// byte[] key3 = new ClaimToTXOPrefixRow(null,null).packKey(k3);
// ClaimToTXOKey k4 = new ClaimToTXOKey();
// k4.claim_hash = new byte[20];
// Arrays.fill(k4.claim_hash,(byte) 0x04);
// byte[] key4 = new ClaimToTXOPrefixRow(null,null).packKey(k4);
ClaimToTXOValue v1 = new ClaimToTXOValue();
v1.tx_num = 1;
v1.position = 0;
v1.root_tx_num = 1;
v1.root_position = 0;
v1.amount = 1;
v1.channel_signature_is_valid = false;
v1.name = "derp";
byte[] val1 = new ClaimToTXOPrefixRow(null,null,null).packValue(v1);
ClaimToTXOValue v2 = new ClaimToTXOValue();
v2.tx_num = 1;
v2.position = 0;
v2.root_tx_num = 1;
v2.root_position = 0;
v2.amount = 1;
v2.channel_signature_is_valid = false;
v2.name = "oops";
byte[] val2 = new ClaimToTXOPrefixRow(null,null,null).packValue(v2);
ClaimToTXOValue v3 = new ClaimToTXOValue();
v3.tx_num = 1;
v3.position = 0;
v3.root_tx_num = 1;
v3.root_position = 0;
v3.amount = 1;
v3.channel_signature_is_valid = false;
v3.name = "other";
byte[] val3 = new ClaimToTXOPrefixRow(null,null,null).packValue(v3);
// Check that we can't delete a non-existent value.
assertThrows(OperationStackIntegrityException.class,() -> this.stack.appendOperation(new RevertibleDelete(key1,val1)));
this.stack.appendOperation(new RevertiblePut(key1,val1));
assertEquals(1,this.stack.length());
this.stack.appendOperation(new RevertibleDelete(key1,val1));
assertEquals(0,this.stack.length());
this.stack.appendOperation(new RevertiblePut(key1,val1));
assertEquals(1,this.stack.length());
// Try to delete the wrong value.
assertThrows(OperationStackIntegrityException.class,() -> this.stack.appendOperation(new RevertibleDelete(key2,val2)));
this.stack.appendOperation(new RevertibleDelete(key1,val1));
assertEquals(0,this.stack.length());
this.stack.appendOperation(new RevertiblePut(key2,val3));
assertEquals(1,this.stack.length());
this.processStack();
assertEquals(this.fakeDatabase,new HashMap<byte[],byte[]>(){{this.put(key2,val3);}});
// Check that we can't put on top of the existing stored value.
assertThrows(OperationStackIntegrityException.class,() -> this.stack.appendOperation(new RevertiblePut(key2,val1)));
assertEquals(0,this.stack.length());
this.stack.appendOperation(new RevertibleDelete(key2,val3));
assertEquals(1,this.stack.length());
this.stack.appendOperation(new RevertiblePut(key2,val3));
assertEquals(0,this.stack.length());
this.update(key2,val3,key2,val1);
assertEquals(2,this.stack.length());
this.processStack();
assertEquals(this.fakeDatabase,new HashMap<byte[],byte[]>(){{this.put(key2,val1);}});
this.update(key2,val1,key2,val2);
assertEquals(2,this.stack.length());
this.update(key2,val2,key2,val3);
this.update(key2,val3,key2,val2);
this.update(key2,val2,key2,val3);
this.update(key2,val3,key2,val2);
assertThrows(OperationStackIntegrityException.class,() -> this.update(key2,val3,key2,val2));
this.update(key2,val2,key2,val3);
assertEquals(2,this.stack.length());
this.stack.appendOperation(new RevertibleDelete(key2,val3));
this.processStack();
this.processStack();
assertEquals(this.fakeDatabase,new HashMap<>());
this.stack.appendOperation(new RevertiblePut(key2,val3));
this.processStack();
assertThrows(OperationStackIntegrityException.class,() -> this.update(key2,val2,key2,val2));
this.update(key2,val3,key2,val2);
assertEquals(this.fakeDatabase,new HashMap<byte[],byte[]>(){{this.put(key2,val3);}});
byte[] undo = this.stack.getUndoOperations();
this.processStack();
this.stack.validateAndApplyStashedOperations();
assertEquals(this.fakeDatabase,new HashMap<byte[],byte[]>(){{this.put(key2,val2);}});
this.stack.applyPackedUndoOperations(undo);
this.processStack();
//TODO FIX: assertEquals(this.fakeDatabase,new HashMap<byte[],byte[]>(){{this.put(key2,val3);}});
}
}