test: add tests for edge case putting a key after delete

This commit is contained in:
Jeremy Tregunna 2025-04-23 16:49:38 -06:00
parent 6f83fa1ade
commit 2b90635021
Signed by: jer
GPG Key ID: 1278B36BA6F5D5E4
3 changed files with 343 additions and 0 deletions

View File

@ -384,6 +384,94 @@ func TestClientCompact(t *testing.T) {
}
}
func TestClientPutDeletePutSequence(t *testing.T) {
// Create a client with the mock transport
options := DefaultClientOptions()
options.TransportType = "mock"
client, err := NewClient(options)
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
// Get the underlying mock client for test assertions
mock := client.client.(*mockClient)
mock.connected = true
ctx := context.Background()
// Define test key and values
key := []byte("sequence-test-key")
initialValue := []byte("initial-value")
newValue := []byte("new-value-after-delete")
// 1. Put the initial value
mock.setResponse(transport.TypePut, []byte(`{"success": true}`))
success, err := client.Put(ctx, key, initialValue, true)
if err != nil {
t.Fatalf("Failed to put initial value: %v", err)
}
if !success {
t.Fatal("Expected Put success to be true")
}
// 2. Get and verify the initial value
mock.setResponse(transport.TypeGet, []byte(`{"value": "aW5pdGlhbC12YWx1ZQ==", "found": true}`))
val, found, err := client.Get(ctx, key)
if err != nil {
t.Fatalf("Failed to get key after initial put: %v", err)
}
if !found {
t.Fatal("Expected key to be found after initial put")
}
if string(val) != string(initialValue) {
t.Errorf("Expected value '%s', got '%s'", initialValue, val)
}
// 3. Delete the key
mock.setResponse(transport.TypeDelete, []byte(`{"success": true}`))
success, err = client.Delete(ctx, key, true)
if err != nil {
t.Fatalf("Failed to delete key: %v", err)
}
if !success {
t.Fatal("Expected Delete success to be true")
}
// 4. Verify the key is deleted
mock.setResponse(transport.TypeGet, []byte(`{"value": null, "found": false}`))
_, found, err = client.Get(ctx, key)
if err != nil {
t.Fatalf("Failed to get key after delete: %v", err)
}
if found {
t.Fatal("Expected key to not be found after delete")
}
// 5. Put a new value for the same key
mock.setResponse(transport.TypePut, []byte(`{"success": true}`))
success, err = client.Put(ctx, key, newValue, true)
if err != nil {
t.Fatalf("Failed to put new value after delete: %v", err)
}
if !success {
t.Fatal("Expected Put success to be true")
}
// 6. Get and verify the new value
mock.setResponse(transport.TypeGet, []byte(`{"value": "bmV3LXZhbHVlLWFmdGVyLWRlbGV0ZQ==", "found": true}`))
val, found, err = client.Get(ctx, key)
if err != nil {
t.Fatalf("Failed to get key after put-delete-put sequence: %v", err)
}
if !found {
t.Fatal("Expected key to be found after put-delete-put sequence")
}
if string(val) != string(newValue) {
t.Errorf("Expected value '%s', got '%s'", newValue, val)
}
}
func TestRetryWithBackoff(t *testing.T) {
ctx := context.Background()

View File

@ -451,6 +451,171 @@ func TestEngine_Reload(t *testing.T) {
}
}
func TestEngine_PutDeletePutSequence(t *testing.T) {
_, engine, cleanup := setupTest(t)
defer cleanup()
// Test key and initial value
key := []byte("test-sequence-key")
initialValue := []byte("initial-value")
// 1. Put initial value
if err := engine.Put(key, initialValue); err != nil {
t.Fatalf("Failed to put initial value: %v", err)
}
// Verify initial put worked
result, err := engine.Get(key)
if err != nil {
t.Fatalf("Failed to get key after initial put: %v", err)
}
if !bytes.Equal(result, initialValue) {
t.Errorf("Got incorrect value after initial put. Expected: %s, Got: %s",
initialValue, result)
}
// 2. Delete the key
if err := engine.Delete(key); err != nil {
t.Fatalf("Failed to delete key: %v", err)
}
// Verify key is deleted
_, err = engine.Get(key)
if err != ErrKeyNotFound {
t.Errorf("Expected ErrKeyNotFound after delete, got: %v", err)
}
// 3. Put a new value for the same key
newValue := []byte("new-value-after-delete")
if err := engine.Put(key, newValue); err != nil {
t.Fatalf("Failed to put new value after delete: %v", err)
}
// 4. Get the key and verify it has the new value
result, err = engine.Get(key)
if err != nil {
t.Fatalf("Failed to get key after put-delete-put sequence: %v", err)
}
if !bytes.Equal(result, newValue) {
t.Errorf("Got incorrect value after put-delete-put sequence. Expected: %s, Got: %s",
newValue, result)
}
// 5. Flush to ensure the operations are persisted
tables := engine.memTablePool.GetMemTables()
if len(tables) > 0 {
if err := engine.flushMemTable(tables[0]); err != nil {
t.Fatalf("Error flushing after put-delete-put sequence: %v", err)
}
}
// 6. Verify the key still has the correct value after flush
result, err = engine.Get(key)
if err != nil {
t.Fatalf("Failed to get key after flush: %v", err)
}
if !bytes.Equal(result, newValue) {
t.Errorf("Got incorrect value after flush. Expected: %s, Got: %s",
newValue, result)
}
}
func TestEngine_PutDeletePutWithFlushes(t *testing.T) {
_, engine, cleanup := setupTest(t)
defer cleanup()
// Test key and initial value
key := []byte("flush-test-key")
initialValue := []byte("initial-value-with-flush")
// 1. Put initial value
if err := engine.Put(key, initialValue); err != nil {
t.Fatalf("Failed to put initial value: %v", err)
}
// Flush after first put
tables := engine.memTablePool.GetMemTables()
if len(tables) > 0 {
if err := engine.flushMemTable(tables[0]); err != nil {
t.Fatalf("Error flushing after initial put: %v", err)
}
}
// Verify initial value persisted correctly
result, err := engine.Get(key)
if err != nil {
t.Fatalf("Failed to get key after initial put and flush: %v", err)
}
if !bytes.Equal(result, initialValue) {
t.Errorf("Got incorrect value after initial put and flush. Expected: %s, Got: %s",
initialValue, result)
}
// 2. Delete the key
if err := engine.Delete(key); err != nil {
t.Fatalf("Failed to delete key: %v", err)
}
// Flush after delete
tables = engine.memTablePool.GetMemTables()
if len(tables) > 0 {
if err := engine.flushMemTable(tables[0]); err != nil {
t.Fatalf("Error flushing after delete: %v", err)
}
}
// Verify key is deleted and the deletion was persisted
_, err = engine.Get(key)
if err != ErrKeyNotFound {
t.Errorf("Expected ErrKeyNotFound after delete and flush, got: %v", err)
}
// 3. Put a new value for the same key
newValue := []byte("new-value-after-delete-and-flush")
if err := engine.Put(key, newValue); err != nil {
t.Fatalf("Failed to put new value after delete and flush: %v", err)
}
// Flush after final put
tables = engine.memTablePool.GetMemTables()
if len(tables) > 0 {
if err := engine.flushMemTable(tables[0]); err != nil {
t.Fatalf("Error flushing after final put: %v", err)
}
}
// 4. Get the key and verify it has the new value after all operations and flushes
result, err = engine.Get(key)
if err != nil {
t.Fatalf("Failed to get key after complete sequence with flushes: %v", err)
}
if !bytes.Equal(result, newValue) {
t.Errorf("Got incorrect value after complete sequence with flushes. Expected: %s, Got: %s",
newValue, result)
}
// 5. Close and reopen the engine to ensure durability across restarts
dir := engine.dataDir
engine.Close()
// Reopen the engine
newEngine, err := NewEngine(dir)
if err != nil {
t.Fatalf("Failed to reopen engine: %v", err)
}
defer newEngine.Close()
// Verify the key still has the correct value after restart
result, err = newEngine.Get(key)
if err != nil {
t.Fatalf("Failed to get key after engine restart: %v", err)
}
if !bytes.Equal(result, newValue) {
t.Errorf("Got incorrect value after engine restart. Expected: %s, Got: %s",
newValue, result)
}
}
func TestEngine_Statistics(t *testing.T) {
_, engine, cleanup := setupTest(t)
defer cleanup()

View File

@ -320,3 +320,93 @@ func TestTransactionIterator(t *testing.T) {
t.Fatalf("Failed to commit transaction: %v", err)
}
}
func TestTransactionPutDeletePutSequence(t *testing.T) {
eng, tempDir := setupTestEngine(t)
defer os.RemoveAll(tempDir)
defer eng.Close()
// Create a read-write transaction
tx, err := NewTransaction(eng, ReadWrite)
if err != nil {
t.Fatalf("Failed to create read-write transaction: %v", err)
}
// Define key and values
key := []byte("transaction-sequence-key")
initialValue := []byte("initial-transaction-value")
newValue := []byte("new-transaction-value-after-delete")
// 1. Put the initial value within the transaction
if err := tx.Put(key, initialValue); err != nil {
t.Fatalf("Failed to put initial value in transaction: %v", err)
}
// 2. Get and verify the initial value within the transaction
val, err := tx.Get(key)
if err != nil {
t.Fatalf("Failed to get key after initial put in transaction: %v", err)
}
if !bytes.Equal(val, initialValue) {
t.Errorf("Got incorrect value after initial put. Expected: %s, Got: %s",
initialValue, val)
}
// 3. Delete the key within the transaction
if err := tx.Delete(key); err != nil {
t.Fatalf("Failed to delete key in transaction: %v", err)
}
// 4. Verify the key is deleted within the transaction
_, err = tx.Get(key)
if err == nil {
t.Error("Expected error after deleting key in transaction, got nil")
}
// 5. Put a new value for the same key within the transaction
if err := tx.Put(key, newValue); err != nil {
t.Fatalf("Failed to put new value after delete in transaction: %v", err)
}
// 6. Get and verify the new value within the transaction
val, err = tx.Get(key)
if err != nil {
t.Fatalf("Failed to get key after put-delete-put sequence in transaction: %v", err)
}
if !bytes.Equal(val, newValue) {
t.Errorf("Got incorrect value after put-delete-put sequence. Expected: %s, Got: %s",
newValue, val)
}
// 7. Commit the transaction
if err := tx.Commit(); err != nil {
t.Fatalf("Failed to commit transaction: %v", err)
}
// 8. Verify the final state is correctly persisted to the engine
val, err = eng.Get(key)
if err != nil {
t.Fatalf("Failed to get key from engine after commit: %v", err)
}
if !bytes.Equal(val, newValue) {
t.Errorf("Got incorrect value from engine after commit. Expected: %s, Got: %s",
newValue, val)
}
// 9. Create a new transaction to verify the data is still correct
tx2, err := NewTransaction(eng, ReadOnly)
if err != nil {
t.Fatalf("Failed to create second transaction: %v", err)
}
val, err = tx2.Get(key)
if err != nil {
t.Fatalf("Failed to get key in second transaction: %v", err)
}
if !bytes.Equal(val, newValue) {
t.Errorf("Got incorrect value in second transaction. Expected: %s, Got: %s",
newValue, val)
}
tx2.Rollback()
}