2025-10-27 09:00:38 +00:00
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using SharpCompress.Archives;
|
2026-01-21 16:57:25 +00:00
|
|
|
using SharpCompress.Archives.GZip;
|
2025-10-27 09:00:38 +00:00
|
|
|
using SharpCompress.Archives.Zip;
|
|
|
|
|
using SharpCompress.Common;
|
2025-10-28 16:48:05 +00:00
|
|
|
using SharpCompress.Compressors;
|
|
|
|
|
using SharpCompress.Compressors.Deflate;
|
2025-10-27 09:00:38 +00:00
|
|
|
using SharpCompress.Readers;
|
2026-01-08 11:28:15 +00:00
|
|
|
using SharpCompress.Test.Mocks;
|
2025-10-27 09:00:38 +00:00
|
|
|
using SharpCompress.Writers;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
2025-10-27 09:47:15 +00:00
|
|
|
namespace SharpCompress.Test.GZip;
|
2025-10-27 09:00:38 +00:00
|
|
|
|
|
|
|
|
public class AsyncTests : TestBase
|
|
|
|
|
{
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Reader_Async_Extract_All()
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
|
|
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
|
2025-10-27 10:19:24 +00:00
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:00:38 +00:00
|
|
|
using var stream = File.OpenRead(testArchive);
|
2025-10-27 10:19:24 +00:00
|
|
|
#else
|
2025-10-27 09:47:15 +00:00
|
|
|
await using var stream = File.OpenRead(testArchive);
|
|
|
|
|
#endif
|
2026-01-16 09:12:54 +00:00
|
|
|
await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream));
|
2025-10-27 09:00:38 +00:00
|
|
|
|
2026-02-09 16:52:54 +00:00
|
|
|
await reader.WriteAllToDirectoryAsync(SCRATCH_FILES_PATH);
|
2025-10-27 09:00:38 +00:00
|
|
|
|
|
|
|
|
// Just verify some files were extracted
|
2025-10-27 09:37:00 +00:00
|
|
|
var extractedFiles = Directory.GetFiles(
|
|
|
|
|
SCRATCH_FILES_PATH,
|
|
|
|
|
"*",
|
|
|
|
|
SearchOption.AllDirectories
|
|
|
|
|
);
|
2025-10-27 09:00:38 +00:00
|
|
|
Assert.True(extractedFiles.Length > 0, "No files were extracted");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Reader_Async_Extract_Single_Entry()
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
|
|
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
|
2025-10-27 09:47:15 +00:00
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:00:38 +00:00
|
|
|
using var stream = File.OpenRead(testArchive);
|
2025-10-27 09:47:15 +00:00
|
|
|
#else
|
|
|
|
|
await using var stream = File.OpenRead(testArchive);
|
|
|
|
|
#endif
|
2026-01-16 09:12:54 +00:00
|
|
|
await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream));
|
2025-10-27 09:00:38 +00:00
|
|
|
|
2026-01-08 11:28:15 +00:00
|
|
|
while (await reader.MoveToNextEntryAsync())
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
|
|
|
|
if (!reader.Entry.IsDirectory)
|
|
|
|
|
{
|
|
|
|
|
var outputPath = Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key!);
|
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
|
2025-10-27 09:47:15 +00:00
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:00:38 +00:00
|
|
|
using var outputStream = File.Create(outputPath);
|
2025-10-27 09:47:15 +00:00
|
|
|
#else
|
|
|
|
|
await using var outputStream = File.Create(outputPath);
|
|
|
|
|
#endif
|
2025-10-27 09:00:38 +00:00
|
|
|
await reader.WriteEntryToAsync(outputStream);
|
|
|
|
|
break; // Just test one entry
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Archive_Entry_Async_Open_Stream()
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
|
|
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
|
2026-02-12 10:18:43 +00:00
|
|
|
await using var archive = await GZipArchive.OpenAsyncArchive(
|
2026-01-13 14:29:10 +00:00
|
|
|
new AsyncOnlyStream(File.OpenRead(testArchive))
|
|
|
|
|
);
|
2025-10-27 09:00:38 +00:00
|
|
|
|
2026-01-13 15:26:45 +00:00
|
|
|
await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory).Take(1))
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
2025-10-27 09:47:15 +00:00
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:00:38 +00:00
|
|
|
using var entryStream = await entry.OpenEntryStreamAsync();
|
2025-10-27 09:47:15 +00:00
|
|
|
#else
|
|
|
|
|
await using var entryStream = await entry.OpenEntryStreamAsync();
|
|
|
|
|
#endif
|
2025-10-27 09:00:38 +00:00
|
|
|
Assert.NotNull(entryStream);
|
|
|
|
|
Assert.True(entryStream.CanRead);
|
|
|
|
|
|
|
|
|
|
// Read some data to verify it works
|
|
|
|
|
var buffer = new byte[1024];
|
|
|
|
|
var read = await entryStream.ReadAsync(buffer, 0, buffer.Length);
|
|
|
|
|
Assert.True(read > 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Writer_Async_Write_Single_File()
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
|
|
|
|
var outputPath = Path.Combine(SCRATCH_FILES_PATH, "async_test.zip");
|
2026-01-08 16:52:23 +00:00
|
|
|
|
|
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:00:38 +00:00
|
|
|
using (var stream = File.Create(outputPath))
|
2026-01-08 16:52:23 +00:00
|
|
|
#else
|
|
|
|
|
await using (var stream = File.Create(outputPath))
|
|
|
|
|
#endif
|
2026-01-12 14:14:46 +00:00
|
|
|
using (
|
2026-02-12 11:53:19 +00:00
|
|
|
var writer = await WriterFactory.OpenAsyncWriter(
|
2026-01-13 14:29:10 +00:00
|
|
|
new AsyncOnlyStream(stream),
|
|
|
|
|
ArchiveType.Zip,
|
2026-01-22 16:38:44 +00:00
|
|
|
new WriterOptions(CompressionType.Deflate) { LeaveStreamOpen = false }
|
2026-01-13 14:29:10 +00:00
|
|
|
)
|
Remove unnecessary ValueTask wrappers from async factory methods
Change return types from ValueTask<T> to direct interface types (IAsyncArchive, IAsyncReader, IWriter) for wrapper methods that don't perform async work. This eliminates unnecessary async state machine allocations while maintaining the same public API behavior.
Changes:
- Interface definitions: Updated IArchiveFactory, IMultiArchiveFactory, IReaderFactory, IWriterFactory
- Concrete factories: Updated archive factories (Zip, Tar, Rar, GZip, SevenZip) and reader-only factories (Ace, Arc, Arj)
- Static factory methods: Updated ReaderFactory, ArchiveFactory, WriterFactory to use new signatures
- Archive classes: Updated static OpenAsync methods in ZipArchive, TarArchive, RarArchive, SevenZipArchive, GZipArchive
- Supporting changes: Updated Factory.cs and async polyfills
Performance benefit: Reduced GC pressure by eliminating unnecessary state machine overhead for non-async wrapper methods.
2026-01-12 13:16:44 +00:00
|
|
|
)
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
|
|
|
|
var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
|
2026-01-08 16:52:23 +00:00
|
|
|
|
|
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:00:38 +00:00
|
|
|
using var fileStream = File.OpenRead(testFile);
|
2026-01-08 16:52:23 +00:00
|
|
|
#else
|
|
|
|
|
await using var fileStream = File.OpenRead(testFile);
|
|
|
|
|
#endif
|
2025-10-27 09:00:38 +00:00
|
|
|
await writer.WriteAsync("test_entry.bin", fileStream, new DateTime(2023, 1, 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the archive was created and contains the entry
|
|
|
|
|
Assert.True(File.Exists(outputPath));
|
2026-02-12 10:18:43 +00:00
|
|
|
await using var archive = await ZipArchive.OpenAsyncArchive(outputPath);
|
2026-01-13 15:26:45 +00:00
|
|
|
Assert.Single(await archive.EntriesAsync.Where(e => !e.IsDirectory).ToListAsync());
|
2025-10-27 09:00:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Async_With_Cancellation_Token()
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
|
|
|
|
using var cts = new CancellationTokenSource();
|
|
|
|
|
cts.CancelAfter(10000); // 10 seconds should be plenty
|
|
|
|
|
|
|
|
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
|
2026-01-08 16:52:23 +00:00
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:00:38 +00:00
|
|
|
using var stream = File.OpenRead(testArchive);
|
2026-01-08 16:52:23 +00:00
|
|
|
#else
|
|
|
|
|
await using var stream = File.OpenRead(testArchive);
|
|
|
|
|
#endif
|
2026-01-16 09:12:54 +00:00
|
|
|
await using var reader = await ReaderFactory.OpenAsyncReader(
|
2026-01-08 11:28:15 +00:00
|
|
|
new AsyncOnlyStream(stream),
|
|
|
|
|
cancellationToken: cts.Token
|
|
|
|
|
);
|
2025-10-27 09:00:38 +00:00
|
|
|
|
2026-03-02 07:00:33 +00:00
|
|
|
await reader.WriteAllToDirectoryAsync(SCRATCH_FILES_PATH, cancellationToken: cts.Token);
|
2025-10-27 09:00:38 +00:00
|
|
|
|
|
|
|
|
// Just verify some files were extracted
|
2025-10-27 09:37:00 +00:00
|
|
|
var extractedFiles = Directory.GetFiles(
|
|
|
|
|
SCRATCH_FILES_PATH,
|
|
|
|
|
"*",
|
|
|
|
|
SearchOption.AllDirectories
|
|
|
|
|
);
|
2025-10-27 09:00:38 +00:00
|
|
|
Assert.True(extractedFiles.Length > 0, "No files were extracted");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Stream_Extensions_Async()
|
2025-10-27 09:00:38 +00:00
|
|
|
{
|
|
|
|
|
var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
|
|
|
|
|
var outputPath = Path.Combine(SCRATCH_FILES_PATH, "async_copy.bin");
|
2026-01-08 16:52:23 +00:00
|
|
|
#if NETFRAMEWORK
|
|
|
|
|
using var inputStream = File.OpenRead(testFile);
|
2025-10-27 09:00:38 +00:00
|
|
|
using var outputStream = File.Create(outputPath);
|
2026-01-08 16:52:23 +00:00
|
|
|
#else
|
|
|
|
|
await using var inputStream = File.OpenRead(testFile);
|
|
|
|
|
await using var outputStream = File.Create(outputPath);
|
|
|
|
|
#endif
|
2025-10-27 09:00:38 +00:00
|
|
|
|
|
|
|
|
// Test the async extension method
|
|
|
|
|
var buffer = new byte[8192];
|
|
|
|
|
int bytesRead;
|
|
|
|
|
while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
|
|
|
|
{
|
|
|
|
|
await outputStream.WriteAsync(buffer, 0, bytesRead);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.True(File.Exists(outputPath));
|
|
|
|
|
Assert.True(new FileInfo(outputPath).Length > 0);
|
|
|
|
|
}
|
2025-10-27 09:28:34 +00:00
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask EntryStream_ReadAsync_Works()
|
2025-10-27 09:28:34 +00:00
|
|
|
{
|
|
|
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
|
2026-01-08 16:52:23 +00:00
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:28:34 +00:00
|
|
|
using var stream = File.OpenRead(testArchive);
|
2026-01-08 16:52:23 +00:00
|
|
|
#else
|
|
|
|
|
await using var stream = File.OpenRead(testArchive);
|
|
|
|
|
#endif
|
2026-01-16 09:12:54 +00:00
|
|
|
await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream));
|
2025-10-27 09:28:34 +00:00
|
|
|
|
2026-01-08 16:24:11 +00:00
|
|
|
while (await reader.MoveToNextEntryAsync())
|
2025-10-27 09:28:34 +00:00
|
|
|
{
|
|
|
|
|
if (!reader.Entry.IsDirectory)
|
|
|
|
|
{
|
2026-01-08 16:52:23 +00:00
|
|
|
#if NETFRAMEWORK
|
2026-01-08 16:24:11 +00:00
|
|
|
using var entryStream = await reader.OpenEntryStreamAsync();
|
2026-01-08 16:52:23 +00:00
|
|
|
#else
|
|
|
|
|
await using var entryStream = await reader.OpenEntryStreamAsync();
|
|
|
|
|
#endif
|
2025-10-27 09:28:34 +00:00
|
|
|
var buffer = new byte[4096];
|
|
|
|
|
var totalRead = 0;
|
|
|
|
|
int bytesRead;
|
2025-10-27 09:37:00 +00:00
|
|
|
|
2025-10-27 09:28:34 +00:00
|
|
|
// Test ReadAsync on EntryStream
|
|
|
|
|
while ((bytesRead = await entryStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
|
|
|
|
{
|
|
|
|
|
totalRead += bytesRead;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.True(totalRead > 0, "Should have read some data from entry stream");
|
|
|
|
|
break; // Test just one entry
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask CompressionStream_Async_ReadWrite()
|
2025-10-27 09:28:34 +00:00
|
|
|
{
|
|
|
|
|
var testData = new byte[1024];
|
|
|
|
|
new Random(42).NextBytes(testData);
|
|
|
|
|
|
|
|
|
|
var compressedPath = Path.Combine(SCRATCH_FILES_PATH, "async_compressed.gz");
|
2025-10-27 09:37:00 +00:00
|
|
|
|
2025-10-27 09:28:34 +00:00
|
|
|
// Test async write with GZipStream
|
2026-01-08 16:52:23 +00:00
|
|
|
#if NETFRAMEWORK
|
2025-10-27 09:28:34 +00:00
|
|
|
using (var fileStream = File.Create(compressedPath))
|
2025-10-29 08:41:05 +00:00
|
|
|
using (var gzipStream = new GZipStream(fileStream, CompressionMode.Compress))
|
2026-01-08 16:52:23 +00:00
|
|
|
#else
|
|
|
|
|
await using (var fileStream = File.Create(compressedPath))
|
|
|
|
|
await using (var gzipStream = new GZipStream(fileStream, CompressionMode.Compress))
|
|
|
|
|
#endif
|
2025-10-27 09:28:34 +00:00
|
|
|
{
|
|
|
|
|
await gzipStream.WriteAsync(testData, 0, testData.Length);
|
|
|
|
|
await gzipStream.FlushAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.True(File.Exists(compressedPath));
|
|
|
|
|
Assert.True(new FileInfo(compressedPath).Length > 0);
|
2026-01-08 16:52:23 +00:00
|
|
|
#if NETFRAMEWORK
|
2026-01-20 11:01:17 +00:00
|
|
|
using (var fileStream = File.OpenRead(compressedPath))
|
|
|
|
|
using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress))
|
2026-01-08 16:52:23 +00:00
|
|
|
#else
|
2025-10-27 09:28:34 +00:00
|
|
|
// Test async read with GZipStream
|
2026-01-20 11:01:17 +00:00
|
|
|
await using (var fileStream = File.OpenRead(compressedPath))
|
|
|
|
|
await using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress))
|
2026-01-08 16:52:23 +00:00
|
|
|
#endif
|
2025-10-27 09:28:34 +00:00
|
|
|
{
|
|
|
|
|
var decompressed = new byte[testData.Length];
|
|
|
|
|
var totalRead = 0;
|
|
|
|
|
int bytesRead;
|
2025-10-27 09:37:00 +00:00
|
|
|
while (
|
|
|
|
|
totalRead < decompressed.Length
|
|
|
|
|
&& (
|
|
|
|
|
bytesRead = await gzipStream.ReadAsync(
|
|
|
|
|
decompressed,
|
|
|
|
|
totalRead,
|
|
|
|
|
decompressed.Length - totalRead
|
|
|
|
|
)
|
|
|
|
|
) > 0
|
|
|
|
|
)
|
2025-10-27 09:28:34 +00:00
|
|
|
{
|
|
|
|
|
totalRead += bytesRead;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.Equal(testData.Length, totalRead);
|
|
|
|
|
Assert.Equal(testData, decompressed);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-27 09:00:38 +00:00
|
|
|
}
|