LibHac/tests/LibHac.Tests/FsSystem/DirectorySaveDataFileSystemTests.cs

554 lines
No EOL
20 KiB
C#

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tests.Fs;
using LibHac.Tests.Fs.IFileSystemTestBase;
using LibHac.Tools.Fs;
using Xunit;
namespace LibHac.Tests.FsSystem;
public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests
{
protected override IFileSystem CreateFileSystem()
{
return CreateFileSystemInternal().saveFs;
}
protected override IReopenableFileSystemCreator GetFileSystemCreator()
{
return new DirectorySaveDataFileSystemCreator();
}
private class DirectorySaveDataFileSystemCreator : IReopenableFileSystemCreator
{
private IFileSystem BaseFileSystem { get; }
public DirectorySaveDataFileSystemCreator()
{
BaseFileSystem = new InMemoryFileSystem();
}
public IFileSystem Create()
{
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, BaseFileSystem, true, true, true)
.ThrowIfFailure();
return saveFs;
}
}
public static Result CreateDirSaveFs(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem,
ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator,
bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled,
FileSystemClient fsClient)
{
var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient);
Result res = obj.Initialize(isJournalingSupported, isMultiCommitSupported, isJournalingEnabled, timeStampGetter,
randomGenerator);
if (res.IsSuccess())
{
created = obj;
return Result.Success;
}
obj.Dispose();
UnsafeHelpers.SkipParamInit(out created);
return res;
}
public static Result CreateDirSaveFs(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem,
bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled)
{
return CreateDirSaveFs(out created, baseFileSystem, null, null, isJournalingSupported, isMultiCommitSupported,
isJournalingEnabled, null);
}
private (IFileSystem baseFs, DirectorySaveDataFileSystem saveFs) CreateFileSystemInternal()
{
var baseFs = new InMemoryFileSystem();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
return (baseFs, saveFs);
}
[Fact]
public void CreateFile_CreatedInModifiedDirectory()
{
(IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal();
saveFs.CreateFile("/file", 0, CreateFileOptions.None);
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/1/file"));
Assert.Equal(DirectoryEntryType.File, type);
}
[Fact]
public void CreateFile_NotCreatedInCommittedDirectory()
{
(IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal();
saveFs.CreateFile("/file", 0, CreateFileOptions.None);
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file"));
}
[Fact]
public void Commit_FileExistsInCommittedDirectory()
{
(IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal();
saveFs.CreateFile("/file", 0, CreateFileOptions.None);
Assert.Success(saveFs.Commit());
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/0/file"));
Assert.Equal(DirectoryEntryType.File, type);
}
[Fact]
public void Rollback_FileDoesNotExistInBaseAfterRollback()
{
(IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal();
saveFs.CreateFile("/file", 0, CreateFileOptions.None);
// Rollback should succeed
Assert.Success(saveFs.Rollback());
// Make sure all the files are gone
Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/1/file"));
}
[Fact]
public void Rollback_DeletedFileIsRestoredInBaseAfterRollback()
{
(IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal();
saveFs.CreateFile("/file", 0, CreateFileOptions.None);
saveFs.Commit();
saveFs.DeleteFile("/file");
// Rollback should succeed
Assert.Success(saveFs.Rollback());
// Make sure all the files are restored
Assert.Success(saveFs.GetEntryType(out _, "/file"));
Assert.Success(baseFs.GetEntryType(out _, "/0/file"));
Assert.Success(baseFs.GetEntryType(out _, "/1/file"));
}
[Fact]
public void Initialize_NormalState_UsesCommittedData()
{
var baseFs = new InMemoryFileSystem();
baseFs.CreateDirectory("/0").ThrowIfFailure();
baseFs.CreateDirectory("/1").ThrowIfFailure();
// Set the existing files before initializing the save FS
baseFs.CreateFile("/0/file1", 0, CreateFileOptions.None).ThrowIfFailure();
baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
Assert.Success(saveFs.GetEntryType(out _, "/file1"));
Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file2"));
}
[Fact]
public void Initialize_InterruptedAfterCommitPart1_UsesModifiedData()
{
var baseFs = new InMemoryFileSystem();
baseFs.CreateDirectory("/_").ThrowIfFailure();
baseFs.CreateDirectory("/1").ThrowIfFailure();
// Set the existing files before initializing the save FS
baseFs.CreateFile("/_/file1", 0, CreateFileOptions.None).ThrowIfFailure();
baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1"));
Assert.Success(saveFs.GetEntryType(out _, "/file2"));
}
[Fact]
public void Initialize_InterruptedDuringCommitPart2_UsesModifiedData()
{
var baseFs = new InMemoryFileSystem();
baseFs.CreateDirectory("/1").ThrowIfFailure();
// Set the existing files before initializing the save FS
baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1"));
Assert.Success(saveFs.GetEntryType(out _, "/file2"));
}
[Fact]
public void Initialize_InitialExtraDataIsEmpty()
{
(IFileSystem _, DirectorySaveDataFileSystem saveFs) = CreateFileSystemInternal();
Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData));
Assert.True(SpanHelpers.AsByteSpan(ref extraData).IsZeros());
}
[Fact]
public void WriteExtraData_CanReadBackExtraData()
{
(IFileSystem _, DirectorySaveDataFileSystem saveFs) = CreateFileSystemInternal();
var originalExtraData = new SaveDataExtraData();
originalExtraData.DataSize = 0x12345;
Assert.Success(saveFs.WriteExtraData(in originalExtraData));
Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData));
Assert.Equal(originalExtraData, extraData);
}
[Fact]
public void Commit_AfterSuccessfulCommit_CanReadCommittedExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
var originalExtraData = new SaveDataExtraData();
originalExtraData.DataSize = 0x12345;
saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure();
Assert.Success(saveFs.CommitExtraData(false));
saveFs.Dispose();
CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure();
Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData));
Assert.Equal(originalExtraData, extraData);
}
[Fact]
public void Rollback_WriteExtraDataThenRollback_ExtraDataIsRolledBack()
{
var baseFs = new InMemoryFileSystem();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
var originalExtraData = new SaveDataExtraData();
originalExtraData.DataSize = 0x12345;
saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure();
saveFs.CommitExtraData(false).ThrowIfFailure();
saveFs.Dispose();
CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure();
var newExtraData = new SaveDataExtraData();
newExtraData.DataSize = 0x67890;
saveFs.WriteExtraData(in newExtraData).ThrowIfFailure();
Assert.Success(saveFs.Rollback());
Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData));
Assert.Equal(originalExtraData, extraData);
}
[Fact]
public void Rollback_WriteExtraDataThenCloseFs_ExtraDataIsRolledBack()
{
var baseFs = new InMemoryFileSystem();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
// Write extra data and close with committing
var originalExtraData = new SaveDataExtraData();
originalExtraData.DataSize = 0x12345;
saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure();
saveFs.CommitExtraData(false).ThrowIfFailure();
saveFs.Dispose();
CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure();
// Write a new extra data and close without committing
var newExtraData = new SaveDataExtraData();
newExtraData.DataSize = 0x67890;
saveFs.WriteExtraData(in newExtraData).ThrowIfFailure();
saveFs.Dispose();
// Read extra data should match the first one
CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure();
Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData));
Assert.Equal(originalExtraData, extraData);
}
[Fact]
public void Initialize_InterruptedAfterCommitPart1_UsesModifiedExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateExtraDataForTest(baseFs, "/ExtraData_", 0x12345).ThrowIfFailure();
CreateExtraDataForTest(baseFs, "/ExtraData1", 0x67890).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(0x67890, extraData.DataSize);
}
[Fact]
public void Initialize_OnlySynchronizingExtraDataExists_UsesSynchronizingExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateExtraDataForTest(baseFs, "/ExtraData_", 0x67890).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(0x67890, extraData.DataSize);
}
[Fact]
public void Initialize_OnlyCommittedExtraDataExists_UsesCommittedExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateExtraDataForTest(baseFs, "/ExtraData0", 0x67890).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(0x67890, extraData.DataSize);
}
[Fact]
public void Initialize_OnlyModifiedExtraDataExists_UsesModifiedExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateExtraDataForTest(baseFs, "/ExtraData1", 0x67890).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(0x67890, extraData.DataSize);
}
[Fact]
public void Initialize_SynchronizingAndCommittedExtraDataExist_UsesCommittedExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateExtraDataForTest(baseFs, "/ExtraData_", 0x12345).ThrowIfFailure();
CreateExtraDataForTest(baseFs, "/ExtraData0", 0x67890).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(0x67890, extraData.DataSize);
}
[Fact]
public void Initialize_SynchronizingAndModifiedExtraDataExist_UsesModifiedExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateExtraDataForTest(baseFs, "/ExtraData_", 0x12345).ThrowIfFailure();
CreateExtraDataForTest(baseFs, "/ExtraData1", 0x67890).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(0x67890, extraData.DataSize);
}
[Fact]
public void Initialize_CommittedAndModifiedExtraDataExist_UsesCommittedExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateExtraDataForTest(baseFs, "/ExtraData0", 0x12345).ThrowIfFailure();
CreateExtraDataForTest(baseFs, "/ExtraData1", 0x67890).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(0x12345, extraData.DataSize);
}
[Fact]
public void Initialize_AllThreeExtraDataFilesExist_UsesCommittedExtraData()
{
var baseFs = new InMemoryFileSystem();
CreateExtraDataForTest(baseFs, "/ExtraData_", 0x12345).ThrowIfFailure();
CreateExtraDataForTest(baseFs, "/ExtraData0", 0x54321).ThrowIfFailure();
CreateExtraDataForTest(baseFs, "/ExtraData1", 0x67890).ThrowIfFailure();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(0x54321, extraData.DataSize);
}
[Fact]
public void CommitSaveData_MultipleCommits_CommitIdIsUpdatedSkippingInvalidIds()
{
var random = new RandomGenerator();
RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer);
var timeStampGetter = new TimeStampGetter();
var baseFs = new InMemoryFileSystem();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter,
randomGeneratorFunc, true, true, true, null).ThrowIfFailure();
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(2, extraData.CommitId);
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(3, extraData.CommitId);
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(6, extraData.CommitId);
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(2, extraData.CommitId);
}
[Fact]
public void CommitSaveData_MultipleCommits_TimeStampUpdated()
{
var random = new RandomGenerator();
RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer);
var timeStampGetter = new TimeStampGetter();
var baseFs = new InMemoryFileSystem();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter,
randomGeneratorFunc, true, true, true, null).ThrowIfFailure();
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(1u, extraData.TimeStamp);
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(2u, extraData.TimeStamp);
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(3u, extraData.TimeStamp);
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(4u, extraData.TimeStamp);
}
[Fact]
public void CommitSaveData_UpdateTimeStampIsFalse_TimeStampAndCommitIdAreNotUpdated()
{
var random = new RandomGenerator();
RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer);
var timeStampGetter = new TimeStampGetter();
var baseFs = new InMemoryFileSystem();
CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter,
randomGeneratorFunc, true, true, true, null).ThrowIfFailure();
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure();
Assert.Equal(1u, extraData.TimeStamp);
Assert.Equal(2, extraData.CommitId);
saveFs.CommitExtraData(false).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(1u, extraData.TimeStamp);
Assert.Equal(2, extraData.CommitId);
saveFs.CommitExtraData(true).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(2u, extraData.TimeStamp);
Assert.Equal(3, extraData.CommitId);
saveFs.CommitExtraData(false).ThrowIfFailure();
saveFs.ReadExtraData(out extraData).ThrowIfFailure();
Assert.Equal(2u, extraData.TimeStamp);
Assert.Equal(3, extraData.CommitId);
}
private class TimeStampGetter : ISaveDataCommitTimeStampGetter
{
private long _currentTimeStamp = 1;
public Result Get(out long timeStamp)
{
timeStamp = _currentTimeStamp++;
return Result.Success;
}
}
private class RandomGenerator
{
private static readonly int[] Values = { 2, 0, 3, 3, 6, 0 };
private int _index;
public void GenerateRandom(Span<byte> output)
{
if (output.Length != 8)
throw new ArgumentException();
Unsafe.As<byte, long>(ref MemoryMarshal.GetReference(output)) = Values[_index];
_index = (_index + 1) % Values.Length;
}
}
private Result CreateExtraDataForTest(IFileSystem fileSystem, string path, int saveDataSize)
{
fileSystem.DeleteFile(path).IgnoreResult();
Result res = fileSystem.CreateFile(path, Unsafe.SizeOf<SaveDataExtraData>());
if (res.IsFailure()) return res.Miss();
var extraData = new SaveDataExtraData();
extraData.DataSize = saveDataSize;
using var file = new UniqueRef<IFile>();
res = fileSystem.OpenFile(ref file.Ref(), path, OpenMode.ReadWrite);
if (res.IsFailure()) return res.Miss();
res = file.Get.Write(0, SpanHelpers.AsByteSpan(ref extraData), WriteOption.Flush);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
}