Compare commits

...

1 Commits

6 changed files with 472 additions and 70 deletions

222
ASYNC_ANALYSIS.md Normal file
View File

@@ -0,0 +1,222 @@
# Async Method Analysis for SharpCompress Stream Implementations
This document analyzes all Stream implementations in SharpCompress to identify which ones need async method implementations and which tests are missing async versions.
## Stream Implementations Analysis
### Criteria
- **Needs Async Read**: Has `Read()` implementation but no proper `ReadAsync()` override (or `ReadAsync` just wraps sync)
- **Needs Async Write**: Has `Write()` implementation but no proper `WriteAsync()` override (or `WriteAsync` just wraps sync)
### ✅ Streams with Proper Async Implementations
| Stream | Location | Read | Write | Notes |
|--------|----------|------|-------|-------|
| TarReadOnlySubStream | Common/Tar/TarReadOnlySubStream.cs | ✅ | N/A | Has proper ReadAsync with async stream delegation |
| EntryStream | Common/EntryStream.cs | ✅ | N/A | Has proper ReadAsync |
| BZip2Stream | Compressors/BZip2/BZip2Stream.cs | ✅ | ✅ | Delegates to inner stream async methods |
| XZStream | Compressors/Xz/XZStream.cs | ✅ | N/A | Has proper async implementation |
| LZipStream | Compressors/LZMA/LZipStream.cs | ✅ | ✅ | Delegates to inner stream async methods |
| LzmaStream | Compressors/LZMA/LzmaStream.cs | ✅ | ⚠️ | Read has proper async; Write wraps sync |
| AesDecoderStream | Compressors/LZMA/AesDecoderStream.cs | ✅ | N/A | Has proper async implementation |
| CrcBuilderStream | Compressors/LZMA/Utilites/CrcBuilderStream.cs | N/A | ✅ | Has proper async write |
| CrcCheckStream | Compressors/LZMA/Utilites/CrcCheckStream.cs | N/A | ⚠️ | WriteAsync wraps sync |
| DeflateStream | Compressors/Deflate/DeflateStream.cs | ✅ | ✅ | Delegates to base stream async |
| ZlibBaseStream | Compressors/Deflate/ZlibBaseStream.cs | ✅ | ✅ | Has proper async implementations |
| RarStream | Compressors/Rar/RarStream.cs | ✅ | ⚠️ | Read has async; Write wraps sync |
| RarCrcStream | Compressors/Rar/RarCrcStream.cs | ✅ | N/A | Extends RarStream with async |
| MultiVolumeReadOnlyStream | Compressors/Rar/MultiVolumeReadOnlyStream.cs | ✅ | N/A | Has proper async read |
| RarBLAKE2spStream | Compressors/Rar/RarBLAKE2spStream.cs | ✅ | N/A | Has proper async read |
| Deflate64Stream | Compressors/Deflate64/Deflate64Stream.cs | ✅ | N/A | Has proper async read |
| ADCStream | Compressors/ADC/ADCStream.cs | ✅ | N/A | Has proper async read |
| ReadOnlySubStream | IO/ReadOnlySubStream.cs | ✅ | N/A | Has proper async read |
| SharpCompressStream | IO/SharpCompressStream.cs | ✅ | ✅ | Has proper async implementations |
---
## ❌ Streams Needing Async Implementation
### High Priority - Has Read but no proper ReadAsync
| Stream | Location | Issue | Complexity |
|--------|----------|-------|------------|
| **Crc32Stream** | Crypto/Crc32Stream.cs | Has `Write()` but no `WriteAsync` | Low - Just add CRC calculation around async write |
| **PkwareTraditionalCryptoStream** | Common/Zip/PkwareTraditionalCryptoStream.cs | Has `Read()` and `Write()` but no async versions | Medium - Crypto operations are synchronous, need to buffer async reads |
| **WinzipAesCryptoStream** | Common/Zip/WinzipAesCryptoStream.cs | Has `Read()` but no `ReadAsync` | Medium - Crypto operations are synchronous, need async stream reads |
| **CBZip2InputStream** | Compressors/BZip2/CBZip2InputStream.cs | `ReadAsync` wraps sync `ReadByte` loop | High - Core decompression is CPU-bound, but stream reads could be async |
| **CBZip2OutputStream** | Compressors/BZip2/CBZip2OutputStream.cs | `WriteAsync` wraps sync `WriteByte` loop | High - Core compression is CPU-bound |
| **Bcj2DecoderStream** | Compressors/LZMA/Bcj2DecoderStream.cs | `ReadAsync` wraps sync - uses iterator/state machine | High - Complex state machine with multiple streams |
| **PpmdStream** | Compressors/PPMd/PpmdStream.cs | No async at all | Medium - Depends on model decoding |
| **ShrinkStream** | Compressors/Shrink/ShrinkStream.cs | No async at all | Low - Single-shot decompression |
| **LzwStream** | Compressors/Lzw/LzwStream.cs | No async at all | Medium - Bit-level operations, but input reads could be async |
| **ExplodeStream** | Compressors/Explode/ExplodeStream.cs | No async at all | Medium - Similar to LzwStream |
| **ArcLzwStream** | Compressors/ArcLzw/ArcLzwStream.cs | No async at all | Low - Single-shot decompression |
| **BufferedSubStream** | IO/BufferedSubStream.cs | No async - uses sync cache refill | Medium - Cache operations could be async |
### Streams Where Async May Not Be Beneficial
| Stream | Location | Reason |
|--------|----------|--------|
| ZStandardStream | Compressors/ZStandard/ZStandardStream.cs | Extends ZstdSharp.DecompressionStream - async behavior depends on base class |
| ReadOnlyStream | Compressors/Xz/ReadOnlyStream.cs | Abstract base - no Read implementation |
| XZReadOnlyStream | Compressors/Xz/XZReadOnlyStream.cs | Abstract base - no Read implementation |
| DecoderStream2 | Compressors/LZMA/DecoderStream.cs | Abstract base - no Read implementation |
---
## Detailed Analysis of Streams Needing Work
### 1. Crc32Stream (Low Priority)
- **Location**: `src/SharpCompress/Crypto/Crc32Stream.cs`
- **Issue**: Has `Write()` (lines 83-87) but no `WriteAsync`
- **Solution**: Add `WriteAsync` that calls base stream's `WriteAsync` and then calculates CRC synchronously (CRC is fast)
### 2. PkwareTraditionalCryptoStream (Medium Priority)
- **Location**: `src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs`
- **Issue**: Has `Read()` (lines 69-86) and `Write()` (lines 88-113) but no async versions
- **Solution**:
- ReadAsync: Read from underlying stream asynchronously, then decrypt synchronously
- WriteAsync: Encrypt synchronously, then write asynchronously
### 3. WinzipAesCryptoStream (Medium Priority)
- **Location**: `src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs`
- **Issue**: Has `Read()` (lines 106-123) but no `ReadAsync`
- **Solution**: Read from stream asynchronously, transform blocks synchronously (crypto is CPU-bound)
### 4. CBZip2InputStream (High Complexity)
- **Location**: `src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs`
- **Issue**: `ReadAsync` (lines 1132-1152) just wraps sync `ReadByte` in a loop
- **Solution**: Would need significant refactoring to make the bit-reading operations async
### 5. CBZip2OutputStream (High Complexity)
- **Location**: `src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs`
- **Issue**: `WriteAsync` (lines 2033-2046) wraps sync `WriteByte` loop
- **Solution**: Would need significant refactoring of compression pipeline
### 6. Bcj2DecoderStream (High Complexity)
- **Location**: `src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs`
- **Issue**: Uses iterator pattern (`Run()`) - `ReadAsync` (lines 196-206) just wraps sync
- **Solution**: Would require rewriting the iterator pattern to support async
### 7. PpmdStream (Medium Priority)
- **Location**: `src/SharpCompress/Compressors/PPMd/PpmdStream.cs`
- **Issue**: No async methods at all
- **Solution**: Add ReadAsync that reads from model decoder asynchronously
### 8. ShrinkStream (Low Priority)
- **Location**: `src/SharpCompress/Compressors/Shrink/ShrinkStream.cs`
- **Issue**: No async - does single-shot decompression in Read
- **Solution**: Could add async wrapper, but single-shot nature limits benefit
### 9. LzwStream (Medium Priority)
- **Location**: `src/SharpCompress/Compressors/Lzw/LzwStream.cs`
- **Issue**: No async methods
- **Solution**: Bit-level operations are sync, but underlying stream reads could be buffered async
### 10. ExplodeStream (Medium Priority)
- **Location**: `src/SharpCompress/Compressors/Explode/ExplodeStream.cs`
- **Issue**: No async methods
- **Solution**: Similar to LzwStream - could async buffer reads from underlying stream
### 11. ArcLzwStream (Low Priority)
- **Location**: `src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs`
- **Issue**: No async - single-shot decompression
- **Solution**: Limited benefit due to single-shot nature
### 12. BufferedSubStream (Medium Priority)
- **Location**: `src/SharpCompress/IO/BufferedSubStream.cs`
- **Issue**: `RefillCache()` uses sync read
- **Solution**: Add async cache refill and ReadAsync
---
## Test Analysis - Missing Async Versions
### Tests WITH Async Versions (Good ✅)
| Sync Test | Async Test |
|-----------|------------|
| BZip2ReaderTests | BZip2StreamAsyncTests |
| TarArchiveTests | TarArchiveAsyncTests |
| TarReaderTests | TarReaderAsyncTests |
| TarWriterTests | TarWriterAsyncTests |
| ZipReaderTests | ZipReaderAsyncTests |
| ZipWriterTests | ZipWriterAsyncTests |
| ZipArchiveTests | ZipArchiveAsyncTests |
| Zip64Tests | Zip64AsyncTests |
| ZipMemoryArchiveWithCrcTests | ZipMemoryArchiveWithCrcAsyncTests |
| RarArchiveTests | RarArchiveAsyncTests |
| RarReaderTests | RarReaderAsyncTests |
| GZipArchiveTests | GZipArchiveAsyncTests |
| GZipReaderTests | GZipReaderAsyncTests |
| GZipWriterTests | GZipWriterAsyncTests |
| XZStreamTests | XZStreamAsyncTests |
| XZHeaderTests | XZHeaderAsyncTests |
| XZIndexTests | XZIndexAsyncTests |
| XZBlockTests | XZBlockAsyncTests |
| SharpCompressStreamTest | SharpCompressStreamAsyncTests |
| RewindableStreamTest | RewindableStreamAsyncTest |
| LzmaStreamTests | LzmaStreamAsyncTests |
| ZlibBaseStreamTests | ZLibBaseStreamAsyncTests |
| ADCTest | AdcAsyncTest |
### Tests WITHOUT Async Versions (Need Work ❌)
| Test File | Location | Priority |
|-----------|----------|----------|
| **SevenZipArchiveTests** | SevenZip/SevenZipArchiveTests.cs | High - 7z is commonly used |
| **ArcReaderTests** | Arc/ArcReaderTests.cs | Low - Less common format |
| **ArjReaderTests** | Arj/ArjReaderTests.cs | Low - Legacy format |
| **Crc32Tests** | Xz/Crc32Tests.cs | Low - Utility tests |
| **Crc64Tests** | Xz/Crc64Tests.cs | Low - Utility tests |
| **RarCRCTest** | Rar/RarCRCTest.cs | Low - Utility tests |
| **RarHeaderFactoryTest** | Rar/RarHeaderFactoryTest.cs | Low - Unit tests |
| **Lzma2Tests** | Xz/Filters/Lzma2Tests.cs | Medium - Filter tests |
| **BCJTests** | Xz/Filters/BCJTests.cs | Medium - Filter tests |
| **BranchExecTests** | Filters/BranchExecTests.cs | Low - Filter tests |
| **ExceptionHierarchyTests** | ExceptionHierarchyTests.cs | N/A - No I/O |
| **UtilityTests** | UtilityTests.cs | N/A - No I/O |
| **Zip64VersionConsistencyTests** | Zip/Zip64VersionConsistencyTests.cs | Low - Unit tests |
### Directory-level Tests Missing Async
| Test File | Has Sync | Needs Async |
|-----------|----------|-------------|
| TarArchiveDirectoryTests | ✅ | ❌ |
| TarWriterDirectoryTests | ✅ | ❌ |
| ZipArchiveDirectoryTests | ✅ | ❌ |
| ZipWriterDirectoryTests | ✅ | ❌ |
| GZipArchiveDirectoryTests | ✅ | ❌ |
| GZipWriterDirectoryTests | ✅ | ❌ |
---
## Summary
### Stream Implementation Work Required
| Priority | Count | Examples |
|----------|-------|----------|
| **High** | 3 | CBZip2InputStream, CBZip2OutputStream, Bcj2DecoderStream |
| **Medium** | 6 | PkwareTraditionalCryptoStream, WinzipAesCryptoStream, PpmdStream, LzwStream, ExplodeStream, BufferedSubStream |
| **Low** | 4 | Crc32Stream, ShrinkStream, ArcLzwStream |
### Test Work Required
| Priority | Count | Examples |
|----------|-------|----------|
| **High** | 1 | SevenZipArchiveTests |
| **Medium** | 2 | Lzma2Tests, BCJTests |
| **Low** | 5 | ArcReaderTests, ArjReaderTests, Crc32Tests, etc. |
| **Directory Tests** | 6 | TarArchiveDirectoryTests, ZipArchiveDirectoryTests, etc. |
---
## Recommendations
1. **Start with Medium-Priority Streams**: PkwareTraditionalCryptoStream and WinzipAesCryptoStream are commonly used in ZIP files and have straightforward async patterns (async read → sync crypto).
2. **Consider the ROI**: The BZip2 and Bcj2 streams are complex to make truly async because their core algorithms are CPU-bound. The benefit would primarily be in avoiding blocking on the underlying stream reads.
3. **Test Coverage**: Prioritize SevenZipArchiveAsyncTests as 7z is a commonly used format.
4. **Directory Tests**: Create async versions of directory tests to ensure async extraction to directories works correctly.

View File

@@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{CDB425
README.md = README.md
FORMATS.md = FORMATS.md
AGENTS.md = AGENTS.md
ASYNC_ANALYSIS.md = ASYNC_ANALYSIS.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCompress.Performance", "tests\SharpCompress.Performance\SharpCompress.Performance.csproj", "{5BDE6DBC-9E5F-4E21-AB71-F138A3E72B17}"

View File

@@ -38,7 +38,7 @@ public static class BinaryUtils
)
{
var bytes = new byte[4];
var read = await stream.ReadFullyAsync(bytes, cancellationToken).ConfigureAwait(false);
var read = await stream.ReadFullyAsync(bytes, 0, 4, cancellationToken).ConfigureAwait(false);
if (!read)
{
throw new EndOfStreamException();

View File

@@ -450,26 +450,30 @@ internal static class Utility
public static async Task<bool> ReadFullyAsync(
this Stream stream,
byte[] buffer,
int? offset = null,
int? total = null,
CancellationToken cancellationToken = default
)
{
var total = 0;
offset ??= 0;
total ??= buffer.Length;
var count = 0;
int read;
while (
(
read = await stream
.ReadAsync(buffer, total, buffer.Length - total, cancellationToken)
.ReadAsync(buffer, offset.Value + count, total.Value - count, cancellationToken)
.ConfigureAwait(false)
) > 0
)
{
total += read;
if (total >= buffer.Length)
count += read;
if (count >= total)
{
return true;
}
}
return (total >= buffer.Length);
return (count >= total);
}
public static string TrimNulls(this string source) => source.Replace('\0', ' ').Trim();

View File

@@ -0,0 +1,167 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.Test.Mocks;
/// <summary>
/// A stream wrapper that only allows async read operations.
/// Throws InvalidOperationException on synchronous Read calls to ensure
/// async code paths are being used.
/// </summary>
public class AsyncOnlyStream : Stream
{
private readonly Stream _stream;
private bool _isDisposed;
public AsyncOnlyStream(Stream stream, bool throwOnSyncMethods = true)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
ThrowOnSyncMethods = throwOnSyncMethods;
}
public bool ThrowOnSyncMethods { get; set; }
public override bool CanRead => _stream.CanRead;
public override bool CanSeek => _stream.CanSeek;
public override bool CanWrite => _stream.CanWrite;
public override long Length => _stream.Length;
public override long Position
{
get => _stream.Position;
set => _stream.Position = value;
}
public override void Flush() => _stream.Flush();
public override Task FlushAsync(CancellationToken cancellationToken) =>
_stream.FlushAsync(cancellationToken);
public override int Read(byte[] buffer, int offset, int count)
{
if (ThrowOnSyncMethods)
{
throw new InvalidOperationException(
"Synchronous Read is not allowed on AsyncOnlyStream. Use ReadAsync instead."
);
}
return _stream.Read(buffer, offset, count);
}
public override int ReadByte()
{
if (ThrowOnSyncMethods)
{
throw new InvalidOperationException(
"Synchronous ReadByte is not allowed on AsyncOnlyStream. Use ReadAsync instead."
);
}
return _stream.ReadByte();
}
public override Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
) => _stream.ReadAsync(buffer, offset, count, cancellationToken);
#if !NETFRAMEWORK && !NETSTANDARD2_0
public override ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
) => _stream.ReadAsync(buffer, cancellationToken);
public override int Read(Span<byte> buffer)
{
if (ThrowOnSyncMethods)
{
throw new InvalidOperationException(
"Synchronous Read is not allowed on AsyncOnlyStream. Use ReadAsync instead."
);
}
return _stream.Read(buffer);
}
#endif
public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin);
public override void SetLength(long value) => _stream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count)
{
if (ThrowOnSyncMethods)
{
throw new InvalidOperationException(
"Synchronous Write is not allowed on AsyncOnlyStream. Use WriteAsync instead."
);
}
_stream.Write(buffer, offset, count);
}
public override void WriteByte(byte value)
{
if (ThrowOnSyncMethods)
{
throw new InvalidOperationException(
"Synchronous WriteByte is not allowed on AsyncOnlyStream. Use WriteAsync instead."
);
}
_stream.WriteByte(value);
}
public override Task WriteAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
) => _stream.WriteAsync(buffer, offset, count, cancellationToken);
#if !NETFRAMEWORK && !NETSTANDARD2_0
public override ValueTask WriteAsync(
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = default
) => _stream.WriteAsync(buffer, cancellationToken);
public override void Write(ReadOnlySpan<byte> buffer)
{
if (ThrowOnSyncMethods)
{
throw new InvalidOperationException(
"Synchronous Write is not allowed on AsyncOnlyStream. Use WriteAsync instead."
);
}
_stream.Write(buffer);
}
#endif
protected override void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
_stream.Dispose();
}
}
base.Dispose(disposing);
}
#if !NETFRAMEWORK && !NETSTANDARD2_0
public override async ValueTask DisposeAsync()
{
if (!_isDisposed)
{
_isDisposed = true;
await _stream.DisposeAsync().ConfigureAwait(false);
}
await base.DisposeAsync().ConfigureAwait(false);
}
#endif
}

View File

@@ -13,12 +13,21 @@ public class ZipReaderAsyncTests : ReaderTests
{
public ZipReaderAsyncTests() => UseExtensionInsteadOfNameToVerify = true;
/// <summary>
/// Opens a file with an AsyncOnlyStream wrapper.
/// ThrowOnSyncMethods starts as false to allow synchronous reads during Open/format detection,
/// then should be set to true after opening to ensure async code paths are used.
/// </summary>
private static AsyncOnlyStream OpenAsyncOnlyFile(string path) =>
new(new ForwardOnlyStream(File.OpenRead(path)), throwOnSyncMethods: false);
[Fact]
public async Task Issue_269_Double_Skip_Async()
{
var path = Path.Combine(TEST_ARCHIVES_PATH, "PrePostHeaders.zip");
using Stream stream = new ForwardOnlyStream(File.OpenRead(path));
using var stream = OpenAsyncOnlyFile(path);
using var reader = ReaderFactory.Open(stream);
stream.ThrowOnSyncMethods = true;
var count = 0;
while (await reader.MoveToNextEntryAsync())
{
@@ -60,10 +69,11 @@ public class ZipReaderAsyncTests : ReaderTests
[Fact]
public async Task Zip_Deflate_Streamed_Skip_Async()
{
using Stream stream = new ForwardOnlyStream(
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"))
using var stream = OpenAsyncOnlyFile(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")
);
using var reader = ReaderFactory.Open(stream);
stream.ThrowOnSyncMethods = true;
var x = 0;
while (await reader.MoveToNextEntryAsync())
{
@@ -116,21 +126,20 @@ public class ZipReaderAsyncTests : ReaderTests
[Fact]
public async Task Zip_BZip2_PkwareEncryption_Read_Async()
{
using (
Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"))
)
using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
using var stream = OpenAsyncOnlyFile(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip")
);
using var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" });
stream.ThrowOnSyncMethods = true;
while (await reader.MoveToNextEntryAsync())
{
while (await reader.MoveToNextEntryAsync())
if (!reader.Entry.IsDirectory)
{
if (!reader.Entry.IsDirectory)
{
Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
VerifyFiles();
@@ -139,11 +148,13 @@ public class ZipReaderAsyncTests : ReaderTests
[Fact]
public async Task Zip_Reader_Disposal_Test_Async()
{
using var stream = new TestStream(
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"))
using var innerStream = OpenAsyncOnlyFile(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")
);
using var stream = new TestStream(innerStream);
using (var reader = ReaderFactory.Open(stream))
{
innerStream.ThrowOnSyncMethods = true;
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
@@ -161,10 +172,12 @@ public class ZipReaderAsyncTests : ReaderTests
[Fact]
public async Task Zip_Reader_Disposal_Test2_Async()
{
using var stream = new TestStream(
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"))
using var innerStream = OpenAsyncOnlyFile(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")
);
using var stream = new TestStream(innerStream);
var reader = ReaderFactory.Open(stream);
innerStream.ThrowOnSyncMethods = true;
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
@@ -182,38 +195,11 @@ public class ZipReaderAsyncTests : ReaderTests
public async Task Zip_LZMA_WinzipAES_Read_Async() =>
await Assert.ThrowsAsync<NotSupportedException>(async () =>
{
using (
Stream stream = File.OpenRead(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip")
)
)
using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
{
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
}
VerifyFiles();
});
[Fact]
public async Task Zip_Deflate_WinzipAES_Read_Async()
{
using (
Stream stream = File.OpenRead(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip")
)
)
using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
{
using var stream = OpenAsyncOnlyFile(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip")
);
using var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" });
stream.ThrowOnSyncMethods = true;
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
@@ -225,6 +211,27 @@ public class ZipReaderAsyncTests : ReaderTests
);
}
}
VerifyFiles();
});
[Fact]
public async Task Zip_Deflate_WinzipAES_Read_Async()
{
using var stream = OpenAsyncOnlyFile(
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip")
);
using var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" });
stream.ThrowOnSyncMethods = true;
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
VerifyFiles();
}
@@ -233,20 +240,21 @@ public class ZipReaderAsyncTests : ReaderTests
public async Task Zip_Deflate_ZipCrypto_Read_Async()
{
var count = 0;
using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip")))
using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
using var stream = OpenAsyncOnlyFile(
Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip")
);
using var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" });
stream.ThrowOnSyncMethods = true;
while (await reader.MoveToNextEntryAsync())
{
while (await reader.MoveToNextEntryAsync())
if (!reader.Entry.IsDirectory)
{
if (!reader.Entry.IsDirectory)
{
Assert.Equal(CompressionType.None, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
count++;
}
Assert.Equal(CompressionType.None, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
count++;
}
}
Assert.Equal(8, count);