Add sync test and attempt to fix async LZMA extraction bug

- Restored original async ExtractAllEntries test with using statement
- Added new ExtractAllEntriesSync test (all tests pass)
- Fixed potential partial read bug in LzmaStream.DecodeChunkHeaderAsync
  - Added ReadFullyAsync helper to ensure complete reads
  - ReadAsync is not guaranteed to return all requested bytes
- Async tests for 7Zip still failing with Data Error
  - Issue appears related to LZMA2 stream state management
  - _needDictReset flag not being cleared correctly in async flow
  - Further investigation needed

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-19 12:15:54 +00:00
parent 48a2ad7b57
commit 93504cf82f
2 changed files with 61 additions and 8 deletions

View File

@@ -425,10 +425,31 @@ public class LzmaStream : Stream, IStreamStack
}
}
private async Task ReadFullyAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
var totalRead = 0;
while (totalRead < count)
{
var read = await _inputStream
.ReadAsync(buffer, offset + totalRead, count - totalRead, cancellationToken)
.ConfigureAwait(false);
if (read == 0)
{
throw new EndOfStreamException();
}
totalRead += read;
}
}
private async Task DecodeChunkHeaderAsync(CancellationToken cancellationToken = default)
{
var controlBuffer = new byte[1];
await _inputStream.ReadAsync(controlBuffer, 0, 1, cancellationToken).ConfigureAwait(false);
await ReadFullyAsync(controlBuffer, 0, 1, cancellationToken).ConfigureAwait(false);
var control = controlBuffer[0];
_inputPosition++;
@@ -455,20 +476,18 @@ public class LzmaStream : Stream, IStreamStack
_availableBytes = (control & 0x1F) << 16;
var buffer = new byte[2];
await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
await ReadFullyAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
_availableBytes += (buffer[0] << 8) + buffer[1] + 1;
_inputPosition += 2;
await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
await ReadFullyAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
_rangeDecoderLimit = (buffer[0] << 8) + buffer[1] + 1;
_inputPosition += 2;
if (control >= 0xC0)
{
_needProps = false;
await _inputStream
.ReadAsync(controlBuffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
await ReadFullyAsync(controlBuffer, 0, 1, cancellationToken).ConfigureAwait(false);
Properties[0] = controlBuffer[0];
_inputPosition++;
@@ -495,7 +514,7 @@ public class LzmaStream : Stream, IStreamStack
{
_uncompressedChunk = true;
var buffer = new byte[2];
await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
await ReadFullyAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
_availableBytes = (buffer[0] << 8) + buffer[1] + 1;
_inputPosition += 2;
}

View File

@@ -1,4 +1,6 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Common;
using SharpCompress.Readers;
@@ -16,7 +18,39 @@ public class ExtractAll : TestBase
[InlineData("7Zip.solid.7z")]
[InlineData("7Zip.nonsolid.7z")]
[InlineData("7Zip.LZMA.7z")]
public void ExtractAllEntries(string archivePath)
public async Task ExtractAllEntries(string archivePath)
{
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, archivePath);
var options = new ExtractionOptions() { ExtractFullPath = true, Overwrite = true };
using var archive = ArchiveFactory.Open(testArchive);
if (archive.IsSolid || archive.Type == ArchiveType.SevenZip)
{
using var reader = archive.ExtractAllEntries();
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{
await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, options);
}
}
}
else
{
archive.ExtractToDirectory(SCRATCH_FILES_PATH, options);
}
}
[Theory]
[InlineData("Zip.deflate.zip")]
[InlineData("Rar5.rar")]
[InlineData("Rar.rar")]
[InlineData("Rar.solid.rar")]
[InlineData("7Zip.solid.7z")]
[InlineData("7Zip.nonsolid.7z")]
[InlineData("7Zip.LZMA.7z")]
public void ExtractAllEntriesSync(string archivePath)
{
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, archivePath);
var options = new ExtractionOptions() { ExtractFullPath = true, Overwrite = true };