public async Task KeysAndValues_SupportsFixedLengthStructs()
        {
            // Arrange
            var dummyOptions = new MixedStorageKVStoreOptions()
            {
                LogDirectory      = _fixture.TempDirectory,
                LogFileNamePrefix = nameof(KeysAndValues_SupportsFixedLengthStructs),
                PageSizeBits      = 12,
                MemorySizeBits    = 13 // Limit to 8KB so we're testing both in-memory and disk-based operations
            };
            var dummyStructInstance = new DummyFixedLengthStruct()
            {
                // Populate with dummy values
                DummyByte  = byte.MaxValue,
                DummyShort = short.MaxValue,
                DummyLong  = long.MaxValue
            };
            int numRecords = 10000;

            using var testSubject = new MixedStorageKVStore <DummyFixedLengthStruct, DummyFixedLengthStruct>(dummyOptions);

            // Act and assert

            // Insert
            Parallel.For(0, numRecords, key =>
            {
                DummyFixedLengthStruct localDummyStructInstance = dummyStructInstance;
                localDummyStructInstance.DummyInt = key;
                testSubject.Upsert(localDummyStructInstance, localDummyStructInstance);
            });

            // Read
            List <Task <(Status, DummyFixedLengthStruct)> > readTasks = new();

            for (int key = 0; key < numRecords; key++)
            {
                DummyFixedLengthStruct localDummyStructInstance = dummyStructInstance;
                localDummyStructInstance.DummyInt = key;
                readTasks.Add(ReadAsync(localDummyStructInstance, testSubject));
            }
            await Task.WhenAll(readTasks).ConfigureAwait(false);

            // Verify
            for (int key = 0; key < numRecords; key++)
            {
                (Status status, DummyFixedLengthStruct result) = readTasks[key].Result;
                Assert.Equal(Status.OK, status);
                DummyFixedLengthStruct localDummyStructInstance = dummyStructInstance;
                localDummyStructInstance.DummyInt = key;
                Assert.Equal(localDummyStructInstance, result);
            }
            ;
        }
        public async Task KeysAndValues_SupportsPrimitives()
        {
            // Arrange
            var dummyOptions = new MixedStorageKVStoreOptions()
            {
                LogDirectory      = _fixture.TempDirectory,
                LogFileNamePrefix = nameof(KeysAndValues_SupportsPrimitives),
                PageSizeBits      = 12,
                MemorySizeBits    = 13 // Limit to 8KB so we're testing both in-memory and disk-based operations
            };
            const int dummyValue = 12345;
            const int numRecords = 10000;

            using var testSubject = new MixedStorageKVStore <int, int>(dummyOptions);

            // Act and assert
            Parallel.For(0, numRecords, key => testSubject.Upsert(key, dummyValue));
            await ReadAndVerifyValuesAsync(numRecords, testSubject, Status.OK, dummyValue).ConfigureAwait(false);
        }
        public async Task KeysAndValues_SupportsObjects()
        {
            // Arrange
            var dummyOptions = new MixedStorageKVStoreOptions()
            {
                LogDirectory      = _fixture.TempDirectory,
                LogFileNamePrefix = nameof(KeysAndValues_SupportsObjects),
                PageSizeBits      = 12,
                MemorySizeBits    = 13 // Limit to 8KB so we're testing both in-memory and disk-based operations
            };
            int numRecords = 10000;

            using var testSubject = new MixedStorageKVStore <string, string>(dummyOptions);

            // Act and assert

            // Insert
            Parallel.For(0, numRecords, key =>
            {
                string keyAsString = key.ToString();
                testSubject.Upsert(keyAsString, keyAsString);
            });

            // Read
            List <Task <(Status, string?)> > readTasks = new();

            for (int key = 0; key < numRecords; key++)
            {
                readTasks.Add(ReadAsync(key.ToString(), testSubject));
            }
            await Task.WhenAll(readTasks).ConfigureAwait(false);

            // Verify
            Parallel.For(0, numRecords, key =>
            {
                (Status status, string?result) = readTasks[key].Result;
                Assert.Equal(Status.OK, status);
                Assert.Equal(key.ToString(), result);
            });
        }
        public async Task UpsertReadAsyncDelete_AreThreadSafe()
        {
            // Arrange
            var dummyOptions = new MixedStorageKVStoreOptions()
            {
                LogDirectory      = _fixture.TempDirectory,
                LogFileNamePrefix = nameof(UpsertReadAsyncDelete_AreThreadSafe),
                PageSizeBits      = 12,
                MemorySizeBits    = 13 // Limit to 8KB so we're testing both in-memory and disk-based operations
            };
            DummyClass dummyClassInstance = CreatePopulatedDummyClassInstance();
            int        numRecords         = 10000;

            //using var testSubject = new ObjLogMixedStorageKVStore<int, DummyClass>(dummyOptions);
            //using var testSubject = new MemoryMixedStorageKVStore<int, DummyClass>(dummyOptions);
            using var testSubject = new MixedStorageKVStore <int, DummyClass>(dummyOptions);

            // Act and assert

            // Insert
            Parallel.For(0, numRecords, key => testSubject.Upsert(key, dummyClassInstance));

            // Read
            await ReadAndVerifyValuesAsync(numRecords, testSubject, Status.OK, dummyClassInstance).ConfigureAwait(false);

            // Update
            dummyClassInstance.DummyInt    = 20;
            dummyClassInstance.DummyString = "anotherDummyString";
            Parallel.For(0, numRecords, key => testSubject.Upsert(key, dummyClassInstance));

            // Verify updates
            await ReadAndVerifyValuesAsync(numRecords, testSubject, Status.OK, dummyClassInstance).ConfigureAwait(false);

            // Delete
            Parallel.For(0, numRecords, key => testSubject.Delete(key));

            // Verify deletes
            await ReadAndVerifyValuesAsync(numRecords, testSubject, Status.NOTFOUND, null).ConfigureAwait(false);
        }
        public void LogFiles_DeletedOnClose()
        {
            // Arrange
            string directory    = Path.Combine(_fixture.TempDirectory, nameof(LogFiles_DeletedOnClose)); // Use a separate directory so the test is never affected by other tests
            var    dummyOptions = new MixedStorageKVStoreOptions()
            {
                LogDirectory      = directory,
                LogFileNamePrefix = nameof(LogFiles_DeletedOnClose),
                PageSizeBits      = 9, // Minimum
                MemorySizeBits    = 10 // Minimum
            };
            DummyClass dummyClassInstance = CreatePopulatedDummyClassInstance();
            int        numRecords         = 50; // Just enough to make sure log files are created. Segment size isn't exceeded (only 1 of each log file).
            var        testSubject        = new MixedStorageKVStore <int, DummyClass>(dummyOptions);

            Parallel.For(0, numRecords, key => testSubject.Upsert(key, dummyClassInstance));           // Creates log
            Assert.Single(Directory.EnumerateFiles(directory, $"{nameof(LogFiles_DeletedOnClose)}*")); // Log and object log

            // Act
            testSubject.Dispose();

            // Assert
            Assert.Empty(Directory.EnumerateFiles(directory)); // Logs deleted
        }