sharpemu/tests/SharpEmu.Libs.Tests/FileSystem/ZArchiveTests.cs

117 lines
4.1 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE.FileSystem;
using Xunit;
namespace SharpEmu.Libs.Tests.FileSystem;
public sealed class ZArchiveTests : IDisposable
{
private readonly string _directory = Directory.CreateTempSubdirectory("sharpemu-zar-").FullName;
public void Dispose() => Directory.Delete(_directory, recursive: true);
private string Write(params (string Path, byte[] Data)[] files)
{
var path = Path.Combine(_directory, $"{Guid.NewGuid():N}.zar");
File.WriteAllBytes(path, ZArchiveTestWriter.Build(files));
return path;
}
private static byte[] Pattern(int length, int seed)
{
var bytes = new byte[length];
for (var index = 0; index < length; index++)
{
bytes[index] = (byte)((index * 31 + seed) ^ (index >> 9));
}
return bytes;
}
[Fact]
public void FilesReadBackAcrossBlocksAndDirectories()
{
var eboot = Pattern(200_000, 1);
var param = Pattern(1_234, 2);
var data = Pattern(70_000, 3);
using var archive = ZArchive.Open(Write(("eboot.bin", eboot), ("sce_sys/param.json", param), ("data/level/a.bin", data)))!;
Assert.NotNull(archive);
foreach (var (path, expected) in new[] { ("eboot.bin", eboot), ("sce_sys/param.json", param), ("data/level/a.bin", data) })
{
var node = archive.LookUp(path);
Assert.True(archive.IsFile(node));
Assert.Equal((ulong)expected.Length, archive.GetFileSize(node));
var actual = new byte[expected.Length];
Assert.Equal(expected.Length, archive.Read(node, 0, actual));
Assert.Equal(expected, actual);
}
}
[Fact]
public void AReadAtAnOffsetSpansBlocksAndStopsAtTheEnd()
{
var bytes = Pattern(150_000, 7);
using var archive = ZArchive.Open(Write(("big.bin", bytes)))!;
var node = archive.LookUp("big.bin");
var window = new byte[20_000];
Assert.Equal(window.Length, archive.Read(node, 60_000, window));
Assert.Equal(bytes.AsSpan(60_000, window.Length).ToArray(), window);
var tail = new byte[1000];
Assert.Equal(100, archive.Read(node, 149_900, tail));
Assert.Equal(bytes.AsSpan(149_900, 100).ToArray(), tail.AsSpan(0, 100).ToArray());
Assert.Equal(0, archive.Read(node, 150_000, tail));
}
[Fact]
public void LookupIgnoresCaseAndAcceptsEitherSeparator()
{
using var archive = ZArchive.Open(Write(("sce_sys/Param.JSON", [1, 2, 3])))!;
Assert.True(archive.IsFile(archive.LookUp("SCE_SYS\\param.json")));
Assert.True(archive.IsDirectory(archive.LookUp("/sce_sys/")));
Assert.True(archive.IsDirectory(archive.LookUp(string.Empty)));
Assert.Equal(ZArchive.InvalidNode, archive.LookUp("sce_sys/missing.json"));
Assert.Equal(ZArchive.InvalidNode, archive.LookUp("sce_sys/param.json/inner"));
}
[Fact]
public void DirectoriesListTheirEntries()
{
using var archive = ZArchive.Open(Write(("dir/b.bin", [1]), ("dir/a.bin", [1, 2]), ("dir/sub/c.bin", [3])))!;
var entries = archive.GetEntries(archive.LookUp("dir")).OrderBy(entry => entry.Name).ToArray();
Assert.Equal(["a.bin", "b.bin", "sub"], entries.Select(entry => entry.Name));
Assert.Equal(2UL, entries[0].Size);
Assert.True(entries[2].IsDirectory);
}
[Fact]
public void LongNamesAndIncompressibleBlocksReadBack()
{
var name = new string('n', 300) + ".bin";
var random = new byte[100_000];
new System.Random(5).NextBytes(random);
using var archive = ZArchive.Open(Write((name, random)))!;
var node = archive.LookUp(name);
var actual = new byte[random.Length];
Assert.Equal(random.Length, archive.Read(node, 0, actual));
Assert.Equal(random, actual);
}
[Fact]
public void AFileThatIsNotAnArchiveDoesNotOpen()
{
var path = Path.Combine(_directory, "plain.zar");
File.WriteAllBytes(path, Pattern(4096, 9));
Assert.Null(ZArchive.Open(path));
}
}