From acf4138cbf75e7cc9360097a89a69ead7905a2ca Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Mon, 31 May 2021 18:21:39 +0100 Subject: [PATCH] Create abstract class to test read only filesystem implementations. --- .../Filesystems/ReadOnlyFilesystemTest.cs | 272 ++++++++++++++++++ Aaru.Tests/Structs.cs | 12 +- 2 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 Aaru.Tests/Filesystems/ReadOnlyFilesystemTest.cs diff --git a/Aaru.Tests/Filesystems/ReadOnlyFilesystemTest.cs b/Aaru.Tests/Filesystems/ReadOnlyFilesystemTest.cs new file mode 100644 index 000000000..069423428 --- /dev/null +++ b/Aaru.Tests/Filesystems/ReadOnlyFilesystemTest.cs @@ -0,0 +1,272 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Aaru.Checksums; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; +using Aaru.Core; +using FluentAssertions; +using NUnit.Framework; +using FileAttributes = Aaru.CommonTypes.Structs.FileAttributes; +using FileSystemInfo = Aaru.CommonTypes.Structs.FileSystemInfo; + +namespace Aaru.Tests.Filesystems +{ + public abstract class ReadOnlyFilesystemTest : FilesystemTest + { + public ReadOnlyFilesystemTest() {} + + public ReadOnlyFilesystemTest(string fileSystemType) : base(fileSystemType) {} + + [Test] + public void Contents() + { + Environment.CurrentDirectory = DataFolder; + + Assert.Multiple(() => + { + foreach(FileSystemTest test in Tests) + { + string testFile = test.TestFile; + bool found = false; + var partition = new Partition(); + + bool exists = File.Exists(testFile); + Assert.True(exists, $"{testFile} not found"); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // It arrives here... + if(!exists) + continue; + + var filtersList = new FiltersList(); + IFilter inputFilter = filtersList.GetFilter(testFile); + + Assert.IsNotNull(inputFilter, $"Filter: {testFile}"); + + IMediaImage image = ImageFormat.Detect(inputFilter); + + Assert.IsNotNull(image, $"Image format: {testFile}"); + + Assert.AreEqual(true, image.Open(inputFilter), $"Cannot open image for {testFile}"); + + List idPlugins; + + if(Partitions) + { + List partitionsList = Core.Partitions.GetAll(image); + + Assert.Greater(partitionsList.Count, 0, $"No partitions found for {testFile}"); + + // In reverse to skip boot partitions we're not interested in + for(int index = partitionsList.Count - 1; index >= 0; index--) + { + Core.Filesystems.Identify(image, out idPlugins, partitionsList[index], true); + + if(idPlugins.Count == 0) + continue; + + if(!idPlugins.Contains(Plugin.Id.ToString())) + continue; + + found = true; + partition = partitionsList[index]; + + break; + } + } + else + { + partition = new Partition + { + Name = "Whole device", + Length = image.Info.Sectors, + Size = image.Info.Sectors * image.Info.SectorSize + }; + + Core.Filesystems.Identify(image, out idPlugins, partition, true); + + Assert.Greater(idPlugins.Count, 0, $"No filesystems found for {testFile}"); + + found = idPlugins.Contains(Plugin.Id.ToString()); + } + + Assert.True(found, $"Filesystem not identified for {testFile}"); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // It is not the case, it changes + if(!found) + continue; + + if(test.Contents is null) + continue; + + var fs = Activator.CreateInstance(Plugin.GetType()) as IReadOnlyFilesystem; + + Assert.NotNull(fs, $"Could not instantiate filesystem for {testFile}"); + + Errno ret = fs.Mount(image, partition, test.Encoding, null, test.Namespace); + + Assert.AreEqual(Errno.NoError, ret, $"Unmountable: {testFile}"); + + ret = fs.StatFs(out FileSystemInfo stat); + + Assert.AreEqual(Errno.NoError, ret, $"Unexpected error retrieving filesystem stats for {testFile}"); + + stat.Should().BeEquivalentTo(test.Info, $"Incorrect filesystem stats for {testFile}"); + + TestDirectory(fs, "/", test.Contents, testFile); + } + }); + } + + void TestDirectory(IReadOnlyFilesystem fs, string path, Dictionary children, string testFile) + { + Errno ret = fs.ReadDir(path, out List contents); + + Assert.AreEqual(Errno.NoError, ret, + $"Unexpected error {ret} when reading directory \"{path}\" of {testFile}."); + + if(children.Count == 0 && + contents.Count == 0) + return; + + if(path == "/") + path = ""; + + List expectedNotFound = new List(); + + foreach(KeyValuePair child in children) + { + string childPath = $"{path}/{child.Key}"; + ret = fs.Stat(childPath, out FileEntryInfo stat); + + if(ret == Errno.NoSuchFile || + !contents.Contains(child.Key)) + { + expectedNotFound.Add(child.Key); + + continue; + } + + contents.Remove(child.Key); + + Assert.AreEqual(Errno.NoError, ret, + $"Unexpected error {ret} retrieving stats for \"{childPath}\" in {testFile}"); + + stat.Should().BeEquivalentTo(child.Value.Info, $"Wrong info for \"{childPath}\" in {testFile}"); + + byte[] buffer = new byte[0]; + + if(child.Value.Info.Attributes.HasFlag(FileAttributes.Directory)) + { + ret = fs.Read(childPath, 0, 1, ref buffer); + + Assert.AreEqual(Errno.IsDirectory, ret, + $"Got wrong data for directory \"{childPath}\" in {testFile}"); + + Assert.IsNotNull(child.Value.Children, + $"Contents for \"{childPath}\" in {testFile} must be defined in unit test declaration!"); + + if(child.Value.Children != null) + TestDirectory(fs, childPath, child.Value.Children, testFile); + } + else if(child.Value.Info.Attributes.HasFlag(FileAttributes.Symlink)) + { + ret = fs.ReadLink(childPath, out string link); + + Assert.AreEqual(Errno.NoError, ret, + $"Got wrong data for symbolic link \"{childPath}\" in {testFile}"); + + Assert.AreEqual(child.Value.LinkTarget, link, + $"Invalid target for symbolic link \"{childPath}\" in {testFile}"); + } + else + + // This ensure the buffer does not hang for collection + TestFile(fs, childPath, child.Value.MD5, child.Value.Info.Length, testFile); + + ret = fs.ListXAttr(childPath, out List xattrs); + + if(ret == Errno.NotSupported) + { + Assert.IsNull(child.Value.XattrsWithMd5, + $"Defined extended attributes for \"{childPath}\" in {testFile} are not supported by filesystem."); + + continue; + } + + Assert.AreEqual(Errno.NoError, ret, + $"Unexpected error {ret} when listing extended attributes for \"{childPath}\" in {testFile}"); + + if(xattrs.Count > 0) + Assert.IsNotNull(child.Value.XattrsWithMd5, + $"Extended attributes for \"{childPath}\" in {testFile} must be defined in unit test declaration!"); + + if(xattrs.Count > 0 || + child.Value.XattrsWithMd5?.Count > 0) + TestFileXattrs(fs, childPath, child.Value.XattrsWithMd5, testFile); + } + + Assert.IsEmpty(expectedNotFound, + $"Could not find the children of \"{path}\" in {testFile}: {string.Join(" ", expectedNotFound)}"); + + Assert.IsEmpty(contents, + $"Found the following unexpected children of \"{path}\" in {testFile}: {string.Join(" ", contents)}"); + } + + void TestFile(IReadOnlyFilesystem fs, string path, string md5, long length, string testFile) + { + byte[] buffer = new byte[length]; + Errno ret = fs.Read(path, 0, length, ref buffer); + + Assert.AreEqual(Errno.NoError, ret, $"Unexpected error {ret} when reading \"{path}\" in {testFile}"); + + string data = Md5Context.Data(buffer, out _); + + Assert.AreEqual(md5, data, $"Got MD5 {data} for \"{path}\" in {testFile} but expected {md5}"); + } + + void TestFileXattrs(IReadOnlyFilesystem fs, string path, Dictionary xattrs, string testFile) + { + fs.ListXAttr(path, out List contents); + + if(xattrs.Count == 0 && + contents.Count == 0) + return; + + List expectedNotFound = new List(); + + foreach(KeyValuePair xattr in xattrs) + { + byte[] buffer = new byte[0]; + Errno ret = fs.GetXattr(path, xattr.Key, ref buffer); + + if(ret == Errno.NoSuchExtendedAttribute || + !contents.Contains(xattr.Key)) + { + expectedNotFound.Add(xattr.Key); + + continue; + } + + contents.Remove(xattr.Key); + + Assert.AreEqual(Errno.NoError, ret, + $"Unexpected error {ret} retrieving extended attributes for \"{path}\" in {testFile}"); + + string data = Md5Context.Data(buffer, out _); + + Assert.AreEqual(xattr.Value, data, + $"Got MD5 {data} for {xattr.Key} of \"{path}\" in {testFile} but expected {xattr.Value}"); + } + + Assert.IsEmpty(expectedNotFound, + $"Could not find the following extended attributes of \"{path}\" in {testFile}: {string.Join(" ", expectedNotFound)}"); + + Assert.IsEmpty(contents, + $"Found the following unexpected extended attributes of \"{path}\" in {testFile}: {string.Join(" ", contents)}"); + } + } +} \ No newline at end of file diff --git a/Aaru.Tests/Structs.cs b/Aaru.Tests/Structs.cs index ec5940c1b..f63f19a8c 100644 --- a/Aaru.Tests/Structs.cs +++ b/Aaru.Tests/Structs.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text; using Aaru.CommonTypes; using Aaru.CommonTypes.Structs; @@ -30,6 +31,10 @@ namespace Aaru.Tests public long Clusters; /// Bytes per cluster public uint ClusterSize; + public Dictionary Contents; + public Encoding Encoding; + public FileSystemInfo Info; + public string Namespace; /// System or OEM ID public string SystemId; /// Filesystem type. null if always the same, as defined in test class @@ -86,8 +91,11 @@ namespace Aaru.Tests public class FileData { - public string MD5; - public Dictionary XattrsWithMd5; + public Dictionary Children; + public FileEntryInfo Info; + public string LinkTarget; + public string MD5; + public Dictionary XattrsWithMd5; } public class VolumeData