using System; using System.IO; using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using SharpCompress.Archives; using SharpCompress.Crypto; using SharpCompress.Readers; using SharpCompress.Test.Mocks; using Xunit; namespace SharpCompress.Test; [Collection(LargeArchiveCollection.Name)] public class LargeArchiveTests : TestBase { private const long LargeFileSize = 64L * 1024 * 1024; private const uint LargeFileCrc = 0xF9081EB0; private const int BufferSize = 64 * 1024; [Theory] [InlineData("Large/Large.zip")] [InlineData("Large/Large.tar")] [InlineData("Large/Large.gz")] [InlineData("Large/Large.rar")] [InlineData("Large/Large.7z")] public void OpenArchive_ShouldStreamLargeEntry(string fixtureName) { using var stream = File.OpenRead(GetMaterializedFixturePath(fixtureName)); using var archive = ArchiveFactory.OpenArchive(stream); VerifyArchive(archive); } [Theory] [InlineData("Large/Large.zip")] [InlineData("Large/Large.tar")] [InlineData("Large/Large.gz")] [InlineData("Large/Large.rar")] [InlineData("Large/Large.7z")] public async Task OpenAsyncArchive_ShouldStreamLargeEntry(string fixtureName) { await using var stream = new AsyncOnlyStream( File.OpenRead(await GetMaterializedFixturePathAsync(fixtureName)) ); await using var archive = await ArchiveFactory.OpenAsyncArchive(stream); var entry = await GetSingleEntryAsync(archive); var entryStream = await entry.OpenEntryStreamAsync(); await using var entryStreamScope = entryStream.DisposeAsyncScope(); await VerifyContentAsync(entry.Key, entryStream); } [Theory] [InlineData("Large/Large.zip")] [InlineData("Large/Large.tar")] [InlineData("Large/Large.gz")] [InlineData("Large/Large.rar")] [InlineData("Large/Large.tar.gz")] public void OpenReader_ShouldStreamLargeEntry(string fixtureName) { using var stream = File.OpenRead(GetMaterializedFixturePath(fixtureName)); using var reader = ReaderFactory.OpenReader(stream); VerifyReader(reader); } [Theory] [InlineData("Large/Large.zip")] [InlineData("Large/Large.tar")] [InlineData("Large/Large.gz")] [InlineData("Large/Large.rar")] [InlineData("Large/Large.tar.gz")] public async Task OpenAsyncReader_ShouldStreamLargeEntry(string fixtureName) { await using var stream = new AsyncOnlyStream( File.OpenRead(await GetMaterializedFixturePathAsync(fixtureName)) ); await using var reader = await ReaderFactory.OpenAsyncReader(stream); Assert.True(await reader.MoveToNextEntryAsync()); Assert.False(reader.Entry.IsDirectory); await using var entryStream = await reader.OpenEntryStreamAsync(); await VerifyContentAsync(reader.Entry.Key, entryStream); Assert.False(await reader.MoveToNextEntryAsync()); } [Fact] public async Task OpenAsyncReader_WithGZipExtensionHint_ShouldStreamLargeTarEntry() { using var file = File.OpenRead(GetFixturePath("Large/Large.tar.gz")); await using var stream = new AsyncOnlyStream(new ForwardOnlyStream(file)); var options = new ReaderOptions { ExtensionHint = "gz" }; await using var reader = await ReaderFactory.OpenAsyncReader(stream, options); Assert.True(await reader.MoveToNextEntryAsync()); Assert.False(reader.Entry.IsDirectory); await using var entryStream = await reader.OpenEntryStreamAsync(); await VerifyContentAsync(reader.Entry.Key, entryStream); Assert.False(await reader.MoveToNextEntryAsync()); } private static string GetFixturePath(string fixtureName) => Path.Combine(TEST_ARCHIVES_PATH, fixtureName); private string GetMaterializedFixturePath(string fixtureName) => fixtureName == "Large/Large.tar" ? MaterializeTarFixture() : GetFixturePath(fixtureName); private async Task GetMaterializedFixturePathAsync(string fixtureName) => fixtureName == "Large/Large.tar" ? await MaterializeTarFixtureAsync() : GetFixturePath(fixtureName); private string MaterializeTarFixture() { var tarPath = Path.Combine(SCRATCH_FILES_PATH, "Large.tar"); using var compressedStream = File.OpenRead(GetFixturePath("Large/Large.tar.gz")); using var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress); using var tarStream = File.Create(tarPath); gzipStream.CopyTo(tarStream); return tarPath; } private async Task MaterializeTarFixtureAsync() { var tarPath = Path.Combine(SCRATCH_FILES_PATH, "Large.tar"); using var compressedStream = File.OpenRead(GetFixturePath("Large/Large.tar.gz")); using var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress); using var tarStream = File.Create(tarPath); await gzipStream.CopyToAsync(tarStream); return tarPath; } private static void VerifyArchive(IArchive archive) { var entry = Assert.Single(archive.Entries); Assert.False(entry.IsDirectory); using var entryStream = entry.OpenEntryStream(); VerifyContent(entry.Key, entryStream); } private static async Task GetSingleEntryAsync(IAsyncArchive archive) { IArchiveEntry? entry = null; await foreach (var candidate in archive.EntriesAsync) { Assert.Null(entry); entry = candidate; } return entry ?? throw new InvalidOperationException("The archive contains no entries."); } private static void VerifyReader(IReader reader) { Assert.True(reader.MoveToNextEntry()); Assert.False(reader.Entry.IsDirectory); using var entryStream = reader.OpenEntryStream(); VerifyContent(reader.Entry.Key, entryStream); Assert.False(reader.MoveToNextEntry()); } private static void VerifyContent(string? key, Stream entryStream) { Assert.Equal("large.bin", key); using var crcStream = new Crc32Stream(Stream.Null); var buffer = new byte[BufferSize]; long length = 0; int bytesRead; while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { crcStream.Write(buffer, 0, bytesRead); length += bytesRead; } Assert.Equal(LargeFileSize, length); Assert.Equal(LargeFileCrc, crcStream.Crc); } private static async Task VerifyContentAsync(string? key, Stream entryStream) { Assert.Equal("large.bin", key); using var crcStream = new Crc32Stream(Stream.Null); var buffer = new byte[BufferSize]; long length = 0; int bytesRead; while ((bytesRead = await entryStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await crcStream.WriteAsync(buffer, 0, bytesRead); length += bytesRead; } Assert.Equal(LargeFileSize, length); Assert.Equal(LargeFileCrc, crcStream.Crc); } } [CollectionDefinition(LargeArchiveCollection.Name, DisableParallelization = true)] public sealed class LargeArchiveCollection { public const string Name = "Large archive fixtures"; }