using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Test.Mocks; using SharpCompress.Writers; using SharpCompress.Writers.Tar; using SharpCompress.Writers.Zip; using Xunit; namespace SharpCompress.Test; /// /// A synchronous progress implementation for testing. /// Unlike Progress<T>, this captures reports immediately without SynchronizationContext. /// internal sealed class TestProgress : IProgress { private readonly List _reports = new(); public IReadOnlyList Reports => _reports; public void Report(T value) => _reports.Add(value); } public class ProgressReportTests : TestBase { private static byte[] CreateTestData(int size, byte fillValue) { var data = new byte[size]; for (var i = 0; i < size; i++) { data[i] = fillValue; } return data; } [Fact] public void Zip_Write_ReportsProgress() { var progress = new TestProgress(); using var archiveStream = new MemoryStream(); var options = new ZipWriterOptions(CompressionType.Deflate) { Progress = progress }; using (var writer = new ZipWriter(archiveStream, options)) { var testData = CreateTestData(10000, (byte)'A'); using var sourceStream = new MemoryStream(testData); writer.Write("test.txt", sourceStream, DateTime.Now); } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("test.txt", p.EntryPath)); Assert.All(progress.Reports, p => Assert.Equal(10000, p.TotalBytes)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); Assert.Equal(100.0, lastReport.PercentComplete); } [Fact] public void Tar_Write_ReportsProgress() { var progress = new TestProgress(); using var archiveStream = new MemoryStream(); var options = new TarWriterOptions(CompressionType.None, true) { Progress = progress }; using (var writer = new TarWriter(archiveStream, options)) { var testData = CreateTestData(10000, (byte)'A'); using var sourceStream = new MemoryStream(testData); writer.Write("test.txt", sourceStream, DateTime.Now); } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("test.txt", p.EntryPath)); Assert.All(progress.Reports, p => Assert.Equal(10000, p.TotalBytes)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); Assert.Equal(100.0, lastReport.PercentComplete); } [Fact] public void Zip_Read_ReportsProgress() { var progress = new TestProgress(); // First create a zip archive using var archiveStream = new MemoryStream(); using ( var writer = new ZipWriter(archiveStream, new ZipWriterOptions(CompressionType.Deflate)) ) { var testData = CreateTestData(10000, (byte)'A'); using var sourceStream = new MemoryStream(testData); writer.Write("test.txt", sourceStream, DateTime.Now); } // Now read it with progress reporting archiveStream.Position = 0; var readerOptions = new ReaderOptions { Progress = progress }; using (var reader = ReaderFactory.OpenReader(archiveStream, readerOptions)) { while (reader.MoveToNextEntry()) { if (!reader.Entry.IsDirectory) { using var extractedStream = new MemoryStream(); reader.WriteEntryTo(extractedStream); } } } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("test.txt", p.EntryPath)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); } [Fact] public void ZipArchive_Entry_WriteTo_ReportsProgress() { var progress = new TestProgress(); // First create a zip archive using var archiveStream = new MemoryStream(); using ( var writer = new ZipWriter(archiveStream, new ZipWriterOptions(CompressionType.Deflate)) ) { var testData = CreateTestData(10000, (byte)'A'); using var sourceStream = new MemoryStream(testData); writer.Write("test.txt", sourceStream, DateTime.Now); } // Now open as archive and extract entry with progress as parameter archiveStream.Position = 0; using var archive = ZipArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { using var extractedStream = new MemoryStream(); entry.WriteTo(extractedStream, progress); } } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("test.txt", p.EntryPath)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); } [Fact] public async ValueTask ZipArchive_Entry_WriteToAsync_ReportsProgress() { var progress = new TestProgress(); // First create a zip archive using var archiveStream = new MemoryStream(); using ( var writer = new ZipWriter(archiveStream, new ZipWriterOptions(CompressionType.Deflate)) ) { var testData = CreateTestData(10000, (byte)'A'); using var sourceStream = new MemoryStream(testData); writer.Write("test.txt", sourceStream, DateTime.Now); } // Now open as archive and extract entry async with progress as parameter archiveStream.Position = 0; using var archive = ZipArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { using var extractedStream = new MemoryStream(); await entry.WriteToAsync(extractedStream, progress, CancellationToken.None); } } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("test.txt", p.EntryPath)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); } [Fact] public void WriterOptions_WithoutProgress_DoesNotThrow() { using var archiveStream = new MemoryStream(); var options = new ZipWriterOptions(CompressionType.Deflate); Assert.Null(options.Progress); using (var writer = new ZipWriter(archiveStream, options)) { var testData = CreateTestData(100, (byte)'A'); using var sourceStream = new MemoryStream(testData); writer.Write("test.txt", sourceStream, DateTime.Now); } Assert.True(archiveStream.Length > 0); } [Fact] public void ReaderOptions_WithoutProgress_DoesNotThrow() { // First create a zip archive using var archiveStream = new MemoryStream(); using ( var writer = new ZipWriter(archiveStream, new ZipWriterOptions(CompressionType.Deflate)) ) { var testData = CreateTestData(100, (byte)'A'); using var sourceStream = new MemoryStream(testData); writer.Write("test.txt", sourceStream, DateTime.Now); } // Read without progress archiveStream.Position = 0; var readerOptions = new ReaderOptions(); Assert.Null(readerOptions.Progress); using (var reader = ReaderFactory.OpenReader(archiveStream, readerOptions)) { while (reader.MoveToNextEntry()) { if (!reader.Entry.IsDirectory) { using var extractedStream = new MemoryStream(); reader.WriteEntryTo(extractedStream); } } } } [Fact] public void ZipArchive_WithoutProgress_DoesNotThrow() { // First create a zip archive using var archiveStream = new MemoryStream(); using ( var writer = new ZipWriter(archiveStream, new ZipWriterOptions(CompressionType.Deflate)) ) { var testData = CreateTestData(100, (byte)'A'); using var sourceStream = new MemoryStream(testData); writer.Write("test.txt", sourceStream, DateTime.Now); } // Open archive and extract without progress archiveStream.Position = 0; using var archive = ZipArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { using var extractedStream = new MemoryStream(); entry.WriteTo(extractedStream); } } } [Fact] public void ProgressReport_PercentComplete_WithUnknownTotalBytes_ReturnsNull() { var progress = new ProgressReport("test.txt", 100, null); Assert.Null(progress.PercentComplete); } [Fact] public void ProgressReport_PercentComplete_WithZeroTotalBytes_ReturnsNull() { var progress = new ProgressReport("test.txt", 0, 0); Assert.Null(progress.PercentComplete); } [Fact] public void ProgressReport_Properties_AreSetCorrectly() { var progress = new ProgressReport("path/to/file.txt", 500, 1000); Assert.Equal("path/to/file.txt", progress.EntryPath); Assert.Equal(500, progress.BytesTransferred); Assert.Equal(1000, progress.TotalBytes); Assert.Equal(50.0, progress.PercentComplete); } [Fact] public void Tar_Read_ReportsProgress() { var progress = new TestProgress(); // Create a tar archive first using var archiveStream = new MemoryStream(); using ( var writer = new TarWriter( archiveStream, new TarWriterOptions(CompressionType.None, true) ) ) { var testData = CreateTestData(10000, (byte)'B'); using var sourceStream = new MemoryStream(testData); writer.Write("data.bin", sourceStream, DateTime.Now); } // Now read it with progress reporting archiveStream.Position = 0; var readerOptions = new ReaderOptions { Progress = progress }; using (var reader = ReaderFactory.OpenReader(archiveStream, readerOptions)) { while (reader.MoveToNextEntry()) { if (!reader.Entry.IsDirectory) { using var extractedStream = new MemoryStream(); reader.WriteEntryTo(extractedStream); } } } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("data.bin", p.EntryPath)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); } [Fact] public void TarArchive_Entry_WriteTo_ReportsProgress() { var progress = new TestProgress(); // Create a tar archive first using var archiveStream = new MemoryStream(); using ( var writer = new TarWriter( archiveStream, new TarWriterOptions(CompressionType.None, true) ) ) { var testData = CreateTestData(10000, (byte)'C'); using var sourceStream = new MemoryStream(testData); writer.Write("file.dat", sourceStream, DateTime.Now); } // Now open as archive and extract entry with progress as parameter archiveStream.Position = 0; using var archive = SharpCompress.Archives.Tar.TarArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { using var extractedStream = new MemoryStream(); entry.WriteTo(extractedStream, progress); } } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("file.dat", p.EntryPath)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); } [Fact] public async ValueTask TarArchive_Entry_WriteToAsync_ReportsProgress() { var progress = new TestProgress(); // Create a tar archive first using var archiveStream = new MemoryStream(); using ( var writer = new TarWriter( archiveStream, new TarWriterOptions(CompressionType.None, true) ) ) { var testData = CreateTestData(10000, (byte)'D'); using var sourceStream = new MemoryStream(testData); writer.Write("async.dat", sourceStream, DateTime.Now); } // Now open as archive and extract entry async with progress as parameter archiveStream.Position = 0; using var archive = SharpCompress.Archives.Tar.TarArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { using var extractedStream = new MemoryStream(); await entry.WriteToAsync(extractedStream, progress, CancellationToken.None); } } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("async.dat", p.EntryPath)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); } [Fact] public void Zip_Read_MultipleEntries_ReportsProgress() { var progress = new TestProgress(); // Create a zip archive with multiple entries using var archiveStream = new MemoryStream(); using ( var writer = new ZipWriter(archiveStream, new ZipWriterOptions(CompressionType.Deflate)) ) { var testData1 = CreateTestData(5000, (byte)'A'); using var sourceStream1 = new MemoryStream(testData1); writer.Write("file1.txt", sourceStream1, DateTime.Now); var testData2 = CreateTestData(8000, (byte)'B'); using var sourceStream2 = new MemoryStream(testData2); writer.Write("file2.txt", sourceStream2, DateTime.Now); } // Now read it with progress reporting archiveStream.Position = 0; var readerOptions = new ReaderOptions { Progress = progress }; using (var reader = ReaderFactory.OpenReader(archiveStream, readerOptions)) { while (reader.MoveToNextEntry()) { if (!reader.Entry.IsDirectory) { using var extractedStream = new MemoryStream(); reader.WriteEntryTo(extractedStream); } } } Assert.NotEmpty(progress.Reports); // Should have reports for both files var file1Reports = progress.Reports.Where(p => p.EntryPath == "file1.txt").ToList(); var file2Reports = progress.Reports.Where(p => p.EntryPath == "file2.txt").ToList(); Assert.NotEmpty(file1Reports); Assert.NotEmpty(file2Reports); // Verify final bytes for each file Assert.Equal(5000, file1Reports[file1Reports.Count - 1].BytesTransferred); Assert.Equal(8000, file2Reports[file2Reports.Count - 1].BytesTransferred); } [Fact] public void ZipArchive_MultipleEntries_WriteTo_ReportsProgress() { var progress = new TestProgress(); // Create a zip archive with multiple entries using var archiveStream = new MemoryStream(); using ( var writer = new ZipWriter(archiveStream, new ZipWriterOptions(CompressionType.Deflate)) ) { var testData1 = CreateTestData(5000, (byte)'A'); using var sourceStream1 = new MemoryStream(testData1); writer.Write("entry1.txt", sourceStream1, DateTime.Now); var testData2 = CreateTestData(7000, (byte)'B'); using var sourceStream2 = new MemoryStream(testData2); writer.Write("entry2.txt", sourceStream2, DateTime.Now); } // Now open as archive and extract entries with progress as parameter archiveStream.Position = 0; using var archive = ZipArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { using var extractedStream = new MemoryStream(); entry.WriteTo(extractedStream, progress); } } Assert.NotEmpty(progress.Reports); // Should have reports for both files var entry1Reports = progress.Reports.Where(p => p.EntryPath == "entry1.txt").ToList(); var entry2Reports = progress.Reports.Where(p => p.EntryPath == "entry2.txt").ToList(); Assert.NotEmpty(entry1Reports); Assert.NotEmpty(entry2Reports); // Verify final bytes for each entry Assert.Equal(5000, entry1Reports[entry1Reports.Count - 1].BytesTransferred); Assert.Equal(7000, entry2Reports[entry2Reports.Count - 1].BytesTransferred); } [Fact] public async ValueTask Zip_ReadAsync_ReportsProgress() { var progress = new TestProgress(); // Create a zip archive using var archiveStream = new MemoryStream(); using ( var writer = new ZipWriter(archiveStream, new ZipWriterOptions(CompressionType.Deflate)) ) { var testData = CreateTestData(10000, (byte)'E'); using var sourceStream = new MemoryStream(testData); writer.Write("async_read.txt", sourceStream, DateTime.Now); } // Now read it with progress reporting archiveStream.Position = 0; var readerOptions = new ReaderOptions { Progress = progress }; await using ( var reader = await ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(archiveStream), readerOptions ) ) { while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { using var extractedStream = new MemoryStream(); await reader.WriteEntryToAsync(extractedStream); } } } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("async_read.txt", p.EntryPath)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); } [Fact] public void GZip_Write_ReportsProgress() { var progress = new TestProgress(); using var archiveStream = new MemoryStream(); var options = new SharpCompress.Writers.GZip.GZipWriterOptions { Progress = progress }; using (var writer = new SharpCompress.Writers.GZip.GZipWriter(archiveStream, options)) { var testData = CreateTestData(10000, (byte)'G'); using var sourceStream = new MemoryStream(testData); writer.Write("gzip_test.txt", sourceStream, DateTime.Now); } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("gzip_test.txt", p.EntryPath)); Assert.All(progress.Reports, p => Assert.Equal(10000, p.TotalBytes)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); Assert.Equal(100.0, lastReport.PercentComplete); } [Fact] public async ValueTask Tar_WriteAsync_ReportsProgress() { var progress = new TestProgress(); using var archiveStream = new MemoryStream(); var options = new TarWriterOptions(CompressionType.None, true) { Progress = progress }; using (var writer = new TarWriter(archiveStream, options)) { var testData = CreateTestData(10000, (byte)'A'); using var sourceStream = new MemoryStream(testData); await writer.WriteAsync("test.txt", sourceStream, DateTime.Now); } Assert.NotEmpty(progress.Reports); Assert.All(progress.Reports, p => Assert.Equal("test.txt", p.EntryPath)); var lastReport = progress.Reports[progress.Reports.Count - 1]; Assert.Equal(10000, lastReport.BytesTransferred); } }