diff --git a/Aaru.Tests/Aaru.Tests.csproj b/Aaru.Tests/Aaru.Tests.csproj index 9431ecd15..e55bc7209 100644 --- a/Aaru.Tests/Aaru.Tests.csproj +++ b/Aaru.Tests/Aaru.Tests.csproj @@ -28,6 +28,7 @@ + diff --git a/Aaru.Tests/Archives/ArchiveTest.cs b/Aaru.Tests/Archives/ArchiveTest.cs new file mode 100644 index 000000000..aab3c6a8b --- /dev/null +++ b/Aaru.Tests/Archives/ArchiveTest.cs @@ -0,0 +1,179 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; +using Aaru.Core; +using FluentAssertions; +using FluentAssertions.Execution; +using NUnit.Framework; +using FileAttributes = Aaru.CommonTypes.Structs.FileAttributes; + +namespace Aaru.Tests.Archives; + +public abstract class ArchiveTest +{ + public abstract string DataFolder { get; } + public abstract IArchive Plugin { get; } + public abstract ArchiveTestExpected[] Tests { get; } + + [OneTimeSetUp] + public void InitTest() => PluginBase.Init(); + + [Test] + public void Identify() + { + Environment.CurrentDirectory = DataFolder; + + using(new AssertionScope()) + { + foreach(ArchiveTestExpected test in Tests) + { + string testFile = test.TestFile; + + bool exists = File.Exists(testFile); + exists.Should().BeTrue(Localization._0_not_found, testFile); + + if(!exists) continue; + + IFilter filter = PluginRegister.Singleton.GetFilter(testFile); + filter.Should().NotBeNull(Localization.Filter_0, testFile); + + ErrorNumber openedFilter = filter.Open(testFile); + openedFilter.Should().Be(ErrorNumber.NoError, string.Format(Localization.Open_0, testFile)); + + if(openedFilter != ErrorNumber.NoError) continue; + + var archive = Activator.CreateInstance(Plugin.GetType()) as IArchive; + archive.Should().NotBeNull(Localization.Could_not_instantiate_filesystem_for_0, testFile); + + archive.Identify(filter).Should().BeTrue(Localization.Not_identified_for_0, testFile); + + filter.Close(); + } + } + } + + [Test] + public void Contents() + { + Environment.CurrentDirectory = DataFolder; + + using(new AssertionScope()) + { + foreach(ArchiveTestExpected test in Tests) + { + string testFile = test.TestFile; + + bool exists = File.Exists(testFile); + exists.Should().BeTrue(Localization._0_not_found, testFile); + + if(!exists) continue; + + IFilter filter = PluginRegister.Singleton.GetFilter(testFile); + filter.Should().NotBeNull(Localization.Filter_0, testFile); + + ErrorNumber openedFilter = filter.Open(testFile); + openedFilter.Should().Be(ErrorNumber.NoError, string.Format(Localization.Open_0, testFile)); + + if(openedFilter != ErrorNumber.NoError) continue; + + var archive = Activator.CreateInstance(Plugin.GetType()) as IArchive; + archive.Should().NotBeNull(Localization.Could_not_instantiate_filesystem_for_0, testFile); + + ErrorNumber openedArchive = archive.Open(filter, Encoding.ASCII); + openedArchive.Should().Be(ErrorNumber.NoError, string.Format(Localization.Open_0, testFile)); + + if(openedArchive != ErrorNumber.NoError) + { + filter.Close(); + + continue; + } + + archive.NumberOfEntries.Should() + .Be(test.EntryCount, + string.Format(Localization.Expected_0_partitions_in_1_but_found_2, + test.EntryCount, + testFile, + archive.NumberOfEntries)); + + ArchiveEntryData[] expectedEntries = test.Contents; + + if(expectedEntries is null && File.Exists($"{testFile}.contents.json")) + { + JsonSerializerOptions serializerOptions = new() + { + PropertyNameCaseInsensitive = true + }; + + using FileStream stream = new($"{testFile}.contents.json", FileMode.Open, FileAccess.Read); + expectedEntries = JsonSerializer.Deserialize(stream, serializerOptions); + } + + expectedEntries.Should().NotBeNull(); + + if(expectedEntries is null) + { + filter.Close(); + archive.Close(); + + continue; + } + + expectedEntries.Length.Should() + .Be(archive.NumberOfEntries, + string.Format(Localization.Expected_0_partitions_in_1_but_found_2, + expectedEntries.Length, + testFile, + archive.NumberOfEntries)); + + using(new AssertionScope()) + { + for(var i = 0; i < archive.NumberOfEntries; i++) + { + ErrorNumber filenameErrno = archive.GetFilename(i, out string fileName); + + filenameErrno.Should() + .Be(ErrorNumber.NoError, + string.Format(Localization.Cannot_open_image_for_0, testFile)); + + ErrorNumber statErrno = archive.Stat(i, out FileEntryInfo stat); + + statErrno.Should() + .Be(ErrorNumber.NoError, + string.Format(Localization.Cannot_open_image_for_0, testFile)); + + string entryType = GetEntryType(stat.Attributes); + long entrySize = stat.Length; + + fileName.Should().Be(expectedEntries[i].Path, testFile); + entryType.Should().Be(expectedEntries[i].Type, testFile); + entrySize.Should().Be(expectedEntries[i].Size, testFile); + } + } + + archive.Close(); + filter.Close(); + } + } + } + + static string GetEntryType(FileAttributes attributes) + { + if(attributes.HasFlag(FileAttributes.Directory)) return "directory"; + + if(attributes.HasFlag(FileAttributes.Symlink)) return "symlink"; + + if(attributes.HasFlag(FileAttributes.CharDevice)) return "chardevice"; + + if(attributes.HasFlag(FileAttributes.BlockDevice)) return "blockdevice"; + + if(attributes.HasFlag(FileAttributes.FIFO)) return "fifo"; + + return "file"; + } +} \ No newline at end of file diff --git a/Aaru.Tests/Structs.cs b/Aaru.Tests/Structs.cs index e3aa1eb97..d604b8da8 100644 --- a/Aaru.Tests/Structs.cs +++ b/Aaru.Tests/Structs.cs @@ -10,12 +10,12 @@ public class MediaInfoTest { /// Expected media type public MediaType MediaType; - /// Expected number of sectors in media - public ulong Sectors; /// Expected media sector size - public uint SectorSize; + public uint SectorSize; + /// Expected number of sectors in media + public ulong Sectors; /// File that contains the image to test - public string TestFile; + public string TestFile; public override string ToString() => TestFile; } @@ -25,25 +25,25 @@ public class MediaInfoTest public class FileSystemTest : MediaInfoTest { /// Application ID - public string ApplicationId; + public string ApplicationId; /// Can the volume boot? - public bool Bootable; - /// Clusters in volume - public long Clusters; + public bool Bootable; /// Bytes per cluster - public uint ClusterSize; + public uint ClusterSize; + /// Clusters in volume + public long Clusters; public Dictionary Contents; public string ContentsJson; public Encoding Encoding; public string Namespace; /// System or OEM ID - public string SystemId; + public string SystemId; /// Filesystem type. null if always the same, as defined in test class - public string Type; + public string Type; /// Volume name - public string VolumeName; + public string VolumeName; /// Volume serial number or set identifier - public string VolumeSerial; + public string VolumeSerial; } public class BlockImageTestExpected : MediaInfoTest @@ -52,6 +52,24 @@ public class BlockImageTestExpected : MediaInfoTest public BlockPartitionVolumes[] Partitions; } +public class ArchiveTestExpected +{ + public ArchiveEntryData[] Contents; + public string ContentsJson; + public int EntryCount; + public string TestFile; + + public override string ToString() => TestFile; +} + +public class ArchiveEntryData +{ + public string Path { get; set; } + public long Size { get; set; } + public string Type { get; set; } + public string LinkTarget { get; set; } +} + public class TrackInfoTestExpected { public ulong End; @@ -78,24 +96,24 @@ public class TapeImageTestExpected : BlockImageTestExpected public class FluxCaptureTestExpected { + /// Capture index for this head/track/subTrack combination + public uint CaptureIndex; + /// Expected data resolution in picoseconds + public ulong DataResolution; /// Physical head (0-based) - public uint Head; + public uint Head; + /// Expected index resolution in picoseconds + public ulong IndexResolution; + /// Physical sub-track (0-based, e.g. half-track) + public byte SubTrack; /// Physical track (0-based) public ushort Track; - /// Physical sub-track (0-based, e.g. half-track) - public byte SubTrack; - /// Capture index for this head/track/subTrack combination - public uint CaptureIndex; - /// Expected index resolution in picoseconds - public ulong IndexResolution; - /// Expected data resolution in picoseconds - public ulong DataResolution; } public class FluxImageTestExpected : BlockImageTestExpected { /// Expected number of flux captures in the image - public uint FluxCaptureCount; + public uint FluxCaptureCount; /// Expected flux captures to validate public FluxCaptureTestExpected[] FluxCaptures; } @@ -104,7 +122,7 @@ public class PartitionTest { public Partition[] Partitions; /// File that contains the partition scheme to test - public string TestFile; + public string TestFile; } public class FsExtractHashData