Added zip and tar.gz archives
This commit is contained in:
196
test/AMWD.Common.Tests/Compression/ArchiveHelperTest.cs
Normal file
196
test/AMWD.Common.Tests/Compression/ArchiveHelperTest.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Common.Compression;
|
||||
|
||||
namespace AMWD.Common.Tests.Compression
|
||||
{
|
||||
[TestClass]
|
||||
public class ArchiveHelperTest
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldCreateZipFile()
|
||||
{
|
||||
// arrange
|
||||
var files = new List<ArchiveFile>
|
||||
{
|
||||
new() { FileName = "folder/hello.txt", Content = Encoding.UTF8.GetBytes("Hello World") },
|
||||
new() { FileName = "readme.md", Content = Encoding.UTF8.GetBytes("# Readme") }
|
||||
};
|
||||
|
||||
// act
|
||||
byte[] zipBytes = await ArchiveHelper.CreateZip(files, TestContext.CancellationToken);
|
||||
|
||||
// assert
|
||||
using var ms = new MemoryStream(zipBytes);
|
||||
using var zip = new ZipArchive(ms, ZipArchiveMode.Read, leaveOpen: false);
|
||||
|
||||
Assert.HasCount(2, zip.Entries);
|
||||
|
||||
var entry1 = zip.GetEntry("folder/hello.txt");
|
||||
Assert.IsNotNull(entry1);
|
||||
|
||||
using (var s = entry1.Open())
|
||||
using (var sr = new MemoryStream())
|
||||
{
|
||||
await s.CopyToAsync(sr, TestContext.CancellationToken);
|
||||
CollectionAssert.AreEqual(files[0].Content, sr.ToArray());
|
||||
}
|
||||
|
||||
var entry2 = zip.GetEntry("readme.md");
|
||||
Assert.IsNotNull(entry2);
|
||||
|
||||
using (var s = entry2.Open())
|
||||
using (var sr = new MemoryStream())
|
||||
{
|
||||
await s.CopyToAsync(sr, TestContext.CancellationToken);
|
||||
CollectionAssert.AreEqual(files[1].Content, sr.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldCreateTarGzFile()
|
||||
{
|
||||
// arrange
|
||||
var file1 = new ArchiveFile
|
||||
{
|
||||
FileName = "a.txt",
|
||||
Content = Encoding.UTF8.GetBytes("Alpha"),
|
||||
};
|
||||
file1.SetPermissions("0755"); // rwxr-xr-x
|
||||
|
||||
var file2 = new ArchiveFile
|
||||
{
|
||||
FileName = "b.bin",
|
||||
Content = [1, 2, 3, 4, 5]
|
||||
};
|
||||
file2.SetPermissions("2750"); // setgid + rwxr-x---
|
||||
|
||||
var files = new List<ArchiveFile> { file1, file2 };
|
||||
|
||||
// act
|
||||
byte[] tarGz = await ArchiveHelper.CreateTarGz(files, cancellationToken: TestContext.CancellationToken);
|
||||
|
||||
// decompress gzip to raw tar bytes
|
||||
byte[] tarBytes;
|
||||
using (var msIn = new MemoryStream(tarGz))
|
||||
using (var gzip = new GZipStream(msIn, CompressionMode.Decompress))
|
||||
using (var msOut = new MemoryStream())
|
||||
{
|
||||
await gzip.CopyToAsync(msOut, TestContext.CancellationToken);
|
||||
tarBytes = msOut.ToArray();
|
||||
}
|
||||
|
||||
var entries = ParseTarEntries(tarBytes);
|
||||
|
||||
// assert
|
||||
Assert.HasCount(4, entries); // 2 files + 2 headers
|
||||
|
||||
var entry1 = entries.FirstOrDefault(e => e.Name == "a.txt");
|
||||
Assert.IsNotNull(entry1);
|
||||
|
||||
CollectionAssert.AreEqual(file1.Content, entry1.Data);
|
||||
Assert.AreEqual(Convert.ToInt32("0755", 8), entry1.Mode);
|
||||
|
||||
var entry2 = entries.FirstOrDefault(e => e.Name == "b.bin");
|
||||
Assert.IsNotNull(entry2);
|
||||
|
||||
CollectionAssert.AreEqual(file2.Content, entry2.Data);
|
||||
Assert.AreEqual(Convert.ToInt32("2750", 8), entry2.Mode);
|
||||
}
|
||||
|
||||
private sealed class TarEntryInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Mode { get; set; }
|
||||
|
||||
public byte[] Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimal TAR parser for test purposes: reads header blocks (512 bytes) and extracts
|
||||
/// name, mode and data for regular files. Stops on two consecutive zero blocks or empty name.
|
||||
/// </summary>
|
||||
private static List<TarEntryInfo> ParseTarEntries(byte[] tar)
|
||||
{
|
||||
var result = new List<TarEntryInfo>();
|
||||
int offset = 0;
|
||||
int zeroBlockCount = 0;
|
||||
|
||||
while (offset + 512 <= tar.Length)
|
||||
{
|
||||
// read header block
|
||||
byte[] header = new ArraySegment<byte>(tar, offset, 512).ToArray();
|
||||
|
||||
// check for zero block
|
||||
if (IsAllZero(header))
|
||||
{
|
||||
zeroBlockCount++;
|
||||
if (zeroBlockCount >= 2) break;
|
||||
offset += 512;
|
||||
continue;
|
||||
}
|
||||
zeroBlockCount = 0;
|
||||
|
||||
string name = ReadNullTerminatedAscii(header, 0, 100);
|
||||
if (string.IsNullOrEmpty(name)) break;
|
||||
|
||||
string modeStr = ReadNullTerminatedAscii(header, 100, 8).Trim();
|
||||
int mode = 0;
|
||||
if (!string.IsNullOrEmpty(modeStr))
|
||||
{
|
||||
mode = Convert.ToInt32(modeStr, 8);
|
||||
}
|
||||
|
||||
string sizeStr = ReadNullTerminatedAscii(header, 124, 12).Trim();
|
||||
long size = 0;
|
||||
if (!string.IsNullOrEmpty(sizeStr))
|
||||
{
|
||||
size = Convert.ToInt64(sizeStr, 8);
|
||||
}
|
||||
|
||||
offset += 512;
|
||||
|
||||
byte[] data = new byte[size > 0 ? size : 0];
|
||||
if (size > 0)
|
||||
{
|
||||
Array.Copy(tar, offset, data, 0, size);
|
||||
}
|
||||
|
||||
result.Add(new TarEntryInfo
|
||||
{
|
||||
Name = name,
|
||||
Mode = mode,
|
||||
Data = data
|
||||
});
|
||||
|
||||
// advance to next header (data is stored in 512-byte blocks)
|
||||
long dataBlockCount = (size + 511) / 512;
|
||||
offset += (int)(dataBlockCount * 512);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsAllZero(byte[] block)
|
||||
{
|
||||
foreach (byte b in block) if (b != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ReadNullTerminatedAscii(byte[] src, int start, int length)
|
||||
{
|
||||
int end = start;
|
||||
int last = start + length;
|
||||
while (end < last && src[end] != 0) end++;
|
||||
return Encoding.ASCII.GetString(src, start, end - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user