LibHac/tests/LibHac.Tests/Fs/StorageTester.cs

285 lines
7.8 KiB
C#
Raw Normal View History

using System;
2024-04-20 22:34:22 +02:00
using System.Diagnostics;
using System.IO;
using System.Linq;
using LibHac.Fs;
2024-04-20 22:34:22 +02:00
using LibHac.Util;
2021-11-14 20:08:57 +01:00
namespace LibHac.Tests.Fs;
public class StorageTester
{
2021-11-14 20:08:57 +01:00
private Random _random;
private byte[][] _backingArrays;
private byte[][] _buffers;
private int _size;
2024-04-20 22:34:22 +02:00
private int[] _accessParamProbs;
2021-11-14 20:08:57 +01:00
private int[] _frequentAccessOffsets;
private int _lastAccessEnd;
private int _totalAccessCount;
private Configuration _config;
public class Configuration
{
2021-11-14 20:08:57 +01:00
public Entry[] Entries { get; set; }
2024-04-20 22:34:22 +02:00
public AccessParam[] AccessParams { get; set; }
2021-11-14 20:08:57 +01:00
public int[] TaskProbs { get; set; }
public int[] AccessTypeProbs { get; set; }
public ulong RngSeed { get; set; }
public int FrequentAccessBlockCount { get; set; }
}
2021-11-14 20:08:57 +01:00
public StorageTester(Configuration config)
{
Entry[] entries = config.Entries;
2021-11-14 20:08:57 +01:00
if (entries.Length < 2)
{
2024-04-20 22:34:22 +02:00
throw new ArgumentException("At least 2 storage entries must be provided", nameof(config));
}
2021-11-14 20:08:57 +01:00
if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1)
{
2024-04-20 22:34:22 +02:00
throw new ArgumentException("All storages must have the same size.", nameof(config));
2021-11-14 20:08:57 +01:00
}
2021-11-14 20:08:57 +01:00
if (entries[0].BackingArray.Length == 0)
{
2024-04-20 22:34:22 +02:00
throw new ArgumentException("The storage size must be greater than 0.", nameof(config));
}
if (config.AccessParams.SelectMany<AccessParam, int>(x => [x.OffsetAlignment, x.SizeAlignment]).Any(x => x != 0 && !BitUtil.IsPowerOfTwo(x)))
{
throw new ArgumentException("All alignments must be 0 or powers of 2.", nameof(config));
2021-11-14 20:08:57 +01:00
}
2021-11-14 20:08:57 +01:00
_config = config;
_random = new Random(config.RngSeed);
2024-04-20 22:34:22 +02:00
_accessParamProbs = config.AccessParams.Select(x => x.Prob).ToArray();
2021-11-14 20:08:57 +01:00
_backingArrays = entries.Select(x => x.BackingArray).ToArray();
2021-11-14 20:08:57 +01:00
_buffers = new byte[entries.Length][];
for (int i = 0; i < entries.Length; i++)
{
2024-04-20 22:34:22 +02:00
_buffers[i] = new byte[config.AccessParams.Max(x => x.MaxSize)];
}
2021-11-14 20:08:57 +01:00
_size = entries[0].BackingArray.Length;
_lastAccessEnd = 0;
2021-11-14 20:08:57 +01:00
_frequentAccessOffsets = new int[config.FrequentAccessBlockCount];
for (int i = 0; i < _frequentAccessOffsets.Length; i++)
{
2024-04-20 22:34:22 +02:00
_frequentAccessOffsets[i] = _random.Next(0, _size);
}
2021-11-14 20:08:57 +01:00
}
2021-11-14 20:08:57 +01:00
public void Run(long accessCount)
{
long endCount = _totalAccessCount + accessCount;
2021-11-14 20:08:57 +01:00
while (_totalAccessCount < endCount)
{
Task task = ChooseTask();
switch (task)
{
2021-11-14 20:08:57 +01:00
case Task.Read:
RunRead();
break;
case Task.Write:
RunWrite();
break;
case Task.Flush:
RunFlush();
break;
}
2021-11-14 20:08:57 +01:00
_totalAccessCount++;
}
2021-11-14 20:08:57 +01:00
}
2021-11-14 20:08:57 +01:00
private void RunRead()
{
2024-04-20 22:34:22 +02:00
AccessParam accessParams = ChooseAccessRangeParams();
2021-11-14 20:08:57 +01:00
AccessType accessType = ChooseAccessType();
2024-04-20 22:34:22 +02:00
int offset = ChooseOffset(accessType, accessParams);
int size = ChooseSize(offset, accessParams);
2021-11-14 20:08:57 +01:00
for (int i = 0; i < _config.Entries.Length; i++)
{
Entry entry = _config.Entries[i];
entry.Storage.Read(offset, _buffers[i].AsSpan(0, size)).ThrowIfFailure();
}
2021-11-14 20:08:57 +01:00
if (!CompareBuffers(_buffers, size))
{
2021-11-14 20:08:57 +01:00
throw new InvalidDataException($"Read: Offset {offset}; Size {size}");
}
2024-04-20 22:34:22 +02:00
_lastAccessEnd = offset + size;
2021-11-14 20:08:57 +01:00
}
2021-11-14 20:08:57 +01:00
private void RunWrite()
{
2024-04-20 22:34:22 +02:00
AccessParam accessParams = ChooseAccessRangeParams();
2021-11-14 20:08:57 +01:00
AccessType accessType = ChooseAccessType();
2024-04-20 22:34:22 +02:00
int offset = ChooseOffset(accessType, accessParams);
int size = ChooseSize(offset, accessParams);
2021-11-14 20:08:57 +01:00
Span<byte> buffer = _buffers[0].AsSpan(0, size);
_random.NextBytes(buffer);
for (int i = 0; i < _config.Entries.Length; i++)
{
2021-11-14 20:08:57 +01:00
Entry entry = _config.Entries[i];
entry.Storage.Write(offset, buffer).ThrowIfFailure();
}
2024-04-20 22:34:22 +02:00
_lastAccessEnd = offset + size;
2021-11-14 20:08:57 +01:00
}
2021-11-14 20:08:57 +01:00
private void RunFlush()
{
foreach (Entry entry in _config.Entries)
{
2021-11-14 20:08:57 +01:00
entry.Storage.Flush().ThrowIfFailure();
}
2021-11-14 20:08:57 +01:00
if (!CompareBuffers(_backingArrays, _size))
{
2021-11-14 20:08:57 +01:00
throw new InvalidDataException("Flush");
}
}
2021-11-14 20:08:57 +01:00
private Task ChooseTask() => (Task)ChooseProb(_config.TaskProbs);
2024-04-20 22:34:22 +02:00
private AccessParam ChooseAccessRangeParams() => _config.AccessParams[ChooseProb(_accessParamProbs)];
2021-11-14 20:08:57 +01:00
private AccessType ChooseAccessType() => (AccessType)ChooseProb(_config.AccessTypeProbs);
2024-04-20 22:34:22 +02:00
private int ChooseOffset(AccessType type, AccessParam accessParams)
2021-11-14 20:08:57 +01:00
{
2024-04-20 22:34:22 +02:00
int offset = type switch
{
AccessType.Random => _random.Next(0, _size),
AccessType.Sequential => _lastAccessEnd == _size ? 0 : _lastAccessEnd,
AccessType.FrequentBlock => _frequentAccessOffsets[_random.Next(0, _frequentAccessOffsets.Length)],
_ => 0
};
return Align(offset, accessParams.OffsetAlignment);
}
2024-04-20 22:34:22 +02:00
private int ChooseSize(int offset, AccessParam sizeRange)
2021-11-14 20:08:57 +01:00
{
int availableSize = Math.Max(0, _size - offset);
2024-04-20 22:34:22 +02:00
int randSize = Align(_random.Next(sizeRange.MinSize, sizeRange.MaxSize), sizeRange.SizeAlignment);
2021-11-14 20:08:57 +01:00
return Math.Min(availableSize, randSize);
}
2024-04-20 22:34:22 +02:00
private int Align(int value, int alignment)
{
if (alignment == 0)
return value;
return Alignment.AlignDown(value, alignment);
}
2021-11-14 20:08:57 +01:00
private int ChooseProb(int[] weights)
{
int total = 0;
foreach (int weight in weights)
{
total += weight;
}
2021-11-14 20:08:57 +01:00
int rand = _random.Next(0, total);
int currentThreshold = 0;
2021-11-14 20:08:57 +01:00
for (int i = 0; i < weights.Length; i++)
{
currentThreshold += weights[i];
2021-11-14 20:08:57 +01:00
if (rand < currentThreshold)
return i;
}
2021-11-14 20:08:57 +01:00
return 0;
}
private bool CompareBuffers(byte[][] buffers, int size)
{
Span<byte> baseBuffer = buffers[0].AsSpan(0, size);
2021-11-14 20:08:57 +01:00
for (int i = 1; i < buffers.Length; i++)
{
Span<byte> testBuffer = buffers[i].AsSpan(0, size);
if (!baseBuffer.SequenceEqual(testBuffer))
{
2021-11-14 20:08:57 +01:00
return false;
}
}
2021-11-14 20:08:57 +01:00
return true;
}
2021-11-14 20:08:57 +01:00
public readonly struct Entry
{
public readonly IStorage Storage;
public readonly byte[] BackingArray;
public Entry(IStorage storage, byte[] backingArray)
{
2021-11-14 20:08:57 +01:00
Storage = storage;
BackingArray = backingArray;
}
}
2021-11-14 20:08:57 +01:00
2024-04-20 22:34:22 +02:00
public readonly struct AccessParam
{
public readonly int Prob;
public readonly int MinSize;
public readonly int MaxSize;
public readonly int OffsetAlignment;
public readonly int SizeAlignment;
public AccessParam(int prob, int minSize, int maxSize, int offsetAlignment, int sizeAlignment)
{
Prob = prob;
MinSize = minSize;
MaxSize = maxSize;
OffsetAlignment = offsetAlignment;
SizeAlignment = sizeAlignment;
}
public AccessParam(int prob, int minSize, int maxSize)
{
Prob = prob;
MinSize = minSize;
MaxSize = maxSize;
}
public AccessParam(int prob, int maxSize)
{
Prob = prob;
MinSize = 0;
MaxSize = maxSize;
}
}
2021-11-14 20:08:57 +01:00
private enum Task
{
Read = 0,
Write = 1,
Flush = 2
}
private enum AccessType
{
Random = 0,
Sequential = 1,
FrequentBlock = 2
}
}