diff --git a/tests/LibHac.Tests/Fs/StorageTester.cs b/tests/LibHac.Tests/Fs/StorageTester.cs index 1beb67cf..5bee09bd 100644 --- a/tests/LibHac.Tests/Fs/StorageTester.cs +++ b/tests/LibHac.Tests/Fs/StorageTester.cs @@ -1,7 +1,9 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; using LibHac.Fs; +using LibHac.Util; namespace LibHac.Tests.Fs; @@ -12,6 +14,8 @@ public class StorageTester private byte[][] _buffers; private int _size; + private int[] _accessParamProbs; + private int[] _frequentAccessOffsets; private int _lastAccessEnd; private int _totalAccessCount; @@ -20,8 +24,7 @@ public class StorageTester public class Configuration { public Entry[] Entries { get; set; } - public int[] SizeClassProbs { get; set; } - public int[] SizeClassMaxSizes { get; set; } + public AccessParam[] AccessParams { get; set; } public int[] TaskProbs { get; set; } public int[] AccessTypeProbs { get; set; } public ulong RngSeed { get; set; } @@ -34,28 +37,34 @@ public class StorageTester if (entries.Length < 2) { - throw new ArgumentException("At least 2 storage entries must be provided", nameof(config.Entries)); + throw new ArgumentException("At least 2 storage entries must be provided", nameof(config)); } if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1) { - throw new ArgumentException("All storages must have the same size.", nameof(config.Entries)); + throw new ArgumentException("All storages must have the same size.", nameof(config)); } if (entries[0].BackingArray.Length == 0) { - throw new ArgumentException("The storage size must be greater than 0.", nameof(config.Entries)); + throw new ArgumentException("The storage size must be greater than 0.", nameof(config)); + } + + if (config.AccessParams.SelectMany(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)); } _config = config; _random = new Random(config.RngSeed); + _accessParamProbs = config.AccessParams.Select(x => x.Prob).ToArray(); _backingArrays = entries.Select(x => x.BackingArray).ToArray(); _buffers = new byte[entries.Length][]; for (int i = 0; i < entries.Length; i++) { - _buffers[i] = new byte[config.SizeClassMaxSizes[^1]]; + _buffers[i] = new byte[config.AccessParams.Max(x => x.MaxSize)]; } _size = entries[0].BackingArray.Length; @@ -64,48 +73,10 @@ public class StorageTester _frequentAccessOffsets = new int[config.FrequentAccessBlockCount]; for (int i = 0; i < _frequentAccessOffsets.Length; i++) { - _frequentAccessOffsets[i] = ChooseOffset(AccessType.Random); + _frequentAccessOffsets[i] = _random.Next(0, _size); } } - //public StorageTester(ulong rngSeed, int frequentAccessBlockCount, params Entry[] entries) - //{ - // if (entries.Length < 2) - // { - // throw new ArgumentException("At least 2 storage entries must be provided", nameof(entries)); - // } - - // if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1) - // { - // throw new ArgumentException("All storages must have the same size.", nameof(entries)); - // } - - // if (entries[0].BackingArray.Length == 0) - // { - // throw new ArgumentException("The storage size must be greater than 0.", nameof(entries)); - // } - - // _random = new Random(rngSeed); - - // _entries = entries; - // _backingArrays = entries.Select(x => x.BackingArray).ToArray(); - - // _buffers = new byte[entries.Length][]; - // for (int i = 0; i < entries.Length; i++) - // { - // _buffers[i] = new byte[SizeClassMaxSizes[^1]]; - // } - - // _size = _entries[0].BackingArray.Length; - // _lastAccessEnd = 0; - - // _frequentAccessOffsets = new int[frequentAccessBlockCount]; - // for (int i = 0; i < _frequentAccessOffsets.Length; i++) - // { - // _frequentAccessOffsets[i] = ChooseOffset(AccessType.Random); - // } - //} - public void Run(long accessCount) { long endCount = _totalAccessCount + accessCount; @@ -132,10 +103,11 @@ public class StorageTester private void RunRead() { - int sizeClass = ChooseSizeClass(); + AccessParam accessParams = ChooseAccessRangeParams(); AccessType accessType = ChooseAccessType(); - int offset = ChooseOffset(accessType); - int size = ChooseSize(offset, sizeClass); + + int offset = ChooseOffset(accessType, accessParams); + int size = ChooseSize(offset, accessParams); for (int i = 0; i < _config.Entries.Length; i++) { @@ -147,14 +119,17 @@ public class StorageTester { throw new InvalidDataException($"Read: Offset {offset}; Size {size}"); } + + _lastAccessEnd = offset + size; } private void RunWrite() { - int sizeClass = ChooseSizeClass(); + AccessParam accessParams = ChooseAccessRangeParams(); AccessType accessType = ChooseAccessType(); - int offset = ChooseOffset(accessType); - int size = ChooseSize(offset, sizeClass); + + int offset = ChooseOffset(accessType, accessParams); + int size = ChooseSize(offset, accessParams); Span buffer = _buffers[0].AsSpan(0, size); _random.NextBytes(buffer); @@ -164,6 +139,8 @@ public class StorageTester Entry entry = _config.Entries[i]; entry.Storage.Write(offset, buffer).ThrowIfFailure(); } + + _lastAccessEnd = offset + size; } private void RunFlush() @@ -180,24 +157,36 @@ public class StorageTester } private Task ChooseTask() => (Task)ChooseProb(_config.TaskProbs); - private int ChooseSizeClass() => ChooseProb(_config.SizeClassProbs); + private AccessParam ChooseAccessRangeParams() => _config.AccessParams[ChooseProb(_accessParamProbs)]; private AccessType ChooseAccessType() => (AccessType)ChooseProb(_config.AccessTypeProbs); - private int ChooseOffset(AccessType type) => type switch + private int ChooseOffset(AccessType type, AccessParam accessParams) { - AccessType.Random => _random.Next(0, _size), - AccessType.Sequential => _lastAccessEnd == _size ? 0 : _lastAccessEnd, - AccessType.FrequentBlock => _frequentAccessOffsets[_random.Next(0, _frequentAccessOffsets.Length)], - _ => 0 - }; + 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); + } - private int ChooseSize(int offset, int sizeClass) + private int ChooseSize(int offset, AccessParam sizeRange) { int availableSize = Math.Max(0, _size - offset); - int randSize = _random.Next(0, _config.SizeClassMaxSizes[sizeClass]); + int randSize = Align(_random.Next(sizeRange.MinSize, sizeRange.MaxSize), sizeRange.SizeAlignment); return Math.Min(availableSize, randSize); } + private int Align(int value, int alignment) + { + if (alignment == 0) + return value; + + return Alignment.AlignDown(value, alignment); + } + private int ChooseProb(int[] weights) { int total = 0; @@ -248,6 +237,38 @@ public class StorageTester } } + 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; + } + } + private enum Task { Read = 0, diff --git a/tests/LibHac.Tests/FsSystem/BufferedStorageTests.cs b/tests/LibHac.Tests/FsSystem/BufferedStorageTests.cs index 84fe11d1..500cfe3b 100644 --- a/tests/LibHac.Tests/FsSystem/BufferedStorageTests.cs +++ b/tests/LibHac.Tests/FsSystem/BufferedStorageTests.cs @@ -209,11 +209,16 @@ public class BufferedStorageTests var memoryStorageEntry = new StorageTester.Entry(memoryStorage, memoryStorageArray); var bufferedStorageEntry = new StorageTester.Entry(bufferedStorage, bufferedStorageArray); - var testerConfig = new StorageTester.Configuration() + var accessParams = new StorageTester.AccessParam[config.SizeClassProbs.Length]; + for (int i = 0; i < accessParams.Length; i++) + { + accessParams[i] = new StorageTester.AccessParam(config.SizeClassProbs[i], config.SizeClassMaxSizes[i]); + } + + var testerConfig = new StorageTester.Configuration { Entries = [memoryStorageEntry, bufferedStorageEntry], - SizeClassProbs = config.SizeClassProbs, - SizeClassMaxSizes = config.SizeClassMaxSizes, + AccessParams = accessParams, TaskProbs = config.TaskProbs, AccessTypeProbs = config.AccessTypeProbs, RngSeed = config.RngSeed, diff --git a/tests/LibHac.Tests/FsSystem/IndirectStorageTests.cs b/tests/LibHac.Tests/FsSystem/IndirectStorageTests.cs index da407215..7839d5bc 100644 --- a/tests/LibHac.Tests/FsSystem/IndirectStorageTests.cs +++ b/tests/LibHac.Tests/FsSystem/IndirectStorageTests.cs @@ -340,11 +340,16 @@ public class IndirectStorageTests : IClassFixture var memoryStorageEntry = new StorageTester.Entry(memoryStorage, expectedStorageArray); var indirectStorageEntry = new StorageTester.Entry(indirectStorage, expectedStorageArray); - var testerConfig = new StorageTester.Configuration() + var accessParams = new StorageTester.AccessParam[accessConfig.SizeClassProbs.Length]; + for (int i = 0; i < accessParams.Length; i++) + { + accessParams[i] = new StorageTester.AccessParam(accessConfig.SizeClassProbs[i], accessConfig.SizeClassMaxSizes[i]); + } + + var testerConfig = new StorageTester.Configuration { Entries = [memoryStorageEntry, indirectStorageEntry], - SizeClassProbs = accessConfig.SizeClassProbs, - SizeClassMaxSizes = accessConfig.SizeClassMaxSizes, + AccessParams = accessParams, TaskProbs = accessConfig.TaskProbs, AccessTypeProbs = accessConfig.AccessTypeProbs, RngSeed = accessConfig.RngSeed,