From 9edb35c015e936919b90b855c8bf4bfab8d48ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=BCller?= Date: Thu, 13 Nov 2025 08:22:03 +0100 Subject: [PATCH] Remove packing --- CHANGELOG.md | 2 - src/AMWD.Common/Packing/Ar/ArFileInfo.cs | 51 --- src/AMWD.Common/Packing/Ar/ArReader.cs | 176 --------- src/AMWD.Common/Packing/Ar/ArWriter.cs | 146 -------- .../Packing/Ar/ArReaderTest.cs | 353 ------------------ .../Packing/Ar/ArWriterTest.cs | 304 --------------- 6 files changed, 1032 deletions(-) delete mode 100644 src/AMWD.Common/Packing/Ar/ArFileInfo.cs delete mode 100644 src/AMWD.Common/Packing/Ar/ArReader.cs delete mode 100644 src/AMWD.Common/Packing/Ar/ArWriter.cs delete mode 100644 test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs delete mode 100644 test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a21db0..bcde64f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `ArReader` and `ArWriter` for Unix archives -- `TarReader` and `TarWriter` for TAR archives - `.AddRange()` for collections - `.AddIfNotNull()` for collections - `DomainComparer` ordering alphabetically from TLD to sub-domain diff --git a/src/AMWD.Common/Packing/Ar/ArFileInfo.cs b/src/AMWD.Common/Packing/Ar/ArFileInfo.cs deleted file mode 100644 index f0cbc38..0000000 --- a/src/AMWD.Common/Packing/Ar/ArFileInfo.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; - -namespace AMWD.Common.Packing.Ar -{ - /// - /// Represents the file information saved in the archive. - /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - public class ArFileInfo - { - /// - /// Gets or sets the file name. - /// - public string FileName { get; set; } - - /// - /// Gets or sets the file size in bytes. - /// - public long FileSize { get; set; } - - /// - /// Gets or sets the timestamp of the last modification. - /// - public DateTime ModifyTime { get; set; } - - /// - /// Gets or sets the user id. - /// - public int UserId { get; set; } - - /// - /// Gets or sets the group id. - /// - public int GroupId { get; set; } - - /// - /// Gets or sets the access mode in decimal (not octal!). - /// - /// - /// To see the octal representation use Convert.ToString(Mode, 8). - /// - public int Mode { get; set; } - } - - internal class ArFileInfoExtended : ArFileInfo - { - public long HeaderPosition { get; set; } - - public long DataPosition { get; set; } - } -} diff --git a/src/AMWD.Common/Packing/Ar/ArReader.cs b/src/AMWD.Common/Packing/Ar/ArReader.cs deleted file mode 100644 index 821b3e6..0000000 --- a/src/AMWD.Common/Packing/Ar/ArReader.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace AMWD.Common.Packing.Ar -{ - /// - /// Reads UNIX ar (archive) files in the GNU format. - /// - public class ArReader - { - // Source: http://en.wikipedia.org/wiki/Ar_%28Unix%29 - - private readonly Stream _inStream; - private readonly List _files = []; - private readonly long _streamStartPosition; - - /// - /// Initializes a new instance of the class. - /// - /// The stream to read the archive from. - public ArReader(Stream inStream) - { - if (!inStream.CanRead || !inStream.CanSeek) - throw new ArgumentException("Stream not readable or seekable", nameof(inStream)); - - _streamStartPosition = inStream.Position; - _inStream = inStream; - - Initialize(); - } - - /// - /// Returns a list with all filenames of the archive. - /// - public IEnumerable GetFileList() - => _files.Select(fi => fi.FileName).ToList(); - - /// - /// Returns the file info of a specific file in the archive. - /// - /// The name of the specific file. - public ArFileInfo GetFileInfo(string fileName) - { - return _files - .Where(fi => fi.FileName == fileName) - .Select(fi => new ArFileInfo - { - FileName = fi.FileName, - FileSize = fi.FileSize, - GroupId = fi.GroupId, - Mode = fi.Mode, - ModifyTime = fi.ModifyTime, - UserId = fi.UserId - }) - .FirstOrDefault(); - } - - /// - /// Reads a file from the archive into a stream. - /// - /// The file name in the archive. - /// The output stream. - public void ReadFile(string fileName, Stream outStream) - { - if (!outStream.CanWrite) - throw new ArgumentException("Stream not writable", nameof(outStream)); - - var info = _files.Where(fi => fi.FileName == fileName).FirstOrDefault(); - if (info == null) - return; - - long bytesToRead = info.FileSize; - byte[] buffer = new byte[1024 * 1024]; - - _inStream.Seek(info.DataPosition, SeekOrigin.Begin); - while (bytesToRead > 0) - { - int readCount = (int)Math.Min(bytesToRead, buffer.Length); - _inStream.Read(buffer, 0, readCount); - outStream.Write(buffer, 0, readCount); - - bytesToRead -= readCount; - } - _inStream.Seek(_streamStartPosition, SeekOrigin.Begin); - } - - /// - /// Reads a fie from the archive and saves it to disk. - /// - /// The file name in the archive. - /// The destination path on disk. - public void ReadFile(string fileName, string destinationPath) - { - var info = _files.Where(fi => fi.FileName == fileName).FirstOrDefault(); - if (info == null) - return; - - using (var fs = File.OpenWrite(destinationPath)) - { - ReadFile(fileName, fs); - } - File.SetLastWriteTimeUtc(destinationPath, info.ModifyTime); - } - - private void Initialize() - { - // Reset stream - _inStream.Seek(_streamStartPosition, SeekOrigin.Begin); - - // Read header - string header = ReadAsciiString(8); - if (header != "!\n") - throw new FormatException("The file stream is no archive"); - - // Create file list - while (_inStream.Position < _inStream.Length) - { - var info = ReadFileHeader(); - _files.Add(info); - - // Move stream behind file content - _inStream.Seek(info.FileSize, SeekOrigin.Current); - - // Align to even offsets (padded with LF bytes) - if (_inStream.Position % 2 != 0) - _inStream.Seek(1, SeekOrigin.Current); - } - - // Reset stream - _inStream.Seek(_streamStartPosition, SeekOrigin.Begin); - } - - private string ReadAsciiString(int byteCount) - { - byte[] buffer = new byte[byteCount]; - _inStream.Read(buffer, 0, byteCount); - return Encoding.ASCII.GetString(buffer); - } - - private ArFileInfoExtended ReadFileHeader() - { - long startPosition = _inStream.Position; - - string fileName = ReadAsciiString(16).Trim(); - - int.TryParse(ReadAsciiString(12).Trim(), out int unixTimestamp); - int.TryParse(ReadAsciiString(6).Trim(), out int userId); - int.TryParse(ReadAsciiString(6).Trim(), out int groupId); - int mode = Convert.ToInt32(ReadAsciiString(8).Trim(), 8); - - long.TryParse(ReadAsciiString(10).Trim(), out long fileSize); - - // file magic - byte[] magic = new byte[2]; - _inStream.Read(magic, 0, magic.Length); - - if (magic[0] != 0x60 || magic[1] != 0x0A) // `\n - throw new FormatException("Invalid file magic"); - - return new ArFileInfoExtended - { - HeaderPosition = startPosition, - DataPosition = _inStream.Position, - FileName = fileName, - ModifyTime = DateTimeOffset.FromUnixTimeSeconds(unixTimestamp).DateTime, - UserId = userId, - GroupId = groupId, - Mode = mode, - FileSize = fileSize - }; - } - } -} diff --git a/src/AMWD.Common/Packing/Ar/ArWriter.cs b/src/AMWD.Common/Packing/Ar/ArWriter.cs deleted file mode 100644 index f1c6563..0000000 --- a/src/AMWD.Common/Packing/Ar/ArWriter.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.IO; -using System.Text; - -namespace AMWD.Common.Packing.Ar -{ - /// - /// Writes UNIX ar (archive) files in the GNU format. - /// - /// - /// Copied from: DotnetMakeDeb - /// - public class ArWriter - { - // Source: http://en.wikipedia.org/wiki/Ar_%28Unix%29 - - private readonly Stream _outStream; - - /// - /// Initialises a new instance of the class. - /// - /// The stream to write the archive to. - public ArWriter(Stream outStream) - { - if (!outStream.CanWrite) - throw new ArgumentException("Stream not writable", nameof(outStream)); - - _outStream = outStream; - Initialize(); - } - - /// - /// Writes a file from disk to the archive. - /// - /// The name of the file to copy. - /// The user ID of the file in the archive. - /// The group ID of the file in the archive. - /// The mode of the file in the archive (decimal). - public void WriteFile(string fileName, int userId = 0, int groupId = 0, int mode = 33188 /* 0100644 */) - { - var fi = new FileInfo(fileName); - - using var fs = File.OpenRead(fileName); - WriteFile(fs, fi.Name, fi.LastWriteTimeUtc, userId, groupId, mode); - } - - /// - /// Writes a file from a Stream to the archive. - /// - /// The stream to read the file contents from. - /// The name of the file in the archive. - /// The last modification time of the file in the archive. - /// The user ID of the file in the archive. - /// The group ID of the file in the archive. - /// The mode of the file in the archive (decimal). - public void WriteFile(Stream stream, string fileName, DateTime modifyTime, int userId = 0, int groupId = 0, int mode = 33188 /* 0100644 */) - { - // Write file header - WriteFileHeader(fileName, modifyTime, userId, groupId, mode, stream.Length); - - // Write file contents - stream.CopyTo(_outStream); - - // Align to even offsets, pad with LF bytes - if ((_outStream.Position % 2) != 0) - { - byte[] bytes = [0x0A]; - _outStream.Write(bytes, 0, 1); - } - } - - /// - /// Writes the archive header. - /// - private void Initialize() - { - WriteAsciiString("!\n"); - } - - /// - /// Writes a file header. - /// - private void WriteFileHeader(string fileName, DateTime modifyTime, int userId, int groupId, int mode, long fileSize) - { - // File name - if (fileName.Length > 16) - throw new ArgumentException("Long file names are not supported."); - - WriteAsciiString(fileName.PadRight(16, ' ')); - - // File modification timestamp - long unixTime = ((DateTimeOffset)DateTime.SpecifyKind(modifyTime, DateTimeKind.Utc)).ToUnixTimeSeconds(); - WriteAsciiString(unixTime.ToString().PadRight(12, ' ')); - - // User ID - if (userId >= 0) - { - WriteAsciiString(userId.ToString().PadRight(6, ' ')); - } - else - { - WriteAsciiString(" "); - } - - // Group ID - if (groupId >= 0) - { - WriteAsciiString(groupId.ToString().PadRight(6, ' ')); - } - else - { - WriteAsciiString(" "); - } - - // File mode - if (mode >= 0) - { - WriteAsciiString(Convert.ToString(mode, 8).PadRight(8, ' ')); - } - else - { - WriteAsciiString(" "); - } - - // File size in bytes - if (fileSize < 0 || 10000000000 <= fileSize) - throw new ArgumentOutOfRangeException(nameof(fileSize), "Invalid file size."); // above 9.32 GB - - WriteAsciiString(fileSize.ToString().PadRight(10, ' ')); - - // File magic - byte[] bytes = [0x60, 0x0A]; - _outStream.Write(bytes, 0, 2); - } - - /// - /// Writes a string using ASCII encoding. - /// - /// The string to write to the output stream. - private void WriteAsciiString(string str) - { - byte[] bytes = Encoding.ASCII.GetBytes(str); - _outStream.Write(bytes, 0, bytes.Length); - } - } -} diff --git a/test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs b/test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs deleted file mode 100644 index eb507e5..0000000 --- a/test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs +++ /dev/null @@ -1,353 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using AMWD.Common.Packing.Ar; - -namespace AMWD.Common.Tests.Packing.Ar -{ - [TestClass] - public class ArReaderTest - { - private readonly DateTime _fixedDateTime = new(2023, 03, 01, 10, 20, 30, 0, DateTimeKind.Utc); - - private Dictionary _files; - - private MemoryStream _inStream; - - [TestInitialize] - public void Initialize() - { - _files = new Dictionary - { - { - "abcd.tmp", - new ArFileInfo - { - FileName = "abcd.tmp", - FileSize = 14, - GroupId = 456, - Mode = 33188, - ModifyTime = _fixedDateTime, - UserId = 123 - } - }, - { - "efgh.tmp", - new ArFileInfo - { - FileName = "efgh.tmp", - FileSize = 14, - GroupId = 456, - Mode = 33188, - ModifyTime = _fixedDateTime, - UserId = 123 - } - }, - { - "ijkl.tmp", - new ArFileInfo - { - FileName = "ijkl.tmp", - FileSize = 13, - GroupId = 456, - Mode = 33188, - ModifyTime = _fixedDateTime, - UserId = 123 - } - } - }; - - _inStream = new MemoryStream(); - _inStream.Write(Encoding.ASCII.GetBytes("!\n")); - - foreach (var file in _files) - { - int unixSeconds = (int)file.Value.ModifyTime.Subtract(DateTime.UnixEpoch).TotalSeconds; - - _inStream.Write(Encoding.ASCII.GetBytes($"{file.Key,-16}{unixSeconds,-12}123 456 100644 {file.Value.FileSize,-10}`\n")); - _inStream.Write(Encoding.UTF8.GetBytes(new string('a', (int)file.Value.FileSize))); - if (file.Value.FileSize % 2 != 0) - _inStream.Write(Encoding.ASCII.GetBytes("\n")); - } - - _inStream.Seek(0, SeekOrigin.Begin); - } - - [TestCleanup] - public void Cleanup() - { - _inStream.Dispose(); - _inStream = null; - } - - [TestMethod] - public void ShouldInitializeArchive() - { - // Arrange - _inStream.Dispose(); - _inStream = new MemoryStream(); - _inStream.Write(Encoding.ASCII.GetBytes("!\n")); - _inStream.Seek(0, SeekOrigin.Begin); - - // Act - var reader = new ArReader(_inStream); - - // Assert - Assert.IsNotNull(reader); - Assert.IsFalse(reader.GetFileList().Any()); - } - - [TestMethod] - public void ShouldInitializeWithFiles() - { - // Arrange - - // Act - var reader = new ArReader(_inStream); - - // Assert - Assert.IsNotNull(reader); - Assert.IsTrue(reader.GetFileList().Any()); - } - - [TestMethod] - public void ShouldListFileNames() - { - // Arrange - var reader = new ArReader(_inStream); - - // Act - var fileList = reader.GetFileList().ToList(); - - // Assert - Assert.IsNotNull(reader); - Assert.AreEqual(_files.Count, fileList.Count); - - foreach (string name in _files.Keys) - Assert.IsTrue(fileList.Contains(name)); - } - - [TestMethod] - public void ShouldReturnValidFileInfo() - { - // Arrange - var infos = new List(); - var reader = new ArReader(_inStream); - - // Act - foreach (string name in _files.Keys) - infos.Add(reader.GetFileInfo(name)); - - // Assert - Assert.IsNotNull(reader); - Assert.AreEqual(_files.Count, infos.Count); - - foreach (var expected in _files.Values) - { - var actual = infos.Single(fi => fi.FileName == expected.FileName); - - Assert.AreEqual(expected.FileName, actual.FileName); - Assert.AreEqual(expected.FileSize, actual.FileSize); - Assert.AreEqual(expected.GroupId, actual.GroupId); - Assert.AreEqual(expected.Mode, actual.Mode); - Assert.AreEqual(expected.ModifyTime, actual.ModifyTime); - Assert.AreEqual(expected.UserId, actual.UserId); - } - } - - [TestMethod] - public void ShouldReturnValidFileContent() - { - // Arrange - var contents = new Dictionary(); - var reader = new ArReader(_inStream); - - // Act - foreach (string name in _files.Keys) - { - using var ms = new MemoryStream(); - reader.ReadFile(name, ms); - ms.Seek(0, SeekOrigin.Begin); - - contents.Add(name, Encoding.UTF8.GetString(ms.ToArray())); - } - - // Assert - Assert.IsNotNull(reader); - Assert.AreEqual(_files.Count, contents.Count); - - foreach (var expected in _files.Values) - { - string content = contents[expected.FileName]; - - if (expected.FileSize % 2 != 0) - Assert.AreEqual(13, content.Length); - else - Assert.AreEqual(14, content.Length); - - Assert.AreEqual(new string('a', (int)expected.FileSize), content); - } - } - - [TestMethod] - public void ShouldWriteFileToDisk() - { - // Arrange - string tmpFile = Path.GetTempFileName(); - var reader = new ArReader(_inStream); - - try - { - // Act - using var ms = new MemoryStream(); - - reader.ReadFile("abcd.tmp", ms); - reader.ReadFile("abcd.tmp", tmpFile); - - // Assert - CollectionAssert.AreEqual(ms.ToArray(), File.ReadAllBytes(tmpFile)); - } - finally - { - File.Delete(tmpFile); - } - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void ShouldThrowExceptionOnMissingRead() - { - // Arrange - using var stream = new OverrideStream(); - stream.CanReadOR = false; - stream.CanSeekOR = true; - stream.CanWriteOR = true; - - // Act - var reader = new ArReader(stream); - - // Assert - ArgumentException - Assert.Fail(); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void ShouldThrowExceptionOnMissingSeek() - { - // Arrange - using var stream = new OverrideStream(); - stream.CanReadOR = true; - stream.CanSeekOR = false; - stream.CanWriteOR = true; - - // Act - var reader = new ArReader(stream); - - // Assert - ArgumentException - Assert.Fail(); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void ShouldThrowExceptionOnMissingWrite() - { - // Arrange - using var stream = new OverrideStream(); - stream.CanReadOR = true; - stream.CanSeekOR = true; - stream.CanWriteOR = false; - - var reader = new ArReader(_inStream); - - // Act - reader.ReadFile("abcd.tmp", stream); - - // Assert - ArgumentException - Assert.Fail(); - } - - [TestMethod] - [ExpectedException(typeof(FormatException))] - public void ShouldThrowExceptionOnInvalidArchive() - { - // Arrange - _inStream.Seek(8, SeekOrigin.Begin); - - // Act - _ = new ArReader(_inStream); - - // Assert - FormatException - Assert.Fail(); - } - - [TestMethod] - [ExpectedException(typeof(FormatException))] - public void ShouldThrowExceptionOnInvalidMagic() - { - // Arrange - _inStream.Seek(0, SeekOrigin.End); - _inStream.Write(Encoding.ASCII.GetBytes($"{"foo.bar",-16}{"123456789",-12}123 456 100644 {"0",-10}´\n")); - _inStream.Seek(0, SeekOrigin.Begin); - - // Act - _ = new ArReader(_inStream); - - // Assert - FormatException - Assert.Fail(); - } - - [TestMethod] - public void ShouldWriteNothingToStreamForMissingFile() - { - // Arrange - var reader = new ArReader(_inStream); - - // Act - using var ms = new MemoryStream(); - - reader.ReadFile("foo.bar", ms); - ms.Seek(0, SeekOrigin.Begin); - - // Assert - Assert.AreEqual(0, ms.Length); - } - - [TestMethod] - public void ShouldWriteNothingToDiskForMissingFile() - { - // Arrange - string tmpFile = Path.GetTempFileName(); - var reader = new ArReader(_inStream); - - try - { - // Act - reader.ReadFile("foo.bar", tmpFile); - - // Assert - Assert.AreEqual(0, new FileInfo(tmpFile).Length); - } - finally - { - File.Delete(tmpFile); - } - } - - private class OverrideStream : MemoryStream - { - public override bool CanWrite => CanWriteOR; - - public bool CanWriteOR { get; set; } - - public override bool CanSeek => CanSeekOR; - - public bool CanSeekOR { get; set; } - - public override bool CanRead => CanReadOR; - - public bool CanReadOR { get; set; } - } - } -} diff --git a/test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs b/test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs deleted file mode 100644 index 74be7bd..0000000 --- a/test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs +++ /dev/null @@ -1,304 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using AMWD.Common.Packing.Ar; - -namespace AMWD.Common.Tests.Packing.Ar -{ - [TestClass] - public class ArWriterTest - { - private readonly DateTime _fixedDateTime = new(2023, 03, 01, 10, 20, 30, 0, DateTimeKind.Utc); - - private readonly Dictionary _files = []; - - private MemoryStream _outStream; - - [TestInitialize] - public void Initialize() - { - _files.Clear(); - for (int i = 0; i < 3; i++) - { - var (filePath, content) = GenerateTestFile(); - _files.Add(filePath, content); - } - - _outStream = new MemoryStream(); - } - - [TestCleanup] - public void Cleanup() - { - foreach (var kvp in _files) - File.Delete(kvp.Key); - - _files.Clear(); - - _outStream.Dispose(); - _outStream = null; - } - - [TestMethod] - public void ShouldInitializeArchive() - { - // Arrange - byte[] initBytes = new byte[8]; - - // Act - _ = new ArWriter(_outStream); - - _outStream.Seek(0, SeekOrigin.Begin); - - // Assert - Assert.AreEqual(initBytes.Length, _outStream.Length); - - _outStream.Read(initBytes, 0, initBytes.Length); - CollectionAssert.AreEqual(Encoding.ASCII.GetBytes("!\n"), initBytes); - } - - [TestMethod] - public void ShouldWriteOneFile() - { - // Arrange - var firstFileKvp = _files.First(); - byte[] headerBytes = new byte[60]; - byte[] contentBytes = new byte[14]; - - var writer = new ArWriter(_outStream); - - // Act - writer.WriteFile(firstFileKvp.Key, 123, 456); - - _outStream.Seek(8, SeekOrigin.Begin); // set behind init bytes - - // Assert - Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length); - - _outStream.Read(headerBytes, 0, headerBytes.Length); - _outStream.Read(contentBytes, 0, contentBytes.Length); - - string header = Encoding.ASCII.GetString(headerBytes); - string content = Encoding.UTF8.GetString(contentBytes); - - Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 456 100644 14 `\n", header); - Assert.AreEqual(firstFileKvp.Value, content); - } - - [TestMethod] - public void ShouldWriteMultipleFiles() - { - // Arrange - var writer = new ArWriter(_outStream); - - // Act - foreach (var kvp in _files) - writer.WriteFile(kvp.Key, 123, 456); - - _outStream.Seek(8, SeekOrigin.Begin); - - // Assert - Assert.AreEqual(8 + 3 * 60 + 3 * 14, _outStream.Length); - - foreach (var kvp in _files) - { - byte[] headerBytes = new byte[60]; - byte[] contentBytes = new byte[14]; - - _outStream.Read(headerBytes, 0, headerBytes.Length); - _outStream.Read(contentBytes, 0, contentBytes.Length); - - string header = Encoding.ASCII.GetString(headerBytes); - string content = Encoding.UTF8.GetString(contentBytes); - - Assert.AreEqual($"{Path.GetFileName(kvp.Key),-16}1677666030 123 456 100644 14 `\n", header); - Assert.AreEqual(kvp.Value, content); - } - } - - [TestMethod] - public void ShouldPadToEven() - { - // Arrange - var (filePath, fileContent) = GenerateTestFile(13); - - try - { - byte[] headerBytes = new byte[60]; - byte[] contentBytes = new byte[14]; - - var writer = new ArWriter(_outStream); - - // Act - writer.WriteFile(filePath, 123, 456); - - _outStream.Seek(8, SeekOrigin.Begin); - - // Assert - Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length); - - _outStream.Read(headerBytes, 0, headerBytes.Length); - _outStream.Read(contentBytes, 0, contentBytes.Length); - - string header = Encoding.ASCII.GetString(headerBytes); - string content = Encoding.UTF8.GetString(contentBytes); - - Assert.AreEqual($"{Path.GetFileName(filePath),-16}1677666030 123 456 100644 13 `\n", header); - Assert.AreEqual(fileContent + "\n", content); - } - finally - { - File.Delete(filePath); - } - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void ShouldFailOnFileNameTooLong() - { - // Arrange - var (filePath, _) = GenerateTestFile(); - try - { - string path = Path.GetDirectoryName(filePath); - string fileName = Path.GetFileName(filePath); - fileName = fileName.PadLeft(20, 'a'); - - File.Move(filePath, Path.Combine(path, fileName)); - filePath = Path.Combine(path, fileName); - - var writer = new ArWriter(_outStream); - - // Act - writer.WriteFile(filePath, 123, 456); - - // Assert - Exception - Assert.Fail(); - } - finally - { - File.Delete(filePath); - } - } - - [TestMethod] - public void ShouldWriteEmptyOnNegativeUserId() - { - // Arrange - var firstFileKvp = _files.First(); - byte[] headerBytes = new byte[60]; - byte[] contentBytes = new byte[14]; - - var writer = new ArWriter(_outStream); - - // Act - writer.WriteFile(firstFileKvp.Key, -123, 456); - - _outStream.Seek(8, SeekOrigin.Begin); - - // Assert - Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length); - - _outStream.Read(headerBytes, 0, headerBytes.Length); - _outStream.Read(contentBytes, 0, contentBytes.Length); - - string header = Encoding.ASCII.GetString(headerBytes); - string content = Encoding.UTF8.GetString(contentBytes); - - Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 456 100644 14 `\n", header); - Assert.AreEqual(firstFileKvp.Value, content); - } - - [TestMethod] - public void ShouldWriteEmptyOnNegativeGroupId() - { - // Arrange - var firstFileKvp = _files.First(); - byte[] headerBytes = new byte[60]; - byte[] contentBytes = new byte[14]; - - var writer = new ArWriter(_outStream); - - // Act - writer.WriteFile(firstFileKvp.Key, 123, -456); - - _outStream.Seek(8, SeekOrigin.Begin); - - // Assert - Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length); - - _outStream.Read(headerBytes, 0, headerBytes.Length); - _outStream.Read(contentBytes, 0, contentBytes.Length); - - string header = Encoding.ASCII.GetString(headerBytes); - string content = Encoding.UTF8.GetString(contentBytes); - - Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 100644 14 `\n", header); - Assert.AreEqual(firstFileKvp.Value, content); - } - - [TestMethod] - public void ShouldWriteEmptyOnNegativeMode() - { - // Arrange - var firstFileKvp = _files.First(); - byte[] headerBytes = new byte[60]; - byte[] contentBytes = new byte[14]; - - var writer = new ArWriter(_outStream); - - // Act - writer.WriteFile(firstFileKvp.Key, 123, 456, -1); - - _outStream.Seek(8, SeekOrigin.Begin); - - // Assert - Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length); - - _outStream.Read(headerBytes, 0, headerBytes.Length); - _outStream.Read(contentBytes, 0, contentBytes.Length); - - string header = Encoding.ASCII.GetString(headerBytes); - string content = Encoding.UTF8.GetString(contentBytes); - - Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 456 14 `\n", header); - Assert.AreEqual(firstFileKvp.Value, content); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void ShouldFailOnNonWritableStream() - { - // Arrange - using var testStream = new NonWriteStream(); - - // Act - _ = new ArWriter(testStream); - - // Assert - ArgumentException - Assert.Fail(); - } - - private (string filePath, string content) GenerateTestFile(int length = 14) - { - string filePath = Path.GetTempFileName(); - string text = CryptographyHelper.GetRandomString(length); - - File.WriteAllText(filePath, text); - File.SetLastWriteTimeUtc(filePath, _fixedDateTime); - return (filePath, text); - } - - private class NonWriteStream : MemoryStream - { - public NonWriteStream() - : base() - { } - - public override bool CanWrite => false; - } - } -}