From aac37243ea9c1cbc2feb0d23594afc95cf32dcc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:35:55 +0000 Subject: [PATCH 1/6] Initial plan From a532578e615e33a2c52e4bc4ad9ad11f93b6749d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 21:07:59 +0000 Subject: [PATCH 2/6] Add ZstandardDecompressionOptions and related API Co-authored-by: rzikm <32671551+rzikm@users.noreply.github.com> --- .../ref/System.IO.Compression.cs | 10 +++ .../src/System.IO.Compression.csproj | 1 + .../Zstandard.PlatformNotSupported.cs | 11 +++ .../Compression/Zstandard/ZstandardDecoder.cs | 31 +++++++ .../ZstandardDecompressionOptions.cs | 42 ++++++++++ .../Zstandard/ZstandardStream.Decompress.cs | 17 ++++ .../CompressionStreamUnitTests.Zstandard.cs | 80 +++++++++++++++++++ .../ZstandardCompressionOptionsTests.cs | 49 +++++++++++- .../Zstandard/ZstandardEncoderDecoderTests.cs | 60 ++++++++++++++ 9 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecompressionOptions.cs diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs index f9ce507af62804..3861dd42123ed7 100644 --- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs +++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs @@ -285,6 +285,7 @@ public sealed partial class ZstandardDecoder : System.IDisposable { public ZstandardDecoder() { } public ZstandardDecoder(int maxWindowLog) { } + public ZstandardDecoder(System.IO.Compression.ZstandardDecompressionOptions decompressionOptions) { } public ZstandardDecoder(System.IO.Compression.ZstandardDictionary dictionary) { } public ZstandardDecoder(System.IO.Compression.ZstandardDictionary dictionary, int maxWindowLog) { } public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan source, System.Span destination, out int bytesConsumed, out int bytesWritten) { throw null; } @@ -297,6 +298,14 @@ public void SetPrefix(System.ReadOnlyMemory prefix) { } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("wasi")] + public sealed partial class ZstandardDecompressionOptions + { + public ZstandardDecompressionOptions() { } + public System.IO.Compression.ZstandardDictionary? Dictionary { get { throw null; } set { } } + public int MaxWindowLog { get { throw null; } set { } } + } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("wasi")] public sealed partial class ZstandardDictionary : System.IDisposable { internal ZstandardDictionary() { } @@ -338,6 +347,7 @@ public ZstandardStream(System.IO.Stream stream, System.IO.Compression.Compressio public ZstandardStream(System.IO.Stream stream, System.IO.Compression.CompressionMode mode, System.IO.Compression.ZstandardDictionary dictionary, bool leaveOpen = false) { } public ZstandardStream(System.IO.Stream stream, System.IO.Compression.ZstandardCompressionOptions compressionOptions, bool leaveOpen = false) { } public ZstandardStream(System.IO.Stream stream, System.IO.Compression.ZstandardDecoder decoder, bool leaveOpen = false) { } + public ZstandardStream(System.IO.Stream stream, System.IO.Compression.ZstandardDecompressionOptions decompressionOptions, bool leaveOpen = false) { } public ZstandardStream(System.IO.Stream stream, System.IO.Compression.ZstandardEncoder encoder, bool leaveOpen = false) { } public System.IO.Stream BaseStream { get { throw null; } } public override bool CanRead { get { throw null; } } diff --git a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj index 070415553fe72a..3a6ccc53f6ebd4 100644 --- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj +++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj @@ -92,6 +92,7 @@ + diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs index ffea4a91d779f0..8afd24e4f124f7 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs @@ -3,6 +3,15 @@ namespace System.IO.Compression { + [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatform("wasi")] + public sealed class ZstandardDecompressionOptions + { + public ZstandardDecompressionOptions() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); + public ZstandardDictionary? Dictionary { get => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); set => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); } + public int MaxWindowLog { get => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); set => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); } + } + [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] [System.Runtime.Versioning.UnsupportedOSPlatform("wasi")] public sealed class ZstandardCompressionOptions @@ -28,6 +37,7 @@ public sealed class ZstandardDecoder : IDisposable { public ZstandardDecoder() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(int maxWindowLog) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); + public ZstandardDecoder(ZstandardDecompressionOptions decompressionOptions) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(ZstandardDictionary dictionary) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(ZstandardDictionary dictionary, int maxWindowLog) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public System.Buffers.OperationStatus Decompress(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); @@ -84,6 +94,7 @@ public sealed partial class ZstandardStream : Stream public ZstandardStream(Stream stream, CompressionMode mode, ZstandardDictionary dictionary, bool leaveOpen = false) : base() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardStream(Stream stream, ZstandardCompressionOptions compressionOptions, bool leaveOpen = false) : base() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardStream(Stream stream, ZstandardDecoder decoder, bool leaveOpen = false) : base() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); + public ZstandardStream(Stream stream, ZstandardDecompressionOptions decompressionOptions, bool leaveOpen = false) : base() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardStream(Stream stream, ZstandardEncoder encoder, bool leaveOpen = false) : base() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public Stream BaseStream => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public override bool CanRead => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs index c5a89178bcd6be..c957a92ff3ead0 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs @@ -78,6 +78,37 @@ public ZstandardDecoder(ZstandardDictionary dictionary) } } + /// Initializes a new instance of the class with the specified decompression options. + /// The options to use for Zstandard decompression. + /// is null. + /// Failed to create the instance. + public ZstandardDecoder(ZstandardDecompressionOptions decompressionOptions) + { + ArgumentNullException.ThrowIfNull(decompressionOptions); + + _disposed = false; + + InitializeDecoder(); + + try + { + if (decompressionOptions.MaxWindowLog != 0) + { + SetWindowLog(decompressionOptions.MaxWindowLog); + } + + if (decompressionOptions.Dictionary is not null) + { + SetDictionary(decompressionOptions.Dictionary); + } + } + catch + { + _context.Dispose(); + throw; + } + } + /// Initializes a new instance of the class with the specified dictionary and maximum window size. /// The decompression dictionary to use. /// The maximum window size to use for decompression, expressed as base 2 logarithm. diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecompressionOptions.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecompressionOptions.cs new file mode 100644 index 00000000000000..64ea47c008e1e5 --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecompressionOptions.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Compression +{ + /// Provides decompression options to be used with Zstandard decompression. + [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatform("wasi")] + public sealed class ZstandardDecompressionOptions + { + /// Initializes a new instance of the class. + public ZstandardDecompressionOptions() + { + } + + /// Gets or sets the maximum allowed window size when decompressing payloads, expressed as base 2 logarithm. + /// The maximum window size for decompression, expressed as base 2 logarithm. + /// + /// The valid range is from to . + /// Value 0 indicates the implementation-defined default window size. + /// + /// The value is not 0 and is not between and . + public int MaxWindowLog + { + get; + set + { + if (value != 0) + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, ZstandardUtils.WindowLog_Min, nameof(value)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, ZstandardUtils.WindowLog_Max, nameof(value)); + } + + field = value; + } + } + + /// Gets or sets the dictionary to use for decompression. + /// The decompression dictionary, or if no dictionary is used. + public ZstandardDictionary? Dictionary { get; set; } + } +} diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Decompress.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Decompress.cs index 1e11905d9b69f4..87cc625a45ab50 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Decompress.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Decompress.cs @@ -32,6 +32,23 @@ public ZstandardStream(Stream stream, ZstandardDecoder decoder, bool leaveOpen = _encoderOwned = false; } + /// Initializes a new instance of the class by using the specified stream, decompression options, and optionally leaves the stream open. + /// The stream from which data to decompress is read. + /// The options to use for Zstandard decompression. + /// to leave the stream open after the object is disposed; otherwise, . + /// or is . + /// does not support reading. + public ZstandardStream(Stream stream, ZstandardDecompressionOptions decompressionOptions, bool leaveOpen = false) + { + ArgumentNullException.ThrowIfNull(decompressionOptions); + + Init(stream, CompressionMode.Decompress); + _mode = CompressionMode.Decompress; + _leaveOpen = leaveOpen; + + _decoder = new ZstandardDecoder(decompressionOptions); + } + private bool TryDecompress(Span destination, out int bytesWritten, out OperationStatus lastResult) { Debug.Assert(_decoder != null); diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs index aa6f961de3d77b..2d25fc764116bc 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs @@ -282,5 +282,85 @@ public void StreamTruncation_IsDetected(TestScenario testScenario) }, testScenario.ToString()).Dispose(); } + [Fact] + public void ZstandardStream_WithDecompressionOptions_DecompressesData() + { + byte[] testData = ZstandardTestUtils.CreateTestData(); + byte[] compressedData = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; + ZstandardEncoder.TryCompress(testData, compressedData, out int compressedLength); + Array.Resize(ref compressedData, compressedLength); + + ZstandardDecompressionOptions options = new(); + using MemoryStream input = new(compressedData); + using MemoryStream output = new(); + + using (ZstandardStream decompressionStream = new(input, options, leaveOpen: true)) + { + decompressionStream.CopyTo(output); + } + + Assert.Equal(testData, output.ToArray()); + } + + [Fact] + public void ZstandardStream_WithDecompressionOptions_NullOptions_ThrowsArgumentNullException() + { + using MemoryStream input = new(); + Assert.Throws("decompressionOptions", () => new ZstandardStream(input, (ZstandardDecompressionOptions)null!)); + } + + [Fact] + public void ZstandardStream_WithDecompressionOptions_OwnsDecoder_DisposesOnStreamDispose() + { + byte[] testData = ZstandardTestUtils.CreateTestData(); + byte[] compressedData = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; + ZstandardEncoder.TryCompress(testData, compressedData, out int compressedLength); + Array.Resize(ref compressedData, compressedLength); + + ZstandardDecompressionOptions options = new(); + using MemoryStream input = new(compressedData); + using MemoryStream output = new(); + + ZstandardStream decompressionStream = new(input, options, leaveOpen: true); + decompressionStream.CopyTo(output); + decompressionStream.Dispose(); + + Assert.Equal(testData, output.ToArray()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ZstandardStream_WithDecompressionOptions_WithDictionary_RoundTrips(bool async) + { + byte[] dictionaryData = ZstandardTestUtils.CreateSampleDictionary(); + using ZstandardDictionary dictionary = ZstandardDictionary.Create(dictionaryData); + + byte[] testData = ZstandardTestUtils.CreateTestData(5000); + + using MemoryStream compressedStream = new(); + using (ZstandardStream compressionStream = new(compressedStream, CompressionMode.Compress, dictionary, leaveOpen: true)) + { + if (async) + await compressionStream.WriteAsync(testData, 0, testData.Length); + else + compressionStream.Write(testData, 0, testData.Length); + } + + compressedStream.Position = 0; + + ZstandardDecompressionOptions options = new() { Dictionary = dictionary }; + using MemoryStream decompressedStream = new(); + using (ZstandardStream decompressionStream = new(compressedStream, options)) + { + if (async) + await decompressionStream.CopyToAsync(decompressedStream); + else + decompressionStream.CopyTo(decompressedStream); + } + + Assert.Equal(testData, decompressedStream.ToArray()); + } + } } diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardCompressionOptionsTests.cs b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardCompressionOptionsTests.cs index 34af5d26bd2ccf..4828ade3e73b99 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardCompressionOptionsTests.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardCompressionOptionsTests.cs @@ -76,4 +76,51 @@ public void TargetBlockSize_SetOutOfRange_ThrowsArgumentOutOfRangeException(int Assert.Throws(() => options.TargetBlockSize = targetBlockSize); } } -} \ No newline at end of file + + public class ZstandardDecompressionOptionsTests + { + [Fact] + public void MaxWindowLog_SetToZero_Succeeds() + { + ZstandardDecompressionOptions options = new(); + options.MaxWindowLog = 0; + Assert.Equal(0, options.MaxWindowLog); + } + + [Theory] + [InlineData(10)] + [InlineData(23)] + [InlineData(30)] + public void MaxWindowLog_SetToValidRange_Succeeds(int maxWindowLog) + { + ZstandardDecompressionOptions options = new(); + options.MaxWindowLog = maxWindowLog; + Assert.Equal(maxWindowLog, options.MaxWindowLog); + } + + [Theory] + [InlineData(9)] + [InlineData(32)] + public void MaxWindowLog_SetOutOfRange_ThrowsArgumentOutOfRangeException(int maxWindowLog) + { + ZstandardDecompressionOptions options = new(); + Assert.Throws(() => options.MaxWindowLog = maxWindowLog); + } + + [Fact] + public void Dictionary_SetAndGet_RoundTrips() + { + using ZstandardDictionary dictionary = ZstandardDictionary.Create(ZstandardTestUtils.CreateSampleDictionary()); + ZstandardDecompressionOptions options = new(); + options.Dictionary = dictionary; + Assert.Same(dictionary, options.Dictionary); + } + + [Fact] + public void Dictionary_DefaultValue_IsNull() + { + ZstandardDecompressionOptions options = new(); + Assert.Null(options.Dictionary); + } + } +} diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs index 8faff4e341014b..286aff16307641 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs @@ -576,5 +576,65 @@ public void Compress_AppendChecksum_RoundTrip(bool corrupt) Assert.Equal(input, decompressed); } } + + [Fact] + public void Decoder_Ctor_DecompressionOptions_Null_ThrowsArgumentNullException() + { + Assert.Throws("decompressionOptions", () => new ZstandardDecoder((ZstandardDecompressionOptions)null!)); + } + + [Fact] + public void Decoder_Ctor_DecompressionOptions_Default_Succeeds() + { + ZstandardDecompressionOptions options = new(); + using ZstandardDecoder decoder = new(options); + + byte[] testData = ZstandardTestUtils.CreateTestData(); + byte[] compressed = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; + ZstandardEncoder.TryCompress(testData, compressed, out int compressedLength); + + byte[] decompressed = new byte[testData.Length]; + OperationStatus status = decoder.Decompress(compressed.AsSpan(0, compressedLength), decompressed, out _, out int bytesWritten); + Assert.Equal(OperationStatus.Done, status); + Assert.Equal(testData, decompressed.AsSpan(0, bytesWritten).ToArray()); + } + + [Fact] + public void Decoder_Ctor_DecompressionOptions_WithMaxWindowLog_Succeeds() + { + ZstandardDecompressionOptions options = new() { MaxWindowLog = 10 }; + using ZstandardDecoder decoder = new(options); + + byte[] testData = ZstandardTestUtils.CreateTestData(100); + byte[] compressed = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; + ZstandardEncoder.TryCompress(testData, compressed, out int compressedLength); + + byte[] decompressed = new byte[testData.Length]; + OperationStatus status = decoder.Decompress(compressed.AsSpan(0, compressedLength), decompressed, out _, out int bytesWritten); + Assert.Equal(OperationStatus.Done, status); + Assert.Equal(testData, decompressed.AsSpan(0, bytesWritten).ToArray()); + } + + [Fact] + public void Decoder_Ctor_DecompressionOptions_WithDictionary_RoundTrips() + { + byte[] dictionaryData = ZstandardTestUtils.CreateSampleDictionary(); + using ZstandardDictionary dictionary = ZstandardDictionary.Create(dictionaryData); + + byte[] testData = ZstandardTestUtils.CreateTestData(500); + + // Compress with dictionary + using ZstandardEncoder encoder = new(dictionary); + byte[] compressed = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; + encoder.Compress(testData, compressed, out _, out int compressedLength, isFinalBlock: true); + + // Decompress with ZstandardDecompressionOptions containing the dictionary + ZstandardDecompressionOptions options = new() { Dictionary = dictionary }; + using ZstandardDecoder decoder = new(options); + byte[] decompressed = new byte[testData.Length]; + OperationStatus status = decoder.Decompress(compressed.AsSpan(0, compressedLength), decompressed, out _, out int bytesWritten); + Assert.Equal(OperationStatus.Done, status); + Assert.Equal(testData, decompressed.AsSpan(0, bytesWritten).ToArray()); + } } } \ No newline at end of file From b6bc06d439eba22634c41dcf269fd5adb2f2440f Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Wed, 24 Jun 2026 09:27:04 +0200 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Radek Zikmund <32671551+rzikm@users.noreply.github.com> --- .../ZstandardDecompressionOptions.cs | 4 ---- .../CompressionStreamUnitTests.Zstandard.cs | 19 ------------------- 2 files changed, 23 deletions(-) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecompressionOptions.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecompressionOptions.cs index 64ea47c008e1e5..ed1cea2457dcce 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecompressionOptions.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecompressionOptions.cs @@ -8,10 +8,6 @@ namespace System.IO.Compression [System.Runtime.Versioning.UnsupportedOSPlatform("wasi")] public sealed class ZstandardDecompressionOptions { - /// Initializes a new instance of the class. - public ZstandardDecompressionOptions() - { - } /// Gets or sets the maximum allowed window size when decompressing payloads, expressed as base 2 logarithm. /// The maximum window size for decompression, expressed as base 2 logarithm. diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs index 2d25fc764116bc..e05b3966ec4b77 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs @@ -309,25 +309,6 @@ public void ZstandardStream_WithDecompressionOptions_NullOptions_ThrowsArgumentN Assert.Throws("decompressionOptions", () => new ZstandardStream(input, (ZstandardDecompressionOptions)null!)); } - [Fact] - public void ZstandardStream_WithDecompressionOptions_OwnsDecoder_DisposesOnStreamDispose() - { - byte[] testData = ZstandardTestUtils.CreateTestData(); - byte[] compressedData = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; - ZstandardEncoder.TryCompress(testData, compressedData, out int compressedLength); - Array.Resize(ref compressedData, compressedLength); - - ZstandardDecompressionOptions options = new(); - using MemoryStream input = new(compressedData); - using MemoryStream output = new(); - - ZstandardStream decompressionStream = new(input, options, leaveOpen: true); - decompressionStream.CopyTo(output); - decompressionStream.Dispose(); - - Assert.Equal(testData, output.ToArray()); - } - [Theory] [InlineData(false)] [InlineData(true)] From 0d9617773cdf636236a5702fb2fa07847e591a59 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:57:35 +0000 Subject: [PATCH 4/6] Address code review: OverloadResolutionPriority, test fixes, ownership test Co-authored-by: rzikm <32671551+rzikm@users.noreply.github.com> --- .../ref/System.IO.Compression.cs | 1 + .../Zstandard.PlatformNotSupported.cs | 3 +++ .../Compression/Zstandard/ZstandardDecoder.cs | 2 ++ .../CompressionStreamUnitTests.Zstandard.cs | 16 +++++++++++++ .../ZstandardCompressionOptionsTests.cs | 10 ++------ .../Zstandard/ZstandardEncoderDecoderTests.cs | 24 ++++--------------- 6 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs index 3861dd42123ed7..2f92c24cf1ba64 100644 --- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs +++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs @@ -285,6 +285,7 @@ public sealed partial class ZstandardDecoder : System.IDisposable { public ZstandardDecoder() { } public ZstandardDecoder(int maxWindowLog) { } + [System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)] public ZstandardDecoder(System.IO.Compression.ZstandardDecompressionOptions decompressionOptions) { } public ZstandardDecoder(System.IO.Compression.ZstandardDictionary dictionary) { } public ZstandardDecoder(System.IO.Compression.ZstandardDictionary dictionary, int maxWindowLog) { } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs index 8afd24e4f124f7..0da04b0ebef2d5 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; + namespace System.IO.Compression { [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] @@ -37,6 +39,7 @@ public sealed class ZstandardDecoder : IDisposable { public ZstandardDecoder() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(int maxWindowLog) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); + [OverloadResolutionPriority(-1)] public ZstandardDecoder(ZstandardDecompressionOptions decompressionOptions) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(ZstandardDictionary dictionary) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(ZstandardDictionary dictionary, int maxWindowLog) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs index c957a92ff3ead0..00bb216b96dc1a 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -82,6 +83,7 @@ public ZstandardDecoder(ZstandardDictionary dictionary) /// The options to use for Zstandard decompression. /// is null. /// Failed to create the instance. + [OverloadResolutionPriority(-1)] public ZstandardDecoder(ZstandardDecompressionOptions decompressionOptions) { ArgumentNullException.ThrowIfNull(decompressionOptions); diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs index e05b3966ec4b77..47dc4714be4b0d 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs @@ -343,5 +343,21 @@ public async Task ZstandardStream_WithDecompressionOptions_WithDictionary_RoundT Assert.Equal(testData, decompressedStream.ToArray()); } + [Fact] + public void ZstandardStream_WithDecompressionOptions_DisposedStream_ThrowsObjectDisposedException() + { + byte[] testData = ZstandardTestUtils.CreateTestData(); + byte[] compressedData = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; + ZstandardEncoder.TryCompress(testData, compressedData, out int compressedLength); + Array.Resize(ref compressedData, compressedLength); + + ZstandardDecompressionOptions options = new(); + using MemoryStream input = new(compressedData); + ZstandardStream decompressionStream = new(input, options); + decompressionStream.Dispose(); + + Assert.Throws(() => decompressionStream.Read(new byte[1], 0, 1)); + } + } } diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardCompressionOptionsTests.cs b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardCompressionOptionsTests.cs index 4828ade3e73b99..f5b6f5a9bfdfbc 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardCompressionOptionsTests.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardCompressionOptionsTests.cs @@ -79,15 +79,8 @@ public void TargetBlockSize_SetOutOfRange_ThrowsArgumentOutOfRangeException(int public class ZstandardDecompressionOptionsTests { - [Fact] - public void MaxWindowLog_SetToZero_Succeeds() - { - ZstandardDecompressionOptions options = new(); - options.MaxWindowLog = 0; - Assert.Equal(0, options.MaxWindowLog); - } - [Theory] + [InlineData(0)] [InlineData(10)] [InlineData(23)] [InlineData(30)] @@ -99,6 +92,7 @@ public void MaxWindowLog_SetToValidRange_Succeeds(int maxWindowLog) } [Theory] + [InlineData(1)] [InlineData(9)] [InlineData(32)] public void MaxWindowLog_SetOutOfRange_ThrowsArgumentOutOfRangeException(int maxWindowLog) diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs index 286aff16307641..c46edbcdb40bfc 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs @@ -583,26 +583,12 @@ public void Decoder_Ctor_DecompressionOptions_Null_ThrowsArgumentNullException() Assert.Throws("decompressionOptions", () => new ZstandardDecoder((ZstandardDecompressionOptions)null!)); } - [Fact] - public void Decoder_Ctor_DecompressionOptions_Default_Succeeds() - { - ZstandardDecompressionOptions options = new(); - using ZstandardDecoder decoder = new(options); - - byte[] testData = ZstandardTestUtils.CreateTestData(); - byte[] compressed = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; - ZstandardEncoder.TryCompress(testData, compressed, out int compressedLength); - - byte[] decompressed = new byte[testData.Length]; - OperationStatus status = decoder.Decompress(compressed.AsSpan(0, compressedLength), decompressed, out _, out int bytesWritten); - Assert.Equal(OperationStatus.Done, status); - Assert.Equal(testData, decompressed.AsSpan(0, bytesWritten).ToArray()); - } - - [Fact] - public void Decoder_Ctor_DecompressionOptions_WithMaxWindowLog_Succeeds() + [Theory] + [InlineData(0)] + [InlineData(10)] + public void Decoder_Ctor_DecompressionOptions_Succeeds(int maxWindowLog) { - ZstandardDecompressionOptions options = new() { MaxWindowLog = 10 }; + ZstandardDecompressionOptions options = new() { MaxWindowLog = maxWindowLog }; using ZstandardDecoder decoder = new(options); byte[] testData = ZstandardTestUtils.CreateTestData(100); From f97f266cd5a3f2952d845e922fa57c4af2131624 Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Wed, 24 Jun 2026 17:04:17 +0200 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Radek Zikmund <32671551+rzikm@users.noreply.github.com> --- src/libraries/System.IO.Compression/ref/System.IO.Compression.cs | 1 - .../IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs | 1 - .../src/System/IO/Compression/Zstandard/ZstandardDecoder.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs index 2f92c24cf1ba64..3861dd42123ed7 100644 --- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs +++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs @@ -285,7 +285,6 @@ public sealed partial class ZstandardDecoder : System.IDisposable { public ZstandardDecoder() { } public ZstandardDecoder(int maxWindowLog) { } - [System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)] public ZstandardDecoder(System.IO.Compression.ZstandardDecompressionOptions decompressionOptions) { } public ZstandardDecoder(System.IO.Compression.ZstandardDictionary dictionary) { } public ZstandardDecoder(System.IO.Compression.ZstandardDictionary dictionary, int maxWindowLog) { } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs index 0da04b0ebef2d5..1da10593dd660c 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/Zstandard.PlatformNotSupported.cs @@ -39,7 +39,6 @@ public sealed class ZstandardDecoder : IDisposable { public ZstandardDecoder() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(int maxWindowLog) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); - [OverloadResolutionPriority(-1)] public ZstandardDecoder(ZstandardDecompressionOptions decompressionOptions) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(ZstandardDictionary dictionary) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); public ZstandardDecoder(ZstandardDictionary dictionary, int maxWindowLog) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ZstandardCompression); diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs index 00bb216b96dc1a..4c347d389c9e9e 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDecoder.cs @@ -83,7 +83,6 @@ public ZstandardDecoder(ZstandardDictionary dictionary) /// The options to use for Zstandard decompression. /// is null. /// Failed to create the instance. - [OverloadResolutionPriority(-1)] public ZstandardDecoder(ZstandardDecompressionOptions decompressionOptions) { ArgumentNullException.ThrowIfNull(decompressionOptions); From 52d458f141cac7127cfa149beddf038785ad989b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:49:50 +0000 Subject: [PATCH 6/6] Assert TryCompress return values and Compress OperationStatus in tests Co-authored-by: rzikm <32671551+rzikm@users.noreply.github.com> --- .../tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs | 4 ++-- .../tests/Zstandard/ZstandardEncoderDecoderTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs index 47dc4714be4b0d..2ecc205c8aff32 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs @@ -287,7 +287,7 @@ public void ZstandardStream_WithDecompressionOptions_DecompressesData() { byte[] testData = ZstandardTestUtils.CreateTestData(); byte[] compressedData = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; - ZstandardEncoder.TryCompress(testData, compressedData, out int compressedLength); + Assert.True(ZstandardEncoder.TryCompress(testData, compressedData, out int compressedLength)); Array.Resize(ref compressedData, compressedLength); ZstandardDecompressionOptions options = new(); @@ -348,7 +348,7 @@ public void ZstandardStream_WithDecompressionOptions_DisposedStream_ThrowsObject { byte[] testData = ZstandardTestUtils.CreateTestData(); byte[] compressedData = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; - ZstandardEncoder.TryCompress(testData, compressedData, out int compressedLength); + Assert.True(ZstandardEncoder.TryCompress(testData, compressedData, out int compressedLength)); Array.Resize(ref compressedData, compressedLength); ZstandardDecompressionOptions options = new(); diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs index c46edbcdb40bfc..8eeace81547b8c 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs @@ -593,7 +593,7 @@ public void Decoder_Ctor_DecompressionOptions_Succeeds(int maxWindowLog) byte[] testData = ZstandardTestUtils.CreateTestData(100); byte[] compressed = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; - ZstandardEncoder.TryCompress(testData, compressed, out int compressedLength); + Assert.True(ZstandardEncoder.TryCompress(testData, compressed, out int compressedLength)); byte[] decompressed = new byte[testData.Length]; OperationStatus status = decoder.Decompress(compressed.AsSpan(0, compressedLength), decompressed, out _, out int bytesWritten); @@ -612,7 +612,7 @@ public void Decoder_Ctor_DecompressionOptions_WithDictionary_RoundTrips() // Compress with dictionary using ZstandardEncoder encoder = new(dictionary); byte[] compressed = new byte[ZstandardEncoder.GetMaxCompressedLength(testData.Length)]; - encoder.Compress(testData, compressed, out _, out int compressedLength, isFinalBlock: true); + Assert.Equal(OperationStatus.Done, encoder.Compress(testData, compressed, out _, out int compressedLength, isFinalBlock: true)); // Decompress with ZstandardDecompressionOptions containing the dictionary ZstandardDecompressionOptions options = new() { Dictionary = dictionary };