From 40b7c69922c5c9d016d32adcba902aab184a13ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:53:50 +0000 Subject: [PATCH 01/28] Initial plan From 479cca3073cb2ea2a3317961fdc214290ad46809 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:58:03 +0000 Subject: [PATCH 02/28] Initial analysis Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- nuget.config | 6 +++--- nuget.config.backup | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 nuget.config.backup diff --git a/nuget.config b/nuget.config index 227ad0c..765346e 100644 --- a/nuget.config +++ b/nuget.config @@ -1,7 +1,7 @@ - + - + - \ No newline at end of file + diff --git a/nuget.config.backup b/nuget.config.backup new file mode 100644 index 0000000..227ad0c --- /dev/null +++ b/nuget.config.backup @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From e0f18f2efb4d23e399784227615eca2fa3e377d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 22:13:02 +0000 Subject: [PATCH 03/28] Changes before error encountered Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- .editorconfig | 1 + Pipelines/recursive-extractor-pr.yml | 4 +-- Pipelines/recursive-extractor-release.yml | 4 +-- RecursiveExtractor.Tests/AssemblyInfo.cs | 3 +++ .../ExtractorTests/BaseExtractorTestClass.cs | 2 ++ .../ExtractorTests/CustomExtractorTests.cs | 26 ++++++++++--------- .../ExtractorTests/DisposeBehaviorTests.cs | 16 +++++------- .../ExtractorTests/EncryptedArchiveTests.cs | 8 +++--- .../ExtractorTests/FilterTests.cs | 14 +++++----- .../ExtractorTests/MiniMagicTests.cs | 2 +- .../ExtractorTests/MiscTests.cs | 22 +++++++++------- .../ExtractorTests/TestQuinesAndSlip.cs | 14 ++++++---- .../ExtractorTests/TimeOutTests.cs | 10 +++---- RecursiveExtractor.Tests/SanitizePathTests.cs | 4 +-- RecursiveExtractor.Tests/testconfig.json | 8 ++++++ nuget.config | 6 ++--- nuget.config.backup | 7 ----- 17 files changed, 80 insertions(+), 71 deletions(-) create mode 100644 RecursiveExtractor.Tests/AssemblyInfo.cs create mode 100644 RecursiveExtractor.Tests/testconfig.json delete mode 100644 nuget.config.backup diff --git a/.editorconfig b/.editorconfig index f195c50..d03ac21 100644 --- a/.editorconfig +++ b/.editorconfig @@ -191,6 +191,7 @@ dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.pascal_case.required_prefix = + dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case diff --git a/Pipelines/recursive-extractor-pr.yml b/Pipelines/recursive-extractor-pr.yml index 3476419..0baf2e6 100644 --- a/Pipelines/recursive-extractor-pr.yml +++ b/Pipelines/recursive-extractor-pr.yml @@ -42,7 +42,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '-- --coverage --report-trx' + dotnetTestArgs: '--logger trx --results-directory $(Build.SourcesDirectory)/TestResults -- --coverage' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '-- --coverage --report-trx' + dotnetTestArgs: '--logger trx --results-directory $(Build.SourcesDirectory)/TestResults -- --coverage' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/Pipelines/recursive-extractor-release.yml b/Pipelines/recursive-extractor-release.yml index b2a85fc..9aca225 100644 --- a/Pipelines/recursive-extractor-release.yml +++ b/Pipelines/recursive-extractor-release.yml @@ -43,7 +43,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '-- --coverage --report-trx' + dotnetTestArgs: '--logger trx --results-directory $(Build.SourcesDirectory)/TestResults -- --coverage' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '-- --coverage --report-trx' + dotnetTestArgs: '--logger trx --results-directory $(Build.SourcesDirectory)/TestResults -- --coverage' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/RecursiveExtractor.Tests/AssemblyInfo.cs b/RecursiveExtractor.Tests/AssemblyInfo.cs new file mode 100644 index 0000000..38707ae --- /dev/null +++ b/RecursiveExtractor.Tests/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs index d223a1a..7c301b5 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs @@ -8,6 +8,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests; public class BaseExtractorTestClass { + public TestContext TestContext { get; set; } = null!; + [ClassCleanup] public static void ClassCleanup() { diff --git a/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs b/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs index 547ba87..048cf04 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs @@ -12,6 +12,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests; [TestClass] public class CustomExtractorTests { + public TestContext TestContext { get; set; } = null!; + /// /// A simple test custom extractor that extracts files with a specific magic number /// For testing purposes, it recognizes files starting with "CUSTOM1" @@ -127,7 +129,7 @@ public void Constructor_WithCustomExtractors_RegistersExtractors() var customExtractor = new TestCustomExtractor(null!); var extractor = new Extractor(new[] { customExtractor }); - Assert.AreEqual(1, extractor.CustomExtractors.Count); + Assert.HasCount(extractor.CustomExtractors, 1); } [TestMethod] @@ -137,7 +139,7 @@ public void Constructor_WithMultipleCustomExtractors_RegistersAll() var customExtractor2 = new SecondTestCustomExtractor(null!); var extractor = new Extractor(new ICustomAsyncExtractor[] { customExtractor1, customExtractor2 }); - Assert.AreEqual(2, extractor.CustomExtractors.Count); + Assert.HasCount(extractor.CustomExtractors, 2); } [TestMethod] @@ -146,7 +148,7 @@ public void Constructor_WithNullInCollection_IgnoresNull() var customExtractor = new TestCustomExtractor(null!); var extractor = new Extractor(new ICustomAsyncExtractor[] { customExtractor, null! }); - Assert.AreEqual(1, extractor.CustomExtractors.Count); + Assert.HasCount(extractor.CustomExtractors, 1); } [TestMethod] @@ -154,7 +156,7 @@ public void Constructor_WithNullCollection_CreatesEmptyExtractor() { var extractor = new Extractor((IEnumerable)null!); - Assert.AreEqual(0, extractor.CustomExtractors.Count); + Assert.IsEmpty(extractor.CustomExtractors); } [TestMethod] @@ -167,7 +169,7 @@ public void Extract_WithMatchingCustomExtractor_UsesCustomExtractor() var testData = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 This is test data"); var results = extractor.Extract("test.custom", testData).ToList(); - Assert.AreEqual(1, results.Count); + Assert.HasCount(results, 1); Assert.AreEqual("extracted_from_custom.txt", results[0].Name); // Read the content to verify it was processed by our custom extractor @@ -185,9 +187,9 @@ public async Task ExtractAsync_WithMatchingCustomExtractor_UsesCustomExtractor() // Create a test file with the custom magic bytes var testData = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 This is test data"); - var results = await extractor.ExtractAsync("test.custom", testData).ToListAsync(); + var results = await extractor.ExtractAsync("test.custom", testData).ToListAsync(TestContext.CancellationTokenSource.Token); - Assert.AreEqual(1, results.Count); + Assert.HasCount(results, 1); Assert.AreEqual("extracted_from_custom.txt", results[0].Name); // Read the content to verify it was processed by our custom extractor @@ -208,7 +210,7 @@ public void Extract_WithoutMatchingCustomExtractor_ReturnsOriginalFile() var results = extractor.Extract("test.txt", testData).ToList(); // Should return the original file since no custom extractor matched - Assert.AreEqual(1, results.Count); + Assert.HasCount(results, 1); Assert.AreEqual("test.txt", results[0].Name); // Verify it's the original content @@ -230,13 +232,13 @@ public void Extract_MultipleCustomExtractors_UsesCorrectOne() // Test with first custom format var testData1 = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 data"); var results1 = extractor.Extract("test1.custom", testData1).ToList(); - Assert.AreEqual(1, results1.Count); + Assert.HasCount(results1, 1); Assert.AreEqual("extracted_from_custom.txt", results1[0].Name); // Test with second custom format var testData2 = System.Text.Encoding.ASCII.GetBytes("CUSTOM2 data"); var results2 = extractor.Extract("test2.custom", testData2).ToList(); - Assert.AreEqual(1, results2.Count); + Assert.HasCount(results2, 1); Assert.AreEqual("extracted_from_second_custom.txt", results2[0].Name); } @@ -250,7 +252,7 @@ public void Extract_NoCustomExtractors_ReturnsOriginalFile() var results = extractor.Extract("test.custom", testData).ToList(); // Should return the original file since no custom extractor is registered - Assert.AreEqual(1, results.Count); + Assert.HasCount(results, 1); Assert.AreEqual("test.custom", results[0].Name); } @@ -267,7 +269,7 @@ public void Extract_CustomExtractorForKnownFormat_UsesBuiltInExtractor() var results = extractor.Extract(path).ToList(); // Should extract the ZIP normally, not use the custom extractor - Assert.IsTrue(results.Count > 0); + Assert.IsGreaterThan(results.Count, 0); Assert.IsTrue(results.Any(r => r.Name.Contains("EmptyFile"))); } } diff --git a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs index bab3061..08b9b91 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs @@ -11,7 +11,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; [TestClass] public class DisposeBehaviorTests : BaseExtractorTestClass { - [DataTestMethod] + [TestMethod] [DataRow("TestData.7z", 3, false)] [DataRow("TestData.tar", 6, false)] [DataRow("TestData.rar", 3, false)] @@ -43,8 +43,6 @@ public class DisposeBehaviorTests : BaseExtractorTestClass [DataRow("EmptyFile.txt", 1, true)] [DataRow("TestDataArchivesNested.Zip", 54, true)] [DataRow("TestDataArchivesNested.Zip", 54, false)] - [DataRow("TestDataArchivesNested.Zip", 54, true)] - [DataRow("TestDataArchivesNested.Zip", 54, false)] public void ExtractArchiveAndDisposeWhileEnumerating(string fileName, int expectedNumFiles = 3, bool parallel = false) { @@ -63,11 +61,11 @@ public void ExtractArchiveAndDisposeWhileEnumerating(string fileName, int expect Assert.AreEqual(expectedNumFiles, disposedResults.Count); foreach (var disposedResult in disposedResults) { - Assert.ThrowsException(() => disposedResult.Content.Position); + Assert.ThrowsExactly(() => disposedResult.Content.Position); } } - [DataTestMethod] + [TestMethod] [DataRow("TestData.7z", 3, false)] [DataRow("TestData.tar", 6, false)] [DataRow("TestData.rar", 3, false)] @@ -99,8 +97,6 @@ public void ExtractArchiveAndDisposeWhileEnumerating(string fileName, int expect [DataRow("EmptyFile.txt", 1, true)] [DataRow("TestDataArchivesNested.Zip", 54, true)] [DataRow("TestDataArchivesNested.Zip", 54, false)] - [DataRow("TestDataArchivesNested.Zip", 54, true)] - [DataRow("TestDataArchivesNested.Zip", 54, false)] public async Task ExtractArchiveAndDisposeWhileEnumeratingAsync(string fileName, int expectedNumFiles = 3, bool parallel = false) { @@ -119,11 +115,11 @@ public async Task ExtractArchiveAndDisposeWhileEnumeratingAsync(string fileName, Assert.AreEqual(expectedNumFiles, disposedResults.Count); foreach (var disposedResult in disposedResults) { - Assert.ThrowsException(() => disposedResult.Content.Position); + Assert.ThrowsExactly(() => disposedResult.Content.Position); } } - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip")] public void EnsureDisposedWithExtractToDirectory(string fileName) { @@ -146,7 +142,7 @@ public void EnsureDisposedWithExtractToDirectory(string fileName) } } - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip")] public async Task EnsureDisposedWithExtractToDirectoryAsync(string fileName) { diff --git a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs index 9d034e7..05b7d9e 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs @@ -11,7 +11,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; [TestClass] public class EncryptedArchiveTests : BaseExtractorTestClass { - [DataTestMethod] + [TestMethod] [DataRow("TestDataEncryptedZipCrypto.zip")] [DataRow("TestDataEncryptedAes.zip")] [DataRow("TestDataEncrypted.7z")] @@ -26,7 +26,7 @@ public void FileTypeSetCorrectlyForEncryptedArchives(string fileName, int expect Assert.AreEqual(FileEntryStatus.EncryptedArchive, results.First().EntryStatus); } - [DataTestMethod] + [TestMethod] [DataRow("TestDataEncryptedZipCrypto.zip")] [DataRow("TestDataEncryptedAes.zip")] [DataRow("TestDataEncrypted.7z")] @@ -46,7 +46,7 @@ public async Task FileTypeSetCorrectlyForEncryptedArchivesAsync(string fileName, Assert.AreEqual(FileEntryStatus.EncryptedArchive, results.First().EntryStatus); } - [DataTestMethod] + [TestMethod] [DataRow("TestDataEncryptedZipCrypto.zip")] [DataRow("TestDataEncryptedAes.zip")] [DataRow("TestDataEncrypted.7z")] @@ -64,7 +64,7 @@ public void ExtractEncryptedArchive(string fileName, int expectedNumFiles = 3) Assert.AreEqual(0, results.Count(x => x.EntryStatus == FileEntryStatus.EncryptedArchive || x.EntryStatus == FileEntryStatus.FailedArchive)); } - [DataTestMethod] + [TestMethod] [DataRow("TestDataEncryptedZipCrypto.zip")] [DataRow("TestDataEncryptedAes.zip")] [DataRow("TestDataEncrypted.7z")] diff --git a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs index 4fcc28a..c5828a0 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs @@ -10,7 +10,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; [TestClass] public class FilterTests : BaseExtractorTestClass { - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip")] [DataRow("TestData.7z")] [DataRow("TestData.tar")] @@ -42,7 +42,7 @@ public async Task ExtractArchiveAsyncAllowFiltered(string fileName, int expected Assert.AreEqual(expectedNumFiles, numResults); } - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip")] [DataRow("TestData.7z")] [DataRow("TestData.tar")] @@ -68,7 +68,7 @@ public void ExtractArchiveAllowFiltered(string fileName, int expectedNumFiles = Assert.AreEqual(expectedNumFiles, results.Count()); } - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip")] [DataRow("TestData.7z")] [DataRow("TestData.tar")] @@ -94,7 +94,7 @@ public void ExtractArchiveParallelAllowFiltered(string fileName, int expectedNum Assert.AreEqual(expectedNumFiles, results.Count()); } - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip", 4)] [DataRow("TestData.7z")] [DataRow("TestData.tar", 5)] @@ -119,7 +119,7 @@ public void ExtractArchiveDenyFiltered(string fileName, int expectedNumFiles = 2 Assert.AreEqual(expectedNumFiles, results.Count()); } - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip", 4)] [DataRow("TestData.7z")] [DataRow("TestData.tar", 5)] @@ -145,7 +145,7 @@ public void ExtractArchiveParallelDenyFiltered(string fileName, int expectedNumF Assert.AreEqual(expectedNumFiles, results.Count()); } - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip", 4)] [DataRow("TestData.7z")] [DataRow("TestData.tar", 5)] @@ -177,7 +177,7 @@ public async Task ExtractArchiveAsyncDenyFiltered(string fileName, int expectedN Assert.AreEqual(expectedNumFiles, numResults); } - [DataTestMethod] + [TestMethod] [DataRow(ArchiveFileType.ZIP, new[] { ArchiveFileType.ZIP }, new ArchiveFileType[] { }, false)] [DataRow(ArchiveFileType.ZIP, new[] { ArchiveFileType.TAR }, new ArchiveFileType[] { }, true)] [DataRow(ArchiveFileType.ZIP, new ArchiveFileType[] { }, new[] { ArchiveFileType.ZIP }, true)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs index 016ef13..d45b91a 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs @@ -7,7 +7,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; [TestClass] public class MiniMagicTests : BaseExtractorTestClass { - [DataTestMethod] + [TestMethod] [DataRow("TestData.zip", ArchiveFileType.ZIP)] [DataRow("TestData.7z", ArchiveFileType.P7ZIP)] [DataRow("TestData.Tar", ArchiveFileType.TAR)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs index e375904..c06a75c 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs @@ -10,7 +10,9 @@ namespace RecursiveExtractor.Tests.ExtractorTests; [TestClass] public class MiscTests { - [DataTestMethod] + public TestContext TestContext { get; set; } = null!; + + [TestMethod] [DataRow("TestDataCorrupt.tar", false, 0, 1)] [DataRow("TestDataCorrupt.tar", true, 1, 1)] [DataRow("TestDataCorrupt.tar.zip", false, 0, 2)] @@ -20,7 +22,7 @@ public async Task ExtractCorruptArchiveAsync(string fileName, bool requireTopLev var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = await extractor.ExtractAsync(path, - new ExtractorOptions() { RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(); + new ExtractorOptions() { RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(TestContext.CancellationTokenSource.Token); Assert.AreEqual(expectedNumFiles, results.Count); @@ -28,23 +30,23 @@ public async Task ExtractCorruptArchiveAsync(string fileName, bool requireTopLev Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); } - [DataTestMethod] + [TestMethod] [DataRow("Lorem.txt", true, 1)] [DataRow("Lorem.txt", false, 0)] public async Task ExtractFlatFileAsync(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestData", fileName); - var results = await extractor.ExtractAsync(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(); + var results = await extractor.ExtractAsync(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(TestContext.CancellationTokenSource.Token); - Assert.AreEqual(1, results.Count); + Assert.HasCount(results, 1); var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive); Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); } - [DataTestMethod] + [TestMethod] [DataRow("TestDataCorrupt.tar", false, 0, 1)] [DataRow("TestDataCorrupt.tar", true, 1, 1)] [DataRow("TestDataCorrupt.tar.zip", false, 0, 2)] @@ -61,7 +63,7 @@ public void ExtractCorruptArchive(string fileName, bool requireTopLevelToBeArchi Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); } - [DataTestMethod] + [TestMethod] [DataRow("Lorem.txt", true, 1)] [DataRow("Lorem.txt", false, 0)] public void ExtractFlatFile(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures) @@ -70,12 +72,12 @@ public void ExtractFlatFile(string fileName, bool requireTopLevelToBeArchive, in var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestData", fileName); var results = extractor.Extract(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToList(); - Assert.AreEqual(1, results.Count); + Assert.HasCount(results, 1); var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive); Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); } - [DataTestMethod] + [TestMethod] [DataRow("EmptyFile.txt")] [DataRow("TestData.zip", ".zip")] public void ExtractAsRaw(string fileName, string? RawExtension = null) @@ -88,6 +90,6 @@ public void ExtractAsRaw(string fileName, string? RawExtension = null) var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, options); - Assert.AreEqual(1, results.Count()); + Assert.HasCount(results, 1); } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs index e2da32d..252820b 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs @@ -42,7 +42,7 @@ public async Task TestZipSlipAsync(string fileName) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "Bombs", fileName); - var results = await extractor.ExtractAsync(path, new ExtractorOptions()).ToListAsync(); + var results = await extractor.ExtractAsync(path, new ExtractorOptions()).ToListAsync(TestContext.CancellationTokenSource.Token); Assert.IsTrue(results.All(x => !x.FullPath.Contains(".."))); } @@ -65,21 +65,25 @@ public static IEnumerable QuineBombNames [TestMethod] [DynamicData(nameof(QuineBombNames))] - [ExpectedException(typeof(OverflowException))] public void TestQuineBombs(string fileName) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "Bombs", fileName); - _ = extractor.Extract(path, new ExtractorOptions() { MemoryStreamCutoff = 1024 * 1024 * 1024 }).ToList(); + Assert.ThrowsExactly(() => + { + _ = extractor.Extract(path, new ExtractorOptions() { MemoryStreamCutoff = 1024 * 1024 * 1024 }).ToList(); + }); } [TestMethod] [DynamicData(nameof(QuineBombNames))] - [ExpectedException(typeof(OverflowException))] public async Task TestQuineBombsAsync(string fileName) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "Bombs", fileName); - _ = await extractor.ExtractAsync(path, new ExtractorOptions() { MemoryStreamCutoff = 1024 * 1024 * 1024 }).ToListAsync(); + await Assert.ThrowsExactlyAsync(async () => + { + _ = await extractor.ExtractAsync(path, new ExtractorOptions() { MemoryStreamCutoff = 1024 * 1024 * 1024 }).ToListAsync(TestContext.CancellationTokenSource.Token); + }); } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs index 2f2c845..16ec939 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs @@ -9,7 +9,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; [TestClass] public class TimeOutTests : BaseExtractorTestClass { - [DataTestMethod] + [TestMethod] [DataRow("TestData.7z", 3, false)] [DataRow("TestData.tar", 6, false)] [DataRow("TestData.rar", 3, false)] @@ -45,7 +45,7 @@ public void TimeoutTest(string fileName, int expectedNumFiles = 3, bool parallel { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => { var results = extractor.Extract(path, new ExtractorOptions() @@ -63,7 +63,7 @@ public void TimeoutTest(string fileName, int expectedNumFiles = 3, bool parallel }); } - [DataTestMethod] + [TestMethod] [DataRow("TestData.7z", 3, false)] [DataRow("TestData.tar", 6, false)] [DataRow("TestData.rar", 3, false)] @@ -95,13 +95,11 @@ public void TimeoutTest(string fileName, int expectedNumFiles = 3, bool parallel [DataRow("EmptyFile.txt", 1, true)] [DataRow("TestDataArchivesNested.Zip", 54, true)] [DataRow("TestDataArchivesNested.Zip", 54, false)] - [DataRow("TestDataArchivesNested.Zip", 54, true)] - [DataRow("TestDataArchivesNested.Zip", 54, false)] public async Task TimeoutTestAsync(string fileName, int expectedNumFiles = 3, bool parallel = false) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); - await Assert.ThrowsExceptionAsync(async () => + await Assert.ThrowsExactlyAsync(async () => { var results = extractor.ExtractAsync(path, new ExtractorOptions() diff --git a/RecursiveExtractor.Tests/SanitizePathTests.cs b/RecursiveExtractor.Tests/SanitizePathTests.cs index f8d56a2..774cf11 100644 --- a/RecursiveExtractor.Tests/SanitizePathTests.cs +++ b/RecursiveExtractor.Tests/SanitizePathTests.cs @@ -10,7 +10,7 @@ namespace RecursiveExtractor.Tests [TestClass] public class SanitizePathTests { - [DataTestMethod] + [TestMethod] [DataRow("a\\file\\with:colon.name", "a\\file\\with_colon.name")] [DataRow("a\\folder:with\\colon.name", "a\\folder_with\\colon.name")] @@ -23,7 +23,7 @@ public void TestSanitizePathWindows(string windowsInputPath, string expectedWind } } - [DataTestMethod] + [TestMethod] [DataRow("a/file/with:colon.name", "a/file/with_colon.name")] [DataRow("a/folder:with/colon.name", "a/folder_with/colon.name")] diff --git a/RecursiveExtractor.Tests/testconfig.json b/RecursiveExtractor.Tests/testconfig.json new file mode 100644 index 0000000..fb8cb9c --- /dev/null +++ b/RecursiveExtractor.Tests/testconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/testconfig.json", + "testingPlatform": { + "trxReport": { + "enabled": true + } + } +} diff --git a/nuget.config b/nuget.config index 765346e..227ad0c 100644 --- a/nuget.config +++ b/nuget.config @@ -1,7 +1,7 @@ - + - + - + \ No newline at end of file diff --git a/nuget.config.backup b/nuget.config.backup deleted file mode 100644 index 227ad0c..0000000 --- a/nuget.config.backup +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file From cca4e14ac9cd2671a00587cc6438a2287a87186a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 22:37:59 +0000 Subject: [PATCH 04/28] Fix all MSTest warnings: replace DataTestMethod, ThrowsException, ExpectedException, duplicate DataRows, add parallelization, use Assert.HasCount/IsEmpty/IsGreaterThan, add CancellationToken support. Update pipeline to use standard trx logger with results directory. Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- .editorconfig | 1 - .../ExtractorTests/CustomExtractorTests.cs | 18 +++++++++--------- .../ExtractorTests/MiscTests.cs | 6 +++--- RecursiveExtractor.Tests/testconfig.json | 8 -------- 4 files changed, 12 insertions(+), 21 deletions(-) delete mode 100644 RecursiveExtractor.Tests/testconfig.json diff --git a/.editorconfig b/.editorconfig index d03ac21..f195c50 100644 --- a/.editorconfig +++ b/.editorconfig @@ -191,7 +191,6 @@ dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.pascal_case.required_prefix = - dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case diff --git a/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs b/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs index 048cf04..281e1c9 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs @@ -129,7 +129,7 @@ public void Constructor_WithCustomExtractors_RegistersExtractors() var customExtractor = new TestCustomExtractor(null!); var extractor = new Extractor(new[] { customExtractor }); - Assert.HasCount(extractor.CustomExtractors, 1); + Assert.HasCount(1, extractor.CustomExtractors); } [TestMethod] @@ -139,7 +139,7 @@ public void Constructor_WithMultipleCustomExtractors_RegistersAll() var customExtractor2 = new SecondTestCustomExtractor(null!); var extractor = new Extractor(new ICustomAsyncExtractor[] { customExtractor1, customExtractor2 }); - Assert.HasCount(extractor.CustomExtractors, 2); + Assert.HasCount(2, extractor.CustomExtractors); } [TestMethod] @@ -148,7 +148,7 @@ public void Constructor_WithNullInCollection_IgnoresNull() var customExtractor = new TestCustomExtractor(null!); var extractor = new Extractor(new ICustomAsyncExtractor[] { customExtractor, null! }); - Assert.HasCount(extractor.CustomExtractors, 1); + Assert.HasCount(1, extractor.CustomExtractors); } [TestMethod] @@ -169,7 +169,7 @@ public void Extract_WithMatchingCustomExtractor_UsesCustomExtractor() var testData = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 This is test data"); var results = extractor.Extract("test.custom", testData).ToList(); - Assert.HasCount(results, 1); + Assert.HasCount(1, results); Assert.AreEqual("extracted_from_custom.txt", results[0].Name); // Read the content to verify it was processed by our custom extractor @@ -189,7 +189,7 @@ public async Task ExtractAsync_WithMatchingCustomExtractor_UsesCustomExtractor() var testData = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 This is test data"); var results = await extractor.ExtractAsync("test.custom", testData).ToListAsync(TestContext.CancellationTokenSource.Token); - Assert.HasCount(results, 1); + Assert.HasCount(1, results); Assert.AreEqual("extracted_from_custom.txt", results[0].Name); // Read the content to verify it was processed by our custom extractor @@ -210,7 +210,7 @@ public void Extract_WithoutMatchingCustomExtractor_ReturnsOriginalFile() var results = extractor.Extract("test.txt", testData).ToList(); // Should return the original file since no custom extractor matched - Assert.HasCount(results, 1); + Assert.HasCount(1, results); Assert.AreEqual("test.txt", results[0].Name); // Verify it's the original content @@ -232,13 +232,13 @@ public void Extract_MultipleCustomExtractors_UsesCorrectOne() // Test with first custom format var testData1 = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 data"); var results1 = extractor.Extract("test1.custom", testData1).ToList(); - Assert.HasCount(results1, 1); + Assert.HasCount(1, results1); Assert.AreEqual("extracted_from_custom.txt", results1[0].Name); // Test with second custom format var testData2 = System.Text.Encoding.ASCII.GetBytes("CUSTOM2 data"); var results2 = extractor.Extract("test2.custom", testData2).ToList(); - Assert.HasCount(results2, 1); + Assert.HasCount(1, results2); Assert.AreEqual("extracted_from_second_custom.txt", results2[0].Name); } @@ -252,7 +252,7 @@ public void Extract_NoCustomExtractors_ReturnsOriginalFile() var results = extractor.Extract("test.custom", testData).ToList(); // Should return the original file since no custom extractor is registered - Assert.HasCount(results, 1); + Assert.HasCount(1, results); Assert.AreEqual("test.custom", results[0].Name); } diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs index c06a75c..5b9aa10 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs @@ -39,7 +39,7 @@ public async Task ExtractFlatFileAsync(string fileName, bool requireTopLevelToBe var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestData", fileName); var results = await extractor.ExtractAsync(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(TestContext.CancellationTokenSource.Token); - Assert.HasCount(results, 1); + Assert.HasCount(1, results); var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive); Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); } @@ -72,7 +72,7 @@ public void ExtractFlatFile(string fileName, bool requireTopLevelToBeArchive, in var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestData", fileName); var results = extractor.Extract(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToList(); - Assert.HasCount(results, 1); + Assert.HasCount(1, results); var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive); Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); } @@ -90,6 +90,6 @@ public void ExtractAsRaw(string fileName, string? RawExtension = null) var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, options); - Assert.HasCount(results, 1); + Assert.HasCount(1, results); } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/testconfig.json b/RecursiveExtractor.Tests/testconfig.json deleted file mode 100644 index fb8cb9c..0000000 --- a/RecursiveExtractor.Tests/testconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/testconfig.json", - "testingPlatform": { - "trxReport": { - "enabled": true - } - } -} From bdfa42c2fc48ddce0d9ba5b0ccb4782cc46beba0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:09:37 +0000 Subject: [PATCH 05/28] Revert pipeline dotnetTestArgs to original format to fix argument parsing error Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- Pipelines/recursive-extractor-pr.yml | 4 ++-- Pipelines/recursive-extractor-release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Pipelines/recursive-extractor-pr.yml b/Pipelines/recursive-extractor-pr.yml index 0baf2e6..3476419 100644 --- a/Pipelines/recursive-extractor-pr.yml +++ b/Pipelines/recursive-extractor-pr.yml @@ -42,7 +42,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory $(Build.SourcesDirectory)/TestResults -- --coverage' + dotnetTestArgs: '-- --coverage --report-trx' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory $(Build.SourcesDirectory)/TestResults -- --coverage' + dotnetTestArgs: '-- --coverage --report-trx' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/Pipelines/recursive-extractor-release.yml b/Pipelines/recursive-extractor-release.yml index 9aca225..b2a85fc 100644 --- a/Pipelines/recursive-extractor-release.yml +++ b/Pipelines/recursive-extractor-release.yml @@ -43,7 +43,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory $(Build.SourcesDirectory)/TestResults -- --coverage' + dotnetTestArgs: '-- --coverage --report-trx' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory $(Build.SourcesDirectory)/TestResults -- --coverage' + dotnetTestArgs: '-- --coverage --report-trx' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' From 1e3c7b202ef50406ed6e3d76dd3d4e8bea3366d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:13:40 +0000 Subject: [PATCH 06/28] Fix test result reporting: use --logger trx with relative results directory Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- Pipelines/recursive-extractor-pr.yml | 4 ++-- Pipelines/recursive-extractor-release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Pipelines/recursive-extractor-pr.yml b/Pipelines/recursive-extractor-pr.yml index 3476419..be9ac8a 100644 --- a/Pipelines/recursive-extractor-pr.yml +++ b/Pipelines/recursive-extractor-pr.yml @@ -42,7 +42,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '-- --coverage --report-trx' + dotnetTestArgs: '--logger trx --results-directory TestResults -- --coverage' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '-- --coverage --report-trx' + dotnetTestArgs: '--logger trx --results-directory TestResults -- --coverage' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/Pipelines/recursive-extractor-release.yml b/Pipelines/recursive-extractor-release.yml index b2a85fc..ab1dd80 100644 --- a/Pipelines/recursive-extractor-release.yml +++ b/Pipelines/recursive-extractor-release.yml @@ -43,7 +43,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '-- --coverage --report-trx' + dotnetTestArgs: '--logger trx --results-directory TestResults -- --coverage' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '-- --coverage --report-trx' + dotnetTestArgs: '--logger trx --results-directory TestResults -- --coverage' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' From 71bdc4f4844a52628cdf1e29354874735c0c03c0 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 17:18:02 -0800 Subject: [PATCH 07/28] Update FileStream usage in tests to specify access/share Explicitly set FileAccess.Read and FileShare.Read when opening FileStream in test methods. This ensures files are opened read-only and can be shared for reading, improving test robustness and compatibility. No changes to test logic. --- .../ExtractorTests/ExpectedNumFilesTests.cs | 8 ++++---- RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs index ab9544c..dfa5a28 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs @@ -223,9 +223,9 @@ public async Task ExtractArchiveAsync(string fileName, int expectedNumFiles) [DynamicData(nameof(ArchiveData))] public async Task ExtractArchiveFromStreamAsync(string fileName, int expectedNumFiles) { - var extractor = new Extractor(); + var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); - using var stream = new FileStream(path, FileMode.Open); + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); var results = extractor.ExtractAsync(path, stream, new ExtractorOptions()); var numFiles = 0; await foreach (var result in results) @@ -240,9 +240,9 @@ public async Task ExtractArchiveFromStreamAsync(string fileName, int expectedNum [DynamicData(nameof(ArchiveData))] public void ExtractArchiveFromStream(string fileName, int expectedNumFiles) { - var extractor = new Extractor(); + var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); - using var stream = new FileStream(path, FileMode.Open); + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); var results = extractor.Extract(path, stream, GetExtractorOptions()); Assert.AreEqual(expectedNumFiles, results.Count()); stream.Close(); diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs index d45b91a..6253c7b 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs @@ -28,7 +28,7 @@ public class MiniMagicTests : BaseExtractorTestClass public void TestMiniMagic(string fileName, ArchiveFileType expectedArchiveFileType) { var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); - using var fs = new FileStream(path, FileMode.Open); + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); // Test just based on the content var fileEntry = new FileEntry("NoName", fs); From 31ca7faa8902563846cb4b644d3f9521cf4d1be4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 01:30:54 +0000 Subject: [PATCH 08/28] Fix indentation in ExtractArchiveFromStream method Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- .../ExtractorTests/ExpectedNumFilesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs index dfa5a28..8ed1281 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs @@ -240,7 +240,7 @@ public async Task ExtractArchiveFromStreamAsync(string fileName, int expectedNum [DynamicData(nameof(ArchiveData))] public void ExtractArchiveFromStream(string fileName, int expectedNumFiles) { - var extractor = new Extractor(); + var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); var results = extractor.Extract(path, stream, GetExtractorOptions()); From 8990a1e26f6547d2be1141bc707f5606cdf50f8a Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 17:55:08 -0800 Subject: [PATCH 09/28] Migrate tests from MSTest to xUnit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace MSTest with xUnit (v2.9.3) across both test projects to improve Azure DevOps pipeline compatibility for test result reporting and code coverage collection. Changes: - Swap MSTest/Microsoft.Testing.Extensions packages for xunit, xunit.runner.visualstudio, coverlet.collector, and Microsoft.NET.Test.Sdk - Remove MSTest runner config from Directory.Build.props and OutputType=Exe from test projects - Convert all test attributes: [TestClass] removed, [TestMethod] → [Fact], [DataRow] → [InlineData], [DynamicData] → [MemberData], [DataTestMethod] → [Theory] - Convert assertions: Assert.AreEqual → Assert.Equal, Assert.ThrowsExactly → Assert.Throws, Assert.IsTrue → Assert.True, etc. - Replace TestContext.CancellationTokenSource.Token usage with parameterless ToListAsync() - Convert BaseExtractorTestClass from ClassInitialize/ClassCleanup to IDisposable pattern - Update pipeline YAML to use --collect:"XPlat Code Coverage" instead of MSTest's -- --coverage flag --- Directory.Build.props | 7 +- Pipelines/recursive-extractor-pr.yml | 4 +- Pipelines/recursive-extractor-release.yml | 4 +- .../CliTests/CliTests.cs | 95 ++++--- .../RecursiveExtractor.Cli.Tests.csproj | 8 +- RecursiveExtractor.Cli.Tests/Usings.cs | 2 +- RecursiveExtractor.Tests/AssemblyInfo.cs | 4 +- .../ExtractorTests/BaseExtractorTestClass.cs | 23 +- .../ExtractorTests/CustomExtractorTests.cs | 69 +++-- .../ExtractorTests/DisposeBehaviorTests.cs | 147 ++++++----- .../ExtractorTests/EncryptedArchiveTests.cs | 75 +++--- .../ExtractorTests/ExpectedNumFilesTests.cs | 79 +++--- .../ExtractorTests/FilterTests.cs | 237 +++++++++--------- .../ExtractorTests/MiniMagicTests.cs | 47 ++-- .../ExtractorTests/MiscTests.cs | 67 +++-- .../ExtractorTests/TestQuinesAndSlip.cs | 31 ++- .../ExtractorTests/TimeOutTests.cs | 143 ++++++----- .../RecursiveExtractor.Tests.csproj | 8 +- RecursiveExtractor.Tests/SanitizePathTests.cs | 19 +- RecursiveExtractor.Tests/TestPathHelpers.cs | 2 +- 20 files changed, 524 insertions(+), 547 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 907effa..e685da3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,10 +7,5 @@ - - - true - true - Recommended - + \ No newline at end of file diff --git a/Pipelines/recursive-extractor-pr.yml b/Pipelines/recursive-extractor-pr.yml index be9ac8a..60085fa 100644 --- a/Pipelines/recursive-extractor-pr.yml +++ b/Pipelines/recursive-extractor-pr.yml @@ -42,7 +42,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory TestResults -- --coverage' + dotnetTestArgs: '--logger trx --results-directory TestResults --collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory TestResults -- --coverage' + dotnetTestArgs: '--logger trx --results-directory TestResults --collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/Pipelines/recursive-extractor-release.yml b/Pipelines/recursive-extractor-release.yml index ab1dd80..a278186 100644 --- a/Pipelines/recursive-extractor-release.yml +++ b/Pipelines/recursive-extractor-release.yml @@ -43,7 +43,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory TestResults -- --coverage' + dotnetTestArgs: '--logger trx --results-directory TestResults --collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory TestResults -- --coverage' + dotnetTestArgs: '--logger trx --results-directory TestResults --collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs b/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs index d430bca..acde6ff 100644 --- a/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs +++ b/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs @@ -3,35 +3,34 @@ using Microsoft.CST.RecursiveExtractor; using Microsoft.CST.RecursiveExtractor.Cli; using Microsoft.CST.RecursiveExtractor.Tests; -using Microsoft.VisualStudio.TestTools.UnitTesting; using RecursiveExtractor.Tests.ExtractorTests; using System; using System.IO; using System.Linq; using System.Threading; +using Xunit; namespace RecursiveExtractor.Tests.CliTests { - [TestClass] public class CliTests : BaseExtractorTestClass { - [DataTestMethod] - [DataRow("TestData.zip", 5)] - [DataRow("TestData.7z")] - [DataRow("TestData.tar", 6)] - [DataRow("TestData.rar")] - [DataRow("TestData.rar4")] - [DataRow("TestData.tar.bz2", 6)] - [DataRow("TestData.tar.gz", 6)] - [DataRow("TestData.tar.xz")] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] - [DataRow("TestData.a")] - [DataRow("TestData.bsd.ar")] - [DataRow("TestData.iso")] - [DataRow("TestData.vhdx")] - [DataRow("TestData.wim")] - [DataRow("EmptyFile.txt", 1)] - [DataRow("TestDataArchivesNested.Zip", 54)] + [Theory] + [InlineData("TestData.zip", 5)] + [InlineData("TestData.7z")] + [InlineData("TestData.tar", 6)] + [InlineData("TestData.rar")] + [InlineData("TestData.rar4")] + [InlineData("TestData.tar.bz2", 6)] + [InlineData("TestData.tar.gz", 6)] + [InlineData("TestData.tar.xz")] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] + [InlineData("TestData.a")] + [InlineData("TestData.bsd.ar")] + [InlineData("TestData.iso")] + [InlineData("TestData.vhdx")] + [InlineData("TestData.wim")] + [InlineData("EmptyFile.txt", 1)] + [InlineData("TestDataArchivesNested.Zip", 54)] public void ExtractArchiveParallel(string fileName, int expectedNumFiles = 3) { ExtractArchive(fileName, expectedNumFiles, false); @@ -48,33 +47,33 @@ internal void ExtractArchive(string fileName, int expectedNumFiles, bool singleT { files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); } - Assert.AreEqual(expectedNumFiles, files.Length); + Assert.Equal(expectedNumFiles, files.Length); } - [DataTestMethod] - [DataRow("TestData.zip", 5)] - [DataRow("TestData.7z")] - [DataRow("TestData.tar", 6)] - [DataRow("TestData.rar")] - [DataRow("TestData.rar4")] - [DataRow("TestData.tar.bz2", 6)] - [DataRow("TestData.tar.gz", 6)] - [DataRow("TestData.tar.xz")] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] - [DataRow("TestData.a")] - [DataRow("TestData.bsd.ar")] - [DataRow("TestData.iso")] - [DataRow("TestData.vhdx")] - [DataRow("TestData.wim")] - [DataRow("EmptyFile.txt", 1)] - [DataRow("TestDataArchivesNested.Zip", 54)] + [Theory] + [InlineData("TestData.zip", 5)] + [InlineData("TestData.7z")] + [InlineData("TestData.tar", 6)] + [InlineData("TestData.rar")] + [InlineData("TestData.rar4")] + [InlineData("TestData.tar.bz2", 6)] + [InlineData("TestData.tar.gz", 6)] + [InlineData("TestData.tar.xz")] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] + [InlineData("TestData.a")] + [InlineData("TestData.bsd.ar")] + [InlineData("TestData.iso")] + [InlineData("TestData.vhdx")] + [InlineData("TestData.wim")] + [InlineData("EmptyFile.txt", 1)] + [InlineData("TestDataArchivesNested.Zip", 54)] public void ExtractArchiveSingleThread(string fileName, int expectedNumFiles = 3) { ExtractArchive(fileName, expectedNumFiles, true); } - [DataTestMethod] - [DataRow("TestDataForFilters.7z")] + [Theory] + [InlineData("TestDataForFilters.7z")] public void ExtractArchiveWithAllowFilters(string fileName, int expectedNumFiles = 1) { var directory = TestPathHelpers.GetFreshTestDirectory(); @@ -96,11 +95,11 @@ public void ExtractArchiveWithAllowFilters(string fileName, int expectedNumFiles { files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); } - Assert.AreEqual(expectedNumFiles, files.Length); + Assert.Equal(expectedNumFiles, files.Length); } - [DataTestMethod] - [DataRow("TestDataForFilters.7z")] + [Theory] + [InlineData("TestDataForFilters.7z")] public void ExtractArchiveWithDenyFilters(string fileName, int expectedNumFiles = 2) { var directory = TestPathHelpers.GetFreshTestDirectory(); @@ -122,13 +121,13 @@ public void ExtractArchiveWithDenyFilters(string fileName, int expectedNumFiles { files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); } - Assert.AreEqual(expectedNumFiles, files.Length); + Assert.Equal(expectedNumFiles, files.Length); } - [DataTestMethod] - [DataRow("TestDataEncrypted.7z")] - [DataRow("TestDataEncryptedAes.zip")] - [DataRow("TestDataEncrypted.rar4")] + [Theory] + [InlineData("TestDataEncrypted.7z")] + [InlineData("TestDataEncryptedAes.zip")] + [InlineData("TestDataEncrypted.rar4")] public void ExtractEncryptedArchive(string fileName, int expectedNumFiles = 3) { var directory = TestPathHelpers.GetFreshTestDirectory(); @@ -136,7 +135,7 @@ public void ExtractEncryptedArchive(string fileName, int expectedNumFiles = 3) var passwords = EncryptedArchiveTests.TestArchivePasswords.Values.SelectMany(x => x); RecursiveExtractorClient.ExtractCommand(new ExtractCommandOptions() { Input = path, Output = directory, Verbose = true, Passwords = passwords }); string[] files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); - Assert.AreEqual(expectedNumFiles, files.Length); + Assert.Equal(expectedNumFiles, files.Length); } protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); diff --git a/RecursiveExtractor.Cli.Tests/RecursiveExtractor.Cli.Tests.csproj b/RecursiveExtractor.Cli.Tests/RecursiveExtractor.Cli.Tests.csproj index f9792e3..41d07b7 100644 --- a/RecursiveExtractor.Cli.Tests/RecursiveExtractor.Cli.Tests.csproj +++ b/RecursiveExtractor.Cli.Tests/RecursiveExtractor.Cli.Tests.csproj @@ -6,13 +6,13 @@ enable false - Exe - - - + + + + diff --git a/RecursiveExtractor.Cli.Tests/Usings.cs b/RecursiveExtractor.Cli.Tests/Usings.cs index bed1fa2..2af8a54 100644 --- a/RecursiveExtractor.Cli.Tests/Usings.cs +++ b/RecursiveExtractor.Cli.Tests/Usings.cs @@ -1,2 +1,2 @@ -global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Xunit; diff --git a/RecursiveExtractor.Tests/AssemblyInfo.cs b/RecursiveExtractor.Tests/AssemblyInfo.cs index 38707ae..b928bc0 100644 --- a/RecursiveExtractor.Tests/AssemblyInfo.cs +++ b/RecursiveExtractor.Tests/AssemblyInfo.cs @@ -1,3 +1,3 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; -[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] +[assembly: CollectionBehavior(DisableTestParallelization = false)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs index 7c301b5..b6cdd50 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs @@ -1,23 +1,16 @@ -using Microsoft.CST.RecursiveExtractor.Tests; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.CST.RecursiveExtractor.Tests; using NLog; using NLog.Config; using NLog.Targets; namespace RecursiveExtractor.Tests.ExtractorTests; -public class BaseExtractorTestClass +public class BaseExtractorTestClass : IDisposable { - public TestContext TestContext { get; set; } = null!; - - [ClassCleanup] - public static void ClassCleanup() - { - TestPathHelpers.DeleteTestDirectory(); - } + protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); - [ClassInitialize] - public static void ClassInitialize(TestContext context) + static BaseExtractorTestClass() { var config = new LoggingConfiguration(); var consoleTarget = new ConsoleTarget @@ -29,5 +22,9 @@ public static void ClassInitialize(TestContext context) LogManager.Configuration = config; } - protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + public void Dispose() + { + TestPathHelpers.DeleteTestDirectory(); + } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs b/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs index 281e1c9..f107709 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/CustomExtractorTests.cs @@ -1,19 +1,16 @@ using Microsoft.CST.RecursiveExtractor; using Microsoft.CST.RecursiveExtractor.Extractors; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -[TestClass] public class CustomExtractorTests { - public TestContext TestContext { get; set; } = null!; - /// /// A simple test custom extractor that extracts files with a specific magic number /// For testing purposes, it recognizes files starting with "CUSTOM1" @@ -123,43 +120,43 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra } } - [TestMethod] + [Fact] public void Constructor_WithCustomExtractors_RegistersExtractors() { var customExtractor = new TestCustomExtractor(null!); var extractor = new Extractor(new[] { customExtractor }); - Assert.HasCount(1, extractor.CustomExtractors); + Assert.Single(extractor.CustomExtractors); } - [TestMethod] + [Fact] public void Constructor_WithMultipleCustomExtractors_RegistersAll() { var customExtractor1 = new TestCustomExtractor(null!); var customExtractor2 = new SecondTestCustomExtractor(null!); var extractor = new Extractor(new ICustomAsyncExtractor[] { customExtractor1, customExtractor2 }); - Assert.HasCount(2, extractor.CustomExtractors); + Assert.Equal(2, extractor.CustomExtractors.Count()); } - [TestMethod] + [Fact] public void Constructor_WithNullInCollection_IgnoresNull() { var customExtractor = new TestCustomExtractor(null!); var extractor = new Extractor(new ICustomAsyncExtractor[] { customExtractor, null! }); - Assert.HasCount(1, extractor.CustomExtractors); + Assert.Single(extractor.CustomExtractors); } - [TestMethod] + [Fact] public void Constructor_WithNullCollection_CreatesEmptyExtractor() { var extractor = new Extractor((IEnumerable)null!); - Assert.IsEmpty(extractor.CustomExtractors); + Assert.Empty(extractor.CustomExtractors); } - [TestMethod] + [Fact] public void Extract_WithMatchingCustomExtractor_UsesCustomExtractor() { var customExtractor = new TestCustomExtractor(null!); @@ -169,17 +166,17 @@ public void Extract_WithMatchingCustomExtractor_UsesCustomExtractor() var testData = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 This is test data"); var results = extractor.Extract("test.custom", testData).ToList(); - Assert.HasCount(1, results); - Assert.AreEqual("extracted_from_custom.txt", results[0].Name); + Assert.Single(results); + Assert.Equal("extracted_from_custom.txt", results[0].Name); // Read the content to verify it was processed by our custom extractor using var reader = new StreamReader(results[0].Content); results[0].Content.Position = 0; var content = reader.ReadToEnd(); - Assert.AreEqual("Extracted by TestCustomExtractor", content); + Assert.Equal("Extracted by TestCustomExtractor", content); } - [TestMethod] + [Fact] public async Task ExtractAsync_WithMatchingCustomExtractor_UsesCustomExtractor() { var customExtractor = new TestCustomExtractor(null!); @@ -187,19 +184,19 @@ public async Task ExtractAsync_WithMatchingCustomExtractor_UsesCustomExtractor() // Create a test file with the custom magic bytes var testData = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 This is test data"); - var results = await extractor.ExtractAsync("test.custom", testData).ToListAsync(TestContext.CancellationTokenSource.Token); + var results = await extractor.ExtractAsync("test.custom", testData).ToListAsync(); - Assert.HasCount(1, results); - Assert.AreEqual("extracted_from_custom.txt", results[0].Name); + Assert.Single(results); + Assert.Equal("extracted_from_custom.txt", results[0].Name); // Read the content to verify it was processed by our custom extractor using var reader = new StreamReader(results[0].Content); results[0].Content.Position = 0; var content = reader.ReadToEnd(); - Assert.AreEqual("Extracted by TestCustomExtractor", content); + Assert.Equal("Extracted by TestCustomExtractor", content); } - [TestMethod] + [Fact] public void Extract_WithoutMatchingCustomExtractor_ReturnsOriginalFile() { var customExtractor = new TestCustomExtractor(null!); @@ -210,17 +207,17 @@ public void Extract_WithoutMatchingCustomExtractor_ReturnsOriginalFile() var results = extractor.Extract("test.txt", testData).ToList(); // Should return the original file since no custom extractor matched - Assert.HasCount(1, results); - Assert.AreEqual("test.txt", results[0].Name); + Assert.Single(results); + Assert.Equal("test.txt", results[0].Name); // Verify it's the original content using var reader = new StreamReader(results[0].Content); results[0].Content.Position = 0; var content = reader.ReadToEnd(); - Assert.AreEqual("NOTCUSTOM This is test data", content); + Assert.Equal("NOTCUSTOM This is test data", content); } - [TestMethod] + [Fact] public void Extract_MultipleCustomExtractors_UsesCorrectOne() { var extractor = new Extractor(new ICustomAsyncExtractor[] @@ -232,17 +229,17 @@ public void Extract_MultipleCustomExtractors_UsesCorrectOne() // Test with first custom format var testData1 = System.Text.Encoding.ASCII.GetBytes("CUSTOM1 data"); var results1 = extractor.Extract("test1.custom", testData1).ToList(); - Assert.HasCount(1, results1); - Assert.AreEqual("extracted_from_custom.txt", results1[0].Name); + Assert.Single(results1); + Assert.Equal("extracted_from_custom.txt", results1[0].Name); // Test with second custom format var testData2 = System.Text.Encoding.ASCII.GetBytes("CUSTOM2 data"); var results2 = extractor.Extract("test2.custom", testData2).ToList(); - Assert.HasCount(1, results2); - Assert.AreEqual("extracted_from_second_custom.txt", results2[0].Name); + Assert.Single(results2); + Assert.Equal("extracted_from_second_custom.txt", results2[0].Name); } - [TestMethod] + [Fact] public void Extract_NoCustomExtractors_ReturnsOriginalFile() { var extractor = new Extractor(); @@ -252,11 +249,11 @@ public void Extract_NoCustomExtractors_ReturnsOriginalFile() var results = extractor.Extract("test.custom", testData).ToList(); // Should return the original file since no custom extractor is registered - Assert.HasCount(1, results); - Assert.AreEqual("test.custom", results[0].Name); + Assert.Single(results); + Assert.Equal("test.custom", results[0].Name); } - [TestMethod] + [Fact] public void Extract_CustomExtractorForKnownFormat_UsesBuiltInExtractor() { var customExtractor = new TestCustomExtractor(null!); @@ -269,8 +266,8 @@ public void Extract_CustomExtractorForKnownFormat_UsesBuiltInExtractor() var results = extractor.Extract(path).ToList(); // Should extract the ZIP normally, not use the custom extractor - Assert.IsGreaterThan(results.Count, 0); - Assert.IsTrue(results.Any(r => r.Name.Contains("EmptyFile"))); + Assert.True(results.Count > 0); + Assert.Contains(results, r => r.Name.Contains("EmptyFile")); } } } diff --git a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs index 08b9b91..eb2dcad 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs @@ -1,48 +1,47 @@ using Microsoft.CST.RecursiveExtractor; using Microsoft.CST.RecursiveExtractor.Tests; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -[TestClass] public class DisposeBehaviorTests : BaseExtractorTestClass { - [TestMethod] - [DataRow("TestData.7z", 3, false)] - [DataRow("TestData.tar", 6, false)] - [DataRow("TestData.rar", 3, false)] - [DataRow("TestData.rar4", 3, false)] - [DataRow("TestData.tar.bz2", 6, false)] - [DataRow("TestData.tar.gz", 6, false)] - [DataRow("TestData.tar.xz", 3, false)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] - [DataRow("TestData.a", 3, false)] - [DataRow("TestData.bsd.ar", 3, false)] - [DataRow("TestData.iso", 3, false)] - [DataRow("TestData.vhdx", 3, false)] - [DataRow("TestData.wim", 3, false)] - [DataRow("EmptyFile.txt", 1, false)] - [DataRow("TestData.zip", 5, true)] - [DataRow("TestData.7z", 3, true)] - [DataRow("TestData.tar", 6, true)] - [DataRow("TestData.rar", 3, true)] - [DataRow("TestData.rar4", 3, true)] - [DataRow("TestData.tar.bz2", 6, true)] - [DataRow("TestData.tar.gz", 6, true)] - [DataRow("TestData.tar.xz", 3, true)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] - [DataRow("TestData.a", 3, true)] - [DataRow("TestData.bsd.ar", 3, true)] - [DataRow("TestData.iso", 3, true)] - [DataRow("TestData.vhdx", 3, true)] - [DataRow("TestData.wim", 3, true)] - [DataRow("EmptyFile.txt", 1, true)] - [DataRow("TestDataArchivesNested.Zip", 54, true)] - [DataRow("TestDataArchivesNested.Zip", 54, false)] + [Theory] + [InlineData("TestData.7z", 3, false)] + [InlineData("TestData.tar", 6, false)] + [InlineData("TestData.rar", 3, false)] + [InlineData("TestData.rar4", 3, false)] + [InlineData("TestData.tar.bz2", 6, false)] + [InlineData("TestData.tar.gz", 6, false)] + [InlineData("TestData.tar.xz", 3, false)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] + [InlineData("TestData.a", 3, false)] + [InlineData("TestData.bsd.ar", 3, false)] + [InlineData("TestData.iso", 3, false)] + [InlineData("TestData.vhdx", 3, false)] + [InlineData("TestData.wim", 3, false)] + [InlineData("EmptyFile.txt", 1, false)] + [InlineData("TestData.zip", 5, true)] + [InlineData("TestData.7z", 3, true)] + [InlineData("TestData.tar", 6, true)] + [InlineData("TestData.rar", 3, true)] + [InlineData("TestData.rar4", 3, true)] + [InlineData("TestData.tar.bz2", 6, true)] + [InlineData("TestData.tar.gz", 6, true)] + [InlineData("TestData.tar.xz", 3, true)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] + [InlineData("TestData.a", 3, true)] + [InlineData("TestData.bsd.ar", 3, true)] + [InlineData("TestData.iso", 3, true)] + [InlineData("TestData.vhdx", 3, true)] + [InlineData("TestData.wim", 3, true)] + [InlineData("EmptyFile.txt", 1, true)] + [InlineData("TestDataArchivesNested.Zip", 54, true)] + [InlineData("TestDataArchivesNested.Zip", 54, false)] public void ExtractArchiveAndDisposeWhileEnumerating(string fileName, int expectedNumFiles = 3, bool parallel = false) { @@ -58,45 +57,45 @@ public void ExtractArchiveAndDisposeWhileEnumerating(string fileName, int expect _ = theStream.ReadByte(); } - Assert.AreEqual(expectedNumFiles, disposedResults.Count); + Assert.Equal(expectedNumFiles, disposedResults.Count); foreach (var disposedResult in disposedResults) { - Assert.ThrowsExactly(() => disposedResult.Content.Position); + Assert.Throws(() => disposedResult.Content.Position); } } - [TestMethod] - [DataRow("TestData.7z", 3, false)] - [DataRow("TestData.tar", 6, false)] - [DataRow("TestData.rar", 3, false)] - [DataRow("TestData.rar4", 3, false)] - [DataRow("TestData.tar.bz2", 6, false)] - [DataRow("TestData.tar.gz", 6, false)] - [DataRow("TestData.tar.xz", 3, false)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] - [DataRow("TestData.a", 3, false)] - [DataRow("TestData.bsd.ar", 3, false)] - [DataRow("TestData.iso", 3, false)] - [DataRow("TestData.vhdx", 3, false)] - [DataRow("TestData.wim", 3, false)] - [DataRow("EmptyFile.txt", 1, false)] - [DataRow("TestData.zip", 5, true)] - [DataRow("TestData.7z", 3, true)] - [DataRow("TestData.tar", 6, true)] - [DataRow("TestData.rar", 3, true)] - [DataRow("TestData.rar4", 3, true)] - [DataRow("TestData.tar.bz2", 6, true)] - [DataRow("TestData.tar.gz", 6, true)] - [DataRow("TestData.tar.xz", 3, true)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] - [DataRow("TestData.a", 3, true)] - [DataRow("TestData.bsd.ar", 3, true)] - [DataRow("TestData.iso", 3, true)] - [DataRow("TestData.vhdx", 3, true)] - [DataRow("TestData.wim", 3, true)] - [DataRow("EmptyFile.txt", 1, true)] - [DataRow("TestDataArchivesNested.Zip", 54, true)] - [DataRow("TestDataArchivesNested.Zip", 54, false)] + [Theory] + [InlineData("TestData.7z", 3, false)] + [InlineData("TestData.tar", 6, false)] + [InlineData("TestData.rar", 3, false)] + [InlineData("TestData.rar4", 3, false)] + [InlineData("TestData.tar.bz2", 6, false)] + [InlineData("TestData.tar.gz", 6, false)] + [InlineData("TestData.tar.xz", 3, false)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] + [InlineData("TestData.a", 3, false)] + [InlineData("TestData.bsd.ar", 3, false)] + [InlineData("TestData.iso", 3, false)] + [InlineData("TestData.vhdx", 3, false)] + [InlineData("TestData.wim", 3, false)] + [InlineData("EmptyFile.txt", 1, false)] + [InlineData("TestData.zip", 5, true)] + [InlineData("TestData.7z", 3, true)] + [InlineData("TestData.tar", 6, true)] + [InlineData("TestData.rar", 3, true)] + [InlineData("TestData.rar4", 3, true)] + [InlineData("TestData.tar.bz2", 6, true)] + [InlineData("TestData.tar.gz", 6, true)] + [InlineData("TestData.tar.xz", 3, true)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] + [InlineData("TestData.a", 3, true)] + [InlineData("TestData.bsd.ar", 3, true)] + [InlineData("TestData.iso", 3, true)] + [InlineData("TestData.vhdx", 3, true)] + [InlineData("TestData.wim", 3, true)] + [InlineData("EmptyFile.txt", 1, true)] + [InlineData("TestDataArchivesNested.Zip", 54, true)] + [InlineData("TestDataArchivesNested.Zip", 54, false)] public async Task ExtractArchiveAndDisposeWhileEnumeratingAsync(string fileName, int expectedNumFiles = 3, bool parallel = false) { @@ -112,15 +111,15 @@ public async Task ExtractArchiveAndDisposeWhileEnumeratingAsync(string fileName, _ = theStream.ReadByte(); } - Assert.AreEqual(expectedNumFiles, disposedResults.Count); + Assert.Equal(expectedNumFiles, disposedResults.Count); foreach (var disposedResult in disposedResults) { - Assert.ThrowsExactly(() => disposedResult.Content.Position); + Assert.Throws(() => disposedResult.Content.Position); } } - [TestMethod] - [DataRow("TestData.zip")] + [Theory] + [InlineData("TestData.zip")] public void EnsureDisposedWithExtractToDirectory(string fileName) { var directory = TestPathHelpers.GetFreshTestDirectory(); @@ -142,8 +141,8 @@ public void EnsureDisposedWithExtractToDirectory(string fileName) } } - [TestMethod] - [DataRow("TestData.zip")] + [Theory] + [InlineData("TestData.zip")] public async Task EnsureDisposedWithExtractToDirectoryAsync(string fileName) { var directory = TestPathHelpers.GetFreshTestDirectory(); diff --git a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs index 05b7d9e..5b960d9 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs @@ -1,37 +1,36 @@ using Microsoft.CST.RecursiveExtractor; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -[TestClass] public class EncryptedArchiveTests : BaseExtractorTestClass { - [TestMethod] - [DataRow("TestDataEncryptedZipCrypto.zip")] - [DataRow("TestDataEncryptedAes.zip")] - [DataRow("TestDataEncrypted.7z")] - [DataRow("TestDataEncrypted.rar4")] - [DataRow("TestDataEncrypted.rar")] + [Theory] + [InlineData("TestDataEncryptedZipCrypto.zip")] + [InlineData("TestDataEncryptedAes.zip")] + [InlineData("TestDataEncrypted.7z")] + [InlineData("TestDataEncrypted.rar4")] + [InlineData("TestDataEncrypted.rar")] public void FileTypeSetCorrectlyForEncryptedArchives(string fileName, int expectedNumFiles = 1) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, new ExtractorOptions() { Parallel = false }).ToList(); - Assert.AreEqual(expectedNumFiles, results.Count()); - Assert.AreEqual(FileEntryStatus.EncryptedArchive, results.First().EntryStatus); + Assert.Equal(expectedNumFiles, results.Count()); + Assert.Equal(FileEntryStatus.EncryptedArchive, results.First().EntryStatus); } - [TestMethod] - [DataRow("TestDataEncryptedZipCrypto.zip")] - [DataRow("TestDataEncryptedAes.zip")] - [DataRow("TestDataEncrypted.7z")] - [DataRow("TestDataEncrypted.rar4")] - [DataRow("TestDataEncrypted.rar")] + [Theory] + [InlineData("TestDataEncryptedZipCrypto.zip")] + [InlineData("TestDataEncryptedAes.zip")] + [InlineData("TestDataEncrypted.7z")] + [InlineData("TestDataEncrypted.rar4")] + [InlineData("TestDataEncrypted.rar")] public async Task FileTypeSetCorrectlyForEncryptedArchivesAsync(string fileName, int expectedNumFiles = 1) { var extractor = new Extractor(); @@ -42,36 +41,36 @@ public async Task FileTypeSetCorrectlyForEncryptedArchivesAsync(string fileName, results.Add(entry); } - Assert.AreEqual(expectedNumFiles, results.Count); - Assert.AreEqual(FileEntryStatus.EncryptedArchive, results.First().EntryStatus); + Assert.Equal(expectedNumFiles, results.Count); + Assert.Equal(FileEntryStatus.EncryptedArchive, results.First().EntryStatus); } - [TestMethod] - [DataRow("TestDataEncryptedZipCrypto.zip")] - [DataRow("TestDataEncryptedAes.zip")] - [DataRow("TestDataEncrypted.7z")] - [DataRow("TestDataEncrypted.rar4")] - [DataRow("EncryptedWithPlainNames.7z")] - [DataRow("EncryptedWithPlainNames.rar4")] - //[DataRow("TestDataEncrypted.rar")] // RAR5 is not yet supported by SharpCompress: https://github.com/adamhathcock/sharpcompress/issues/517 + [Theory] + [InlineData("TestDataEncryptedZipCrypto.zip")] + [InlineData("TestDataEncryptedAes.zip")] + [InlineData("TestDataEncrypted.7z")] + [InlineData("TestDataEncrypted.rar4")] + [InlineData("EncryptedWithPlainNames.7z")] + [InlineData("EncryptedWithPlainNames.rar4")] + //[InlineData("TestDataEncrypted.rar")] // RAR5 is not yet supported by SharpCompress: https://github.com/adamhathcock/sharpcompress/issues/517 public void ExtractEncryptedArchive(string fileName, int expectedNumFiles = 3) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, new ExtractorOptions() { Passwords = TestArchivePasswords, ExtractSelfOnFail = false }) .ToList(); // Make this a list so it fully populates - Assert.AreEqual(expectedNumFiles, results.Count); - Assert.AreEqual(0, results.Count(x => x.EntryStatus == FileEntryStatus.EncryptedArchive || x.EntryStatus == FileEntryStatus.FailedArchive)); + Assert.Equal(expectedNumFiles, results.Count); + Assert.Equal(0, results.Count(x => x.EntryStatus == FileEntryStatus.EncryptedArchive || x.EntryStatus == FileEntryStatus.FailedArchive)); } - [TestMethod] - [DataRow("TestDataEncryptedZipCrypto.zip")] - [DataRow("TestDataEncryptedAes.zip")] - [DataRow("TestDataEncrypted.7z")] - [DataRow("TestDataEncrypted.rar4")] - [DataRow("EncryptedWithPlainNames.7z")] - [DataRow("EncryptedWithPlainNames.rar4")] - //[DataRow("TestDataEncrypted.rar")] // RAR5 is not yet supported by SharpCompress: https://github.com/adamhathcock/sharpcompress/issues/517 + [Theory] + [InlineData("TestDataEncryptedZipCrypto.zip")] + [InlineData("TestDataEncryptedAes.zip")] + [InlineData("TestDataEncrypted.7z")] + [InlineData("TestDataEncrypted.rar4")] + [InlineData("EncryptedWithPlainNames.7z")] + [InlineData("EncryptedWithPlainNames.rar4")] + //[InlineData("TestDataEncrypted.rar")] // RAR5 is not yet supported by SharpCompress: https://github.com/adamhathcock/sharpcompress/issues/517 public async Task ExtractEncryptedArchiveAsync(string fileName, int expectedNumFiles = 3) { var extractor = new Extractor(); @@ -88,8 +87,8 @@ public async Task ExtractEncryptedArchiveAsync(string fileName, int expectedNumF } } - Assert.AreEqual(expectedNumFiles, numEntries); - Assert.AreEqual(0, numEntriesEncrypted); + Assert.Equal(expectedNumFiles, numEntries); + Assert.Equal(0, numEntriesEncrypted); } diff --git a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs index dfa5a28..6a9358d 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs @@ -2,16 +2,15 @@ using Microsoft.CST.RecursiveExtractor; using Microsoft.CST.RecursiveExtractor.Tests; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests { - [TestClass] public class ExpectedNumFilesTests : BaseExtractorTestClass { @@ -88,15 +87,15 @@ private ExtractorOptions GetExtractorOptions(bool parallel = false) private ExtractorOptions defaultExtractorTestOptions = new ExtractorOptions() { MaxExtractedBytesRatio = 300 }; private ExtractorOptions defaultExtractorTestOptionsParallel = new ExtractorOptions() { Parallel = true, MaxExtractedBytesRatio = 300 }; - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public void ExtractArchiveToDirectoryParallel(string fileName, int expectedNumFiles) { ExtractArchiveToDirectory(fileName, expectedNumFiles, true); } - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public void ExtractArchiveToDirectorySingleThread(string fileName, int expectedNumFiles) { ExtractArchiveToDirectory(fileName, expectedNumFiles, false); @@ -113,27 +112,27 @@ internal void ExtractArchiveToDirectory(string fileName, int expectedNumFiles, b { files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); } - Assert.AreEqual(expectedNumFiles, files.Length); + Assert.Equal(expectedNumFiles, files.Length); } - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public async Task ExtractArchiveToDirectoryAsync(string fileName, int expectedNumFiles) { var directory = TestPathHelpers.GetFreshTestDirectory(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var extractor = new Extractor(); - Assert.AreEqual(ExtractionStatusCode.Ok, await extractor.ExtractToDirectoryAsync(directory, path, GetExtractorOptions()).ConfigureAwait(false)); + Assert.Equal(ExtractionStatusCode.Ok, await extractor.ExtractToDirectoryAsync(directory, path, GetExtractorOptions())); var files = Array.Empty(); if (Directory.Exists(directory)) { files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); } - Assert.AreEqual(expectedNumFiles, files.Length); + Assert.Equal(expectedNumFiles, files.Length); } - [TestMethod] - [DynamicData(nameof(NoRecursionData))] + [Theory] + [MemberData(nameof(NoRecursionData))] public async Task ExtractArchiveAsyncNoRecursion(string fileName, int expectedNumFiles) { var extractor = new Extractor(); @@ -145,11 +144,11 @@ public async Task ExtractArchiveAsyncNoRecursion(string fileName, int expectedNu { numResults++; } - Assert.AreEqual(expectedNumFiles, numResults); + Assert.Equal(expectedNumFiles, numResults); } - [TestMethod] - [DynamicData(nameof(NoRecursionData))] + [Theory] + [MemberData(nameof(NoRecursionData))] public void ExtractArchiveParallelNoRecursion(string fileName, int expectedNumFiles) { var extractor = new Extractor(); @@ -157,11 +156,11 @@ public void ExtractArchiveParallelNoRecursion(string fileName, int expectedNumFi var opts = GetExtractorOptions(true); opts.Recurse = false; var results = extractor.Extract(path, opts); - Assert.AreEqual(expectedNumFiles, results.Count(entry => entry.EntryStatus == FileEntryStatus.Default)); + Assert.Equal(expectedNumFiles, results.Count(entry => entry.EntryStatus == FileEntryStatus.Default)); } - [TestMethod] - [DynamicData(nameof(NoRecursionData))] + [Theory] + [MemberData(nameof(NoRecursionData))] public void ExtractArchiveNoRecursion(string fileName, int expectedNumFiles) { var extractor = new Extractor(); @@ -169,11 +168,11 @@ public void ExtractArchiveNoRecursion(string fileName, int expectedNumFiles) var opts = GetExtractorOptions(); opts.Recurse = false; var results = extractor.Extract(path, opts); - Assert.AreEqual(expectedNumFiles, results.Count(entry => entry.EntryStatus == FileEntryStatus.Default)); + Assert.Equal(expectedNumFiles, results.Count(entry => entry.EntryStatus == FileEntryStatus.Default)); } - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public void ExtractArchive(string fileName, int expectedNumFiles) { var extractor = new Extractor(); @@ -181,13 +180,13 @@ public void ExtractArchive(string fileName, int expectedNumFiles) var results = extractor.Extract(path, GetExtractorOptions()).ToList(); foreach (var result in results) { - Assert.AreNotEqual(FileEntryStatus.FailedArchive, result.EntryStatus); + Assert.NotEqual(FileEntryStatus.FailedArchive, result.EntryStatus); } - Assert.AreEqual(expectedNumFiles, results.Count); + Assert.Equal(expectedNumFiles, results.Count); } - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public void ExtractArchiveParallel(string fileName, int expectedNumFiles) { var extractor = new Extractor(); @@ -195,11 +194,11 @@ public void ExtractArchiveParallel(string fileName, int expectedNumFiles) var results = extractor.Extract(path, GetExtractorOptions(true)).ToList(); var names = results.Select(x => x.FullPath); var stringOfNames = string.Join("\n", names); - Assert.AreEqual(expectedNumFiles, results.Count()); + Assert.Equal(expectedNumFiles, results.Count()); } - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public async Task ExtractArchiveAsync(string fileName, int expectedNumFiles) { var extractor = new Extractor(); @@ -215,12 +214,12 @@ public async Task ExtractArchiveAsync(string fileName, int expectedNumFiles) files++; } } - Assert.AreEqual(expectedNumFiles, numFound); - Assert.AreEqual(expectedNumFiles, files); + Assert.Equal(expectedNumFiles, numFound); + Assert.Equal(expectedNumFiles, files); } - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public async Task ExtractArchiveFromStreamAsync(string fileName, int expectedNumFiles) { var extractor = new Extractor(); @@ -232,24 +231,24 @@ public async Task ExtractArchiveFromStreamAsync(string fileName, int expectedNum { numFiles++; } - Assert.AreEqual(expectedNumFiles, numFiles); + Assert.Equal(expectedNumFiles, numFiles); stream.Close(); } - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public void ExtractArchiveFromStream(string fileName, int expectedNumFiles) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); var results = extractor.Extract(path, stream, GetExtractorOptions()); - Assert.AreEqual(expectedNumFiles, results.Count()); + Assert.Equal(expectedNumFiles, results.Count()); stream.Close(); } - [TestMethod] - [DynamicData(nameof(ArchiveData))] + [Theory] + [MemberData(nameof(ArchiveData))] public void ExtractArchiveSmallBatchSize(string fileName, int expectedNumFiles) { var extractor = new Extractor(); @@ -257,7 +256,7 @@ public void ExtractArchiveSmallBatchSize(string fileName, int expectedNumFiles) var opts = GetExtractorOptions(true); opts.BatchSize = 2; var results = extractor.Extract(path, opts); - Assert.AreEqual(expectedNumFiles, results.Count(entry => entry.EntryStatus == FileEntryStatus.Default)); + Assert.Equal(expectedNumFiles, results.Count(entry => entry.EntryStatus == FileEntryStatus.Default)); } } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs index c5828a0..30e9d86 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs @@ -1,32 +1,31 @@ -using Microsoft.CST.RecursiveExtractor; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.CST.RecursiveExtractor; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -[TestClass] public class FilterTests : BaseExtractorTestClass { - [TestMethod] - [DataRow("TestData.zip")] - [DataRow("TestData.7z")] - [DataRow("TestData.tar")] - [DataRow("TestData.rar")] - [DataRow("TestData.rar4")] - [DataRow("TestData.tar.bz2")] - [DataRow("TestData.tar.gz")] - [DataRow("TestData.tar.xz")] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 0)] - [DataRow("TestData.a", 0)] - [DataRow("TestData.bsd.ar", 0)] - [DataRow("TestData.iso")] - [DataRow("TestData.vhdx")] - [DataRow("TestData.wim")] - [DataRow("EmptyFile.txt", 0)] - [DataRow("TestDataArchivesNested.Zip", 9)] + [Theory] + [InlineData("TestData.zip")] + [InlineData("TestData.7z")] + [InlineData("TestData.tar")] + [InlineData("TestData.rar")] + [InlineData("TestData.rar4")] + [InlineData("TestData.tar.bz2")] + [InlineData("TestData.tar.gz")] + [InlineData("TestData.tar.xz")] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 0)] + [InlineData("TestData.a", 0)] + [InlineData("TestData.bsd.ar", 0)] + [InlineData("TestData.iso")] + [InlineData("TestData.vhdx")] + [InlineData("TestData.wim")] + [InlineData("EmptyFile.txt", 0)] + [InlineData("TestDataArchivesNested.Zip", 9)] public async Task ExtractArchiveAsyncAllowFiltered(string fileName, int expectedNumFiles = 1) { var extractor = new Extractor(); @@ -39,129 +38,129 @@ public async Task ExtractArchiveAsyncAllowFiltered(string fileName, int expected numResults++; } - Assert.AreEqual(expectedNumFiles, numResults); + Assert.Equal(expectedNumFiles, numResults); } - [TestMethod] - [DataRow("TestData.zip")] - [DataRow("TestData.7z")] - [DataRow("TestData.tar")] - [DataRow("TestData.rar")] - [DataRow("TestData.rar4")] - [DataRow("TestData.tar.bz2")] - [DataRow("TestData.tar.gz")] - [DataRow("TestData.tar.xz")] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 0)] - [DataRow("TestData.a", 0)] - [DataRow("TestData.bsd.ar", 0)] - [DataRow("TestData.iso")] - [DataRow("TestData.vhdx")] - [DataRow("TestData.wim")] - [DataRow("EmptyFile.txt", 0)] - [DataRow("TestDataArchivesNested.Zip", 9)] + [Theory] + [InlineData("TestData.zip")] + [InlineData("TestData.7z")] + [InlineData("TestData.tar")] + [InlineData("TestData.rar")] + [InlineData("TestData.rar4")] + [InlineData("TestData.tar.bz2")] + [InlineData("TestData.tar.gz")] + [InlineData("TestData.tar.xz")] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 0)] + [InlineData("TestData.a", 0)] + [InlineData("TestData.bsd.ar", 0)] + [InlineData("TestData.iso")] + [InlineData("TestData.vhdx")] + [InlineData("TestData.wim")] + [InlineData("EmptyFile.txt", 0)] + [InlineData("TestDataArchivesNested.Zip", 9)] public void ExtractArchiveAllowFiltered(string fileName, int expectedNumFiles = 1) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, new ExtractorOptions() { AllowFilters = new string[] { "**/Bar/**", "**/TestData.tar" } }); - Assert.AreEqual(expectedNumFiles, results.Count()); + Assert.Equal(expectedNumFiles, results.Count()); } - [TestMethod] - [DataRow("TestData.zip")] - [DataRow("TestData.7z")] - [DataRow("TestData.tar")] - [DataRow("TestData.rar")] - [DataRow("TestData.rar4")] - [DataRow("TestData.tar.bz2")] - [DataRow("TestData.tar.gz")] - [DataRow("TestData.tar.xz")] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 0)] - [DataRow("TestData.a", 0)] - [DataRow("TestData.bsd.ar", 0)] - [DataRow("TestData.iso")] - [DataRow("TestData.vhdx")] - [DataRow("TestData.wim")] - [DataRow("EmptyFile.txt", 0)] - [DataRow("TestDataArchivesNested.Zip", 9)] + [Theory] + [InlineData("TestData.zip")] + [InlineData("TestData.7z")] + [InlineData("TestData.tar")] + [InlineData("TestData.rar")] + [InlineData("TestData.rar4")] + [InlineData("TestData.tar.bz2")] + [InlineData("TestData.tar.gz")] + [InlineData("TestData.tar.xz")] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 0)] + [InlineData("TestData.a", 0)] + [InlineData("TestData.bsd.ar", 0)] + [InlineData("TestData.iso")] + [InlineData("TestData.vhdx")] + [InlineData("TestData.wim")] + [InlineData("EmptyFile.txt", 0)] + [InlineData("TestDataArchivesNested.Zip", 9)] public void ExtractArchiveParallelAllowFiltered(string fileName, int expectedNumFiles = 1) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, new ExtractorOptions() { Parallel = true, AllowFilters = new string[] { "**/Bar/**", "**/TestData.tar" } }); - Assert.AreEqual(expectedNumFiles, results.Count()); + Assert.Equal(expectedNumFiles, results.Count()); } - [TestMethod] - [DataRow("TestData.zip", 4)] - [DataRow("TestData.7z")] - [DataRow("TestData.tar", 5)] - [DataRow("TestData.rar")] - [DataRow("TestData.rar4")] - [DataRow("TestData.tar.bz2", 5)] - [DataRow("TestData.tar.gz", 5)] - [DataRow("TestData.tar.xz")] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] - [DataRow("TestData.a", 3)] - [DataRow("TestData.bsd.ar", 3)] - [DataRow("TestData.iso")] - [DataRow("TestData.vhdx")] - [DataRow("TestData.wim")] - [DataRow("EmptyFile.txt", 1)] - [DataRow("TestDataArchivesNested.Zip", 45)] + [Theory] + [InlineData("TestData.zip", 4)] + [InlineData("TestData.7z")] + [InlineData("TestData.tar", 5)] + [InlineData("TestData.rar")] + [InlineData("TestData.rar4")] + [InlineData("TestData.tar.bz2", 5)] + [InlineData("TestData.tar.gz", 5)] + [InlineData("TestData.tar.xz")] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] + [InlineData("TestData.a", 3)] + [InlineData("TestData.bsd.ar", 3)] + [InlineData("TestData.iso")] + [InlineData("TestData.vhdx")] + [InlineData("TestData.wim")] + [InlineData("EmptyFile.txt", 1)] + [InlineData("TestDataArchivesNested.Zip", 45)] public void ExtractArchiveDenyFiltered(string fileName, int expectedNumFiles = 2) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, new ExtractorOptions() { DenyFilters = new string[] { "**/Bar/**" } }); - Assert.AreEqual(expectedNumFiles, results.Count()); + Assert.Equal(expectedNumFiles, results.Count()); } - [TestMethod] - [DataRow("TestData.zip", 4)] - [DataRow("TestData.7z")] - [DataRow("TestData.tar", 5)] - [DataRow("TestData.rar")] - [DataRow("TestData.rar4")] - [DataRow("TestData.tar.bz2", 5)] - [DataRow("TestData.tar.gz", 5)] - [DataRow("TestData.tar.xz")] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] - [DataRow("TestData.a", 3)] - [DataRow("TestData.bsd.ar", 3)] - [DataRow("TestData.iso")] - [DataRow("TestData.vhdx")] - [DataRow("TestData.wim")] - [DataRow("EmptyFile.txt", 1)] - [DataRow("TestDataArchivesNested.Zip", 45)] + [Theory] + [InlineData("TestData.zip", 4)] + [InlineData("TestData.7z")] + [InlineData("TestData.tar", 5)] + [InlineData("TestData.rar")] + [InlineData("TestData.rar4")] + [InlineData("TestData.tar.bz2", 5)] + [InlineData("TestData.tar.gz", 5)] + [InlineData("TestData.tar.xz")] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] + [InlineData("TestData.a", 3)] + [InlineData("TestData.bsd.ar", 3)] + [InlineData("TestData.iso")] + [InlineData("TestData.vhdx")] + [InlineData("TestData.wim")] + [InlineData("EmptyFile.txt", 1)] + [InlineData("TestDataArchivesNested.Zip", 45)] public void ExtractArchiveParallelDenyFiltered(string fileName, int expectedNumFiles = 2) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, new ExtractorOptions() { Parallel = true, DenyFilters = new string[] { "**/Bar/**" } }); - Assert.AreEqual(expectedNumFiles, results.Count()); + Assert.Equal(expectedNumFiles, results.Count()); } - [TestMethod] - [DataRow("TestData.zip", 4)] - [DataRow("TestData.7z")] - [DataRow("TestData.tar", 5)] - [DataRow("TestData.rar")] - [DataRow("TestData.rar4")] - [DataRow("TestData.tar.bz2", 5)] - [DataRow("TestData.tar.gz", 5)] - [DataRow("TestData.tar.xz")] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] - [DataRow("TestData.a", 3)] - [DataRow("TestData.bsd.ar", 3)] - [DataRow("TestData.iso")] - [DataRow("TestData.vhdx")] - [DataRow("TestData.wim")] - [DataRow("EmptyFile.txt", 1)] - [DataRow("TestDataArchivesNested.Zip", 45)] + [Theory] + [InlineData("TestData.zip", 4)] + [InlineData("TestData.7z")] + [InlineData("TestData.tar", 5)] + [InlineData("TestData.rar")] + [InlineData("TestData.rar4")] + [InlineData("TestData.tar.bz2", 5)] + [InlineData("TestData.tar.gz", 5)] + [InlineData("TestData.tar.xz")] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8)] + [InlineData("TestData.a", 3)] + [InlineData("TestData.bsd.ar", 3)] + [InlineData("TestData.iso")] + [InlineData("TestData.vhdx")] + [InlineData("TestData.wim")] + [InlineData("EmptyFile.txt", 1)] + [InlineData("TestDataArchivesNested.Zip", 45)] public async Task ExtractArchiveAsyncDenyFiltered(string fileName, int expectedNumFiles = 2) { var extractor = new Extractor(); @@ -174,19 +173,19 @@ public async Task ExtractArchiveAsyncDenyFiltered(string fileName, int expectedN numResults++; } - Assert.AreEqual(expectedNumFiles, numResults); + Assert.Equal(expectedNumFiles, numResults); } - [TestMethod] - [DataRow(ArchiveFileType.ZIP, new[] { ArchiveFileType.ZIP }, new ArchiveFileType[] { }, false)] - [DataRow(ArchiveFileType.ZIP, new[] { ArchiveFileType.TAR }, new ArchiveFileType[] { }, true)] - [DataRow(ArchiveFileType.ZIP, new ArchiveFileType[] { }, new[] { ArchiveFileType.ZIP }, true)] - [DataRow(ArchiveFileType.TAR, new ArchiveFileType[] { }, new[] { ArchiveFileType.ZIP }, false)] - [DataRow(ArchiveFileType.ZIP, new[] { ArchiveFileType.ZIP }, new[] { ArchiveFileType.ZIP }, false)] + [Theory] + [InlineData(ArchiveFileType.ZIP, new[] { ArchiveFileType.ZIP }, new ArchiveFileType[] { }, false)] + [InlineData(ArchiveFileType.ZIP, new[] { ArchiveFileType.TAR }, new ArchiveFileType[] { }, true)] + [InlineData(ArchiveFileType.ZIP, new ArchiveFileType[] { }, new[] { ArchiveFileType.ZIP }, true)] + [InlineData(ArchiveFileType.TAR, new ArchiveFileType[] { }, new[] { ArchiveFileType.ZIP }, false)] + [InlineData(ArchiveFileType.ZIP, new[] { ArchiveFileType.ZIP }, new[] { ArchiveFileType.ZIP }, false)] public void TestArchiveTypeFilters(ArchiveFileType typeToCheck, IEnumerable denyTypes, IEnumerable allowTypes, bool expected) { ExtractorOptions opts = new() { AllowTypes = allowTypes, DenyTypes = denyTypes }; - Assert.AreEqual(expected, opts.IsAcceptableType(typeToCheck)); + Assert.Equal(expected, opts.IsAcceptableType(typeToCheck)); } -} \ No newline at end of file +} diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs index 6253c7b..46a26d7 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs @@ -1,30 +1,29 @@ using Microsoft.CST.RecursiveExtractor; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -[TestClass] public class MiniMagicTests : BaseExtractorTestClass { - [TestMethod] - [DataRow("TestData.zip", ArchiveFileType.ZIP)] - [DataRow("TestData.7z", ArchiveFileType.P7ZIP)] - [DataRow("TestData.Tar", ArchiveFileType.TAR)] - [DataRow("TestData.rar", ArchiveFileType.RAR5)] - [DataRow("TestData.rar4", ArchiveFileType.RAR)] - [DataRow("TestData.tar.bz2", ArchiveFileType.BZIP2)] - [DataRow("TestData.tar.gz", ArchiveFileType.GZIP)] - [DataRow("TestData.tar.xz", ArchiveFileType.XZ)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", ArchiveFileType.DEB)] - [DataRow("TestData.a", ArchiveFileType.AR)] - [DataRow("TestData.iso", ArchiveFileType.ISO_9660)] - [DataRow("UdfTest.iso", ArchiveFileType.UDF)] - [DataRow("TestData.vhdx", ArchiveFileType.VHDX)] - [DataRow("TestData.wim", ArchiveFileType.WIM)] - [DataRow("Empty.vmdk", ArchiveFileType.VMDK)] - [DataRow("HfsSampleUDCO.dmg", ArchiveFileType.DMG)] - [DataRow("EmptyFile.txt", ArchiveFileType.UNKNOWN)] + [Theory] + [InlineData("TestData.zip", ArchiveFileType.ZIP)] + [InlineData("TestData.7z", ArchiveFileType.P7ZIP)] + [InlineData("TestData.Tar", ArchiveFileType.TAR)] + [InlineData("TestData.rar", ArchiveFileType.RAR5)] + [InlineData("TestData.rar4", ArchiveFileType.RAR)] + [InlineData("TestData.tar.bz2", ArchiveFileType.BZIP2)] + [InlineData("TestData.tar.gz", ArchiveFileType.GZIP)] + [InlineData("TestData.tar.xz", ArchiveFileType.XZ)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", ArchiveFileType.DEB)] + [InlineData("TestData.a", ArchiveFileType.AR)] + [InlineData("TestData.iso", ArchiveFileType.ISO_9660)] + [InlineData("UdfTest.iso", ArchiveFileType.UDF)] + [InlineData("TestData.vhdx", ArchiveFileType.VHDX)] + [InlineData("TestData.wim", ArchiveFileType.WIM)] + [InlineData("Empty.vmdk", ArchiveFileType.VMDK)] + [InlineData("HfsSampleUDCO.dmg", ArchiveFileType.DMG)] + [InlineData("EmptyFile.txt", ArchiveFileType.UNKNOWN)] public void TestMiniMagic(string fileName, ArchiveFileType expectedArchiveFileType) { var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); @@ -33,12 +32,12 @@ public void TestMiniMagic(string fileName, ArchiveFileType expectedArchiveFileTy var fileEntry = new FileEntry("NoName", fs); // We make sure the expected type matches and we have reset the stream - Assert.AreEqual(expectedArchiveFileType, fileEntry.ArchiveType); - Assert.AreEqual(0, fileEntry.Content.Position); + Assert.Equal(expectedArchiveFileType, fileEntry.ArchiveType); + Assert.Equal(0, fileEntry.Content.Position); // Should also work if the stream doesn't start at 0 fileEntry.Content.Position = 10; - Assert.AreEqual(expectedArchiveFileType, fileEntry.ArchiveType); - Assert.AreEqual(10, fileEntry.Content.Position); + Assert.Equal(expectedArchiveFileType, fileEntry.ArchiveType); + Assert.Equal(10, fileEntry.Content.Position); } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs index 5b9aa10..789f9bf 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs @@ -1,85 +1,82 @@ using Microsoft.CST.RecursiveExtractor; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -[TestClass] public class MiscTests { - public TestContext TestContext { get; set; } = null!; - - [TestMethod] - [DataRow("TestDataCorrupt.tar", false, 0, 1)] - [DataRow("TestDataCorrupt.tar", true, 1, 1)] - [DataRow("TestDataCorrupt.tar.zip", false, 0, 2)] - [DataRow("TestDataCorrupt.tar.zip", true, 0, 2)] + [Theory] + [InlineData("TestDataCorrupt.tar", false, 0, 1)] + [InlineData("TestDataCorrupt.tar", true, 1, 1)] + [InlineData("TestDataCorrupt.tar.zip", false, 0, 2)] + [InlineData("TestDataCorrupt.tar.zip", true, 0, 2)] public async Task ExtractCorruptArchiveAsync(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures, int expectedNumFiles) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = await extractor.ExtractAsync(path, - new ExtractorOptions() { RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(TestContext.CancellationTokenSource.Token); + new ExtractorOptions() { RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(); - Assert.AreEqual(expectedNumFiles, results.Count); + Assert.Equal(expectedNumFiles, results.Count); var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive); - Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); + Assert.Equal(expectedNumFailures, actualNumberOfFailedArchives); } - [TestMethod] - [DataRow("Lorem.txt", true, 1)] - [DataRow("Lorem.txt", false, 0)] + [Theory] + [InlineData("Lorem.txt", true, 1)] + [InlineData("Lorem.txt", false, 0)] public async Task ExtractFlatFileAsync(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestData", fileName); - var results = await extractor.ExtractAsync(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(TestContext.CancellationTokenSource.Token); + var results = await extractor.ExtractAsync(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync(); - Assert.HasCount(1, results); + Assert.Single(results); var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive); - Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); + Assert.Equal(expectedNumFailures, actualNumberOfFailedArchives); } - [TestMethod] - [DataRow("TestDataCorrupt.tar", false, 0, 1)] - [DataRow("TestDataCorrupt.tar", true, 1, 1)] - [DataRow("TestDataCorrupt.tar.zip", false, 0, 2)] - [DataRow("TestDataCorrupt.tar.zip", true, 0, 2)] - [DataRow("TestDataCorruptWim.zip", true, 0, 0)] + [Theory] + [InlineData("TestDataCorrupt.tar", false, 0, 1)] + [InlineData("TestDataCorrupt.tar", true, 1, 1)] + [InlineData("TestDataCorrupt.tar.zip", false, 0, 2)] + [InlineData("TestDataCorrupt.tar.zip", true, 0, 2)] + [InlineData("TestDataCorruptWim.zip", true, 0, 0)] public void ExtractCorruptArchive(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures, int expectedNumFiles) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToList(); - Assert.AreEqual(expectedNumFiles, results.Count); + Assert.Equal(expectedNumFiles, results.Count); var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive); - Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); + Assert.Equal(expectedNumFailures, actualNumberOfFailedArchives); } - [TestMethod] - [DataRow("Lorem.txt", true, 1)] - [DataRow("Lorem.txt", false, 0)] + [Theory] + [InlineData("Lorem.txt", true, 1)] + [InlineData("Lorem.txt", false, 0)] public void ExtractFlatFile(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestData", fileName); var results = extractor.Extract(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToList(); - Assert.HasCount(1, results); + Assert.Single(results); var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive); - Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives); + Assert.Equal(expectedNumFailures, actualNumberOfFailedArchives); } - [TestMethod] - [DataRow("EmptyFile.txt")] - [DataRow("TestData.zip", ".zip")] + [Theory] + [InlineData("EmptyFile.txt")] + [InlineData("TestData.zip", ".zip")] public void ExtractAsRaw(string fileName, string? RawExtension = null) { var extractor = new Extractor(); @@ -90,6 +87,6 @@ public void ExtractAsRaw(string fileName, string? RawExtension = null) var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var results = extractor.Extract(path, options); - Assert.HasCount(1, results); + Assert.Single(results); } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs index 252820b..d9416cf 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs @@ -1,15 +1,14 @@ using Microsoft.CST.RecursiveExtractor; -using Microsoft.VisualStudio.TestTools.UnitTesting; using SharpCompress.Archives.Tar; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -[TestClass] public class TestQuinesAndSlip : BaseExtractorTestClass { public static IEnumerable ZipSlipNames @@ -26,24 +25,24 @@ public static IEnumerable ZipSlipNames } } - [TestMethod] - [DynamicData(nameof(ZipSlipNames))] + [Theory] + [MemberData(nameof(ZipSlipNames))] public void TestZipSlip(string fileName) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "Bombs", fileName); var results = extractor.Extract(path, new ExtractorOptions()).ToList(); - Assert.IsTrue(results.All(x => !x.FullPath.Contains(".."))); + Assert.True(results.All(x => !x.FullPath.Contains(".."))); } - [TestMethod] - [DynamicData(nameof(ZipSlipNames))] + [Theory] + [MemberData(nameof(ZipSlipNames))] public async Task TestZipSlipAsync(string fileName) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "Bombs", fileName); - var results = await extractor.ExtractAsync(path, new ExtractorOptions()).ToListAsync(TestContext.CancellationTokenSource.Token); - Assert.IsTrue(results.All(x => !x.FullPath.Contains(".."))); + var results = await extractor.ExtractAsync(path, new ExtractorOptions()).ToListAsync(); + Assert.True(results.All(x => !x.FullPath.Contains(".."))); } public static IEnumerable QuineBombNames @@ -63,27 +62,27 @@ public static IEnumerable QuineBombNames } } - [TestMethod] - [DynamicData(nameof(QuineBombNames))] + [Theory] + [MemberData(nameof(QuineBombNames))] public void TestQuineBombs(string fileName) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "Bombs", fileName); - Assert.ThrowsExactly(() => + Assert.Throws(() => { _ = extractor.Extract(path, new ExtractorOptions() { MemoryStreamCutoff = 1024 * 1024 * 1024 }).ToList(); }); } - [TestMethod] - [DynamicData(nameof(QuineBombNames))] + [Theory] + [MemberData(nameof(QuineBombNames))] public async Task TestQuineBombsAsync(string fileName) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "Bombs", fileName); - await Assert.ThrowsExactlyAsync(async () => + await Assert.ThrowsAsync(async () => { - _ = await extractor.ExtractAsync(path, new ExtractorOptions() { MemoryStreamCutoff = 1024 * 1024 * 1024 }).ToListAsync(TestContext.CancellationTokenSource.Token); + _ = await extractor.ExtractAsync(path, new ExtractorOptions() { MemoryStreamCutoff = 1024 * 1024 * 1024 }).ToListAsync(); }); } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs index 16ec939..d0e5f19 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs @@ -1,51 +1,50 @@ using Microsoft.CST.RecursiveExtractor; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Threading.Tasks; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -[TestClass] public class TimeOutTests : BaseExtractorTestClass { - [TestMethod] - [DataRow("TestData.7z", 3, false)] - [DataRow("TestData.tar", 6, false)] - [DataRow("TestData.rar", 3, false)] - [DataRow("TestData.rar4", 3, false)] - [DataRow("TestData.tar.bz2", 6, false)] - [DataRow("TestData.tar.gz", 6, false)] - [DataRow("TestData.tar.xz", 3, false)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] - [DataRow("TestData.a", 3, false)] - [DataRow("TestData.bsd.ar", 3, false)] - [DataRow("TestData.iso", 3, false)] - [DataRow("TestData.vhdx", 3, false)] - [DataRow("TestData.wim", 3, false)] - [DataRow("EmptyFile.txt", 1, false)] - [DataRow("TestData.zip", 5, true)] - [DataRow("TestData.7z", 3, true)] - [DataRow("TestData.tar", 6, true)] - [DataRow("TestData.rar", 3, true)] - [DataRow("TestData.rar4", 3, true)] - [DataRow("TestData.tar.bz2", 6, true)] - [DataRow("TestData.tar.gz", 6, true)] - [DataRow("TestData.tar.xz", 3, true)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] - [DataRow("TestData.a", 3, true)] - [DataRow("TestData.bsd.ar", 3, true)] - [DataRow("TestData.iso", 3, true)] - [DataRow("TestData.vhdx", 3, true)] - [DataRow("TestData.wim", 3, true)] - [DataRow("EmptyFile.txt", 1, true)] - [DataRow("TestDataArchivesNested.Zip", 54, true)] - [DataRow("TestDataArchivesNested.Zip", 54, false)] - public void TimeoutTest(string fileName, int expectedNumFiles = 3, bool parallel = false) + [Theory] + [InlineData("TestData.7z", 3, false)] + [InlineData("TestData.tar", 6, false)] + [InlineData("TestData.rar", 3, false)] + [InlineData("TestData.rar4", 3, false)] + [InlineData("TestData.tar.bz2", 6, false)] + [InlineData("TestData.tar.gz", 6, false)] + [InlineData("TestData.tar.xz", 3, false)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] + [InlineData("TestData.a", 3, false)] + [InlineData("TestData.bsd.ar", 3, false)] + [InlineData("TestData.iso", 3, false)] + [InlineData("TestData.vhdx", 3, false)] + [InlineData("TestData.wim", 3, false)] + [InlineData("EmptyFile.txt", 1, false)] + [InlineData("TestData.zip", 5, true)] + [InlineData("TestData.7z", 3, true)] + [InlineData("TestData.tar", 6, true)] + [InlineData("TestData.rar", 3, true)] + [InlineData("TestData.rar4", 3, true)] + [InlineData("TestData.tar.bz2", 6, true)] + [InlineData("TestData.tar.gz", 6, true)] + [InlineData("TestData.tar.xz", 3, true)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] + [InlineData("TestData.a", 3, true)] + [InlineData("TestData.bsd.ar", 3, true)] + [InlineData("TestData.iso", 3, true)] + [InlineData("TestData.vhdx", 3, true)] + [InlineData("TestData.wim", 3, true)] + [InlineData("EmptyFile.txt", 1, true)] + [InlineData("TestDataArchivesNested.Zip", 54, true)] + [InlineData("TestDataArchivesNested.Zip", 54, false)] + public void TimeoutTest(string fileName, int _expectedNumFiles = 3, bool parallel = false) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); - Assert.ThrowsExactly(() => + Assert.Throws(() => { var results = extractor.Extract(path, new ExtractorOptions() @@ -59,47 +58,47 @@ public void TimeoutTest(string fileName, int expectedNumFiles = 3, bool parallel } // We should not be able to get to all the files - Assert.Fail(); + Assert.Fail("Should have thrown TimeoutException"); }); } - [TestMethod] - [DataRow("TestData.7z", 3, false)] - [DataRow("TestData.tar", 6, false)] - [DataRow("TestData.rar", 3, false)] - [DataRow("TestData.rar4", 3, false)] - [DataRow("TestData.tar.bz2", 6, false)] - [DataRow("TestData.tar.gz", 6, false)] - [DataRow("TestData.tar.xz", 3, false)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] - [DataRow("TestData.a", 3, false)] - [DataRow("TestData.bsd.ar", 3, false)] - [DataRow("TestData.iso", 3, false)] - [DataRow("TestData.vhdx", 3, false)] - [DataRow("TestData.wim", 3, false)] - [DataRow("EmptyFile.txt", 1, false)] - [DataRow("TestData.zip", 5, true)] - [DataRow("TestData.7z", 3, true)] - [DataRow("TestData.tar", 6, true)] - [DataRow("TestData.rar", 3, true)] - [DataRow("TestData.rar4", 3, true)] - [DataRow("TestData.tar.bz2", 6, true)] - [DataRow("TestData.tar.gz", 6, true)] - [DataRow("TestData.tar.xz", 3, true)] - [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] - [DataRow("TestData.a", 3, true)] - [DataRow("TestData.bsd.ar", 3, true)] - [DataRow("TestData.iso", 3, true)] - [DataRow("TestData.vhdx", 3, true)] - [DataRow("TestData.wim", 3, true)] - [DataRow("EmptyFile.txt", 1, true)] - [DataRow("TestDataArchivesNested.Zip", 54, true)] - [DataRow("TestDataArchivesNested.Zip", 54, false)] - public async Task TimeoutTestAsync(string fileName, int expectedNumFiles = 3, bool parallel = false) + [Theory] + [InlineData("TestData.7z", 3, false)] + [InlineData("TestData.tar", 6, false)] + [InlineData("TestData.rar", 3, false)] + [InlineData("TestData.rar4", 3, false)] + [InlineData("TestData.tar.bz2", 6, false)] + [InlineData("TestData.tar.gz", 6, false)] + [InlineData("TestData.tar.xz", 3, false)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] + [InlineData("TestData.a", 3, false)] + [InlineData("TestData.bsd.ar", 3, false)] + [InlineData("TestData.iso", 3, false)] + [InlineData("TestData.vhdx", 3, false)] + [InlineData("TestData.wim", 3, false)] + [InlineData("EmptyFile.txt", 1, false)] + [InlineData("TestData.zip", 5, true)] + [InlineData("TestData.7z", 3, true)] + [InlineData("TestData.tar", 6, true)] + [InlineData("TestData.rar", 3, true)] + [InlineData("TestData.rar4", 3, true)] + [InlineData("TestData.tar.bz2", 6, true)] + [InlineData("TestData.tar.gz", 6, true)] + [InlineData("TestData.tar.xz", 3, true)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] + [InlineData("TestData.a", 3, true)] + [InlineData("TestData.bsd.ar", 3, true)] + [InlineData("TestData.iso", 3, true)] + [InlineData("TestData.vhdx", 3, true)] + [InlineData("TestData.wim", 3, true)] + [InlineData("EmptyFile.txt", 1, true)] + [InlineData("TestDataArchivesNested.Zip", 54, true)] + [InlineData("TestDataArchivesNested.Zip", 54, false)] + public async Task TimeoutTestAsync(string fileName, int _expectedNumFiles = 3, bool parallel = false) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); - await Assert.ThrowsExactlyAsync(async () => + await Assert.ThrowsAsync(async () => { var results = extractor.ExtractAsync(path, new ExtractorOptions() @@ -113,7 +112,7 @@ await Assert.ThrowsExactlyAsync(async () => } // We should not be able to get to all the files - Assert.Fail(); + Assert.Fail("Should have thrown TimeoutException"); }); } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj b/RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj index fe84518..d1300e5 100644 --- a/RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj +++ b/RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj @@ -5,7 +5,6 @@ false enable 10.0 - Exe @@ -14,9 +13,10 @@ - - - + + + + diff --git a/RecursiveExtractor.Tests/SanitizePathTests.cs b/RecursiveExtractor.Tests/SanitizePathTests.cs index 774cf11..a0fb2f2 100644 --- a/RecursiveExtractor.Tests/SanitizePathTests.cs +++ b/RecursiveExtractor.Tests/SanitizePathTests.cs @@ -1,38 +1,37 @@ // Copyright (c) Microsoft Corporation. Licensed under the MIT License. using Microsoft.CST.RecursiveExtractor; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; using System.Runtime.InteropServices; +using Xunit; namespace RecursiveExtractor.Tests { - [TestClass] public class SanitizePathTests { - [TestMethod] - [DataRow("a\\file\\with:colon.name", "a\\file\\with_colon.name")] - [DataRow("a\\folder:with\\colon.name", "a\\folder_with\\colon.name")] + [Theory] + [InlineData("a\\file\\with:colon.name", "a\\file\\with_colon.name")] + [InlineData("a\\folder:with\\colon.name", "a\\folder_with\\colon.name")] public void TestSanitizePathWindows(string windowsInputPath, string expectedWindowsPath) { var entry = new FileEntry(windowsInputPath, Stream.Null); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Assert.AreEqual(expectedWindowsPath, entry.GetSanitizedPath()); + Assert.Equal(expectedWindowsPath, entry.GetSanitizedPath()); } } - [TestMethod] - [DataRow("a/file/with:colon.name", "a/file/with_colon.name")] - [DataRow("a/folder:with/colon.name", "a/folder_with/colon.name")] + [Theory] + [InlineData("a/file/with:colon.name", "a/file/with_colon.name")] + [InlineData("a/folder:with/colon.name", "a/folder_with/colon.name")] public void TestSanitizePathLinux(string linuxInputPath, string expectedLinuxPath) { var entry = new FileEntry(linuxInputPath, Stream.Null); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - Assert.AreEqual(expectedLinuxPath, entry.GetSanitizedPath()); + Assert.Equal(expectedLinuxPath, entry.GetSanitizedPath()); } } diff --git a/RecursiveExtractor.Tests/TestPathHelpers.cs b/RecursiveExtractor.Tests/TestPathHelpers.cs index 97461b2..d87e168 100644 --- a/RecursiveExtractor.Tests/TestPathHelpers.cs +++ b/RecursiveExtractor.Tests/TestPathHelpers.cs @@ -26,7 +26,7 @@ public static void DeleteTestDirectory() { // Not an error. Not every test makes the folder. } - catch (Exception e) + catch (Exception) { // Throwing the exception up may cause tests to fail due to file system oddness so just log Logger.Warn("Failed to delete Test Working Directory at {directory}", TestDirectoryPath); From c93472b4b466c92e26e6caea3881bd16d1c0e65e Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 17:57:52 -0800 Subject: [PATCH 10/28] Refactor TimeOutTests to remove expectedNumFiles param Updated TimeoutTest and TimeoutTestAsync to use only fileName and parallel parameters in InlineData and method signatures. Test logic remains unchanged, still asserting TimeoutException. --- .../ExtractorTests/TimeOutTests.cs | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs index d0e5f19..5537a21 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs @@ -9,38 +9,38 @@ namespace RecursiveExtractor.Tests.ExtractorTests; public class TimeOutTests : BaseExtractorTestClass { [Theory] - [InlineData("TestData.7z", 3, false)] - [InlineData("TestData.tar", 6, false)] - [InlineData("TestData.rar", 3, false)] - [InlineData("TestData.rar4", 3, false)] - [InlineData("TestData.tar.bz2", 6, false)] - [InlineData("TestData.tar.gz", 6, false)] - [InlineData("TestData.tar.xz", 3, false)] - [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] - [InlineData("TestData.a", 3, false)] - [InlineData("TestData.bsd.ar", 3, false)] - [InlineData("TestData.iso", 3, false)] - [InlineData("TestData.vhdx", 3, false)] - [InlineData("TestData.wim", 3, false)] - [InlineData("EmptyFile.txt", 1, false)] - [InlineData("TestData.zip", 5, true)] - [InlineData("TestData.7z", 3, true)] - [InlineData("TestData.tar", 6, true)] - [InlineData("TestData.rar", 3, true)] - [InlineData("TestData.rar4", 3, true)] - [InlineData("TestData.tar.bz2", 6, true)] - [InlineData("TestData.tar.gz", 6, true)] - [InlineData("TestData.tar.xz", 3, true)] - [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] - [InlineData("TestData.a", 3, true)] - [InlineData("TestData.bsd.ar", 3, true)] - [InlineData("TestData.iso", 3, true)] - [InlineData("TestData.vhdx", 3, true)] - [InlineData("TestData.wim", 3, true)] - [InlineData("EmptyFile.txt", 1, true)] - [InlineData("TestDataArchivesNested.Zip", 54, true)] - [InlineData("TestDataArchivesNested.Zip", 54, false)] - public void TimeoutTest(string fileName, int _expectedNumFiles = 3, bool parallel = false) + [InlineData("TestData.7z", false)] + [InlineData("TestData.tar", false)] + [InlineData("TestData.rar", false)] + [InlineData("TestData.rar4", false)] + [InlineData("TestData.tar.bz2", false)] + [InlineData("TestData.tar.gz", false)] + [InlineData("TestData.tar.xz", false)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", false)] + [InlineData("TestData.a", false)] + [InlineData("TestData.bsd.ar", false)] + [InlineData("TestData.iso", false)] + [InlineData("TestData.vhdx", false)] + [InlineData("TestData.wim", false)] + [InlineData("EmptyFile.txt", false)] + [InlineData("TestData.zip", true)] + [InlineData("TestData.7z", true)] + [InlineData("TestData.tar", true)] + [InlineData("TestData.rar", true)] + [InlineData("TestData.rar4", true)] + [InlineData("TestData.tar.bz2", true)] + [InlineData("TestData.tar.gz", true)] + [InlineData("TestData.tar.xz", true)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", true)] + [InlineData("TestData.a", true)] + [InlineData("TestData.bsd.ar", true)] + [InlineData("TestData.iso", true)] + [InlineData("TestData.vhdx", true)] + [InlineData("TestData.wim", true)] + [InlineData("EmptyFile.txt", true)] + [InlineData("TestDataArchivesNested.Zip", true)] + [InlineData("TestDataArchivesNested.Zip", false)] + public void TimeoutTest(string fileName, bool parallel = false) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); @@ -63,38 +63,38 @@ public void TimeoutTest(string fileName, int _expectedNumFiles = 3, bool paralle } [Theory] - [InlineData("TestData.7z", 3, false)] - [InlineData("TestData.tar", 6, false)] - [InlineData("TestData.rar", 3, false)] - [InlineData("TestData.rar4", 3, false)] - [InlineData("TestData.tar.bz2", 6, false)] - [InlineData("TestData.tar.gz", 6, false)] - [InlineData("TestData.tar.xz", 3, false)] - [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)] - [InlineData("TestData.a", 3, false)] - [InlineData("TestData.bsd.ar", 3, false)] - [InlineData("TestData.iso", 3, false)] - [InlineData("TestData.vhdx", 3, false)] - [InlineData("TestData.wim", 3, false)] - [InlineData("EmptyFile.txt", 1, false)] - [InlineData("TestData.zip", 5, true)] - [InlineData("TestData.7z", 3, true)] - [InlineData("TestData.tar", 6, true)] - [InlineData("TestData.rar", 3, true)] - [InlineData("TestData.rar4", 3, true)] - [InlineData("TestData.tar.bz2", 6, true)] - [InlineData("TestData.tar.gz", 6, true)] - [InlineData("TestData.tar.xz", 3, true)] - [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)] - [InlineData("TestData.a", 3, true)] - [InlineData("TestData.bsd.ar", 3, true)] - [InlineData("TestData.iso", 3, true)] - [InlineData("TestData.vhdx", 3, true)] - [InlineData("TestData.wim", 3, true)] - [InlineData("EmptyFile.txt", 1, true)] - [InlineData("TestDataArchivesNested.Zip", 54, true)] - [InlineData("TestDataArchivesNested.Zip", 54, false)] - public async Task TimeoutTestAsync(string fileName, int _expectedNumFiles = 3, bool parallel = false) + [InlineData("TestData.7z", false)] + [InlineData("TestData.tar", false)] + [InlineData("TestData.rar", false)] + [InlineData("TestData.rar4", false)] + [InlineData("TestData.tar.bz2", false)] + [InlineData("TestData.tar.gz", false)] + [InlineData("TestData.tar.xz", false)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", false)] + [InlineData("TestData.a", false)] + [InlineData("TestData.bsd.ar", false)] + [InlineData("TestData.iso", false)] + [InlineData("TestData.vhdx", false)] + [InlineData("TestData.wim", false)] + [InlineData("EmptyFile.txt", false)] + [InlineData("TestData.zip", true)] + [InlineData("TestData.7z", true)] + [InlineData("TestData.tar", true)] + [InlineData("TestData.rar", true)] + [InlineData("TestData.rar4", true)] + [InlineData("TestData.tar.bz2", true)] + [InlineData("TestData.tar.gz", true)] + [InlineData("TestData.tar.xz", true)] + [InlineData("sysvbanner_1.0-17fakesync1_amd64.deb", true)] + [InlineData("TestData.a", true)] + [InlineData("TestData.bsd.ar", true)] + [InlineData("TestData.iso", true)] + [InlineData("TestData.vhdx", true)] + [InlineData("TestData.wim", true)] + [InlineData("EmptyFile.txt", true)] + [InlineData("TestDataArchivesNested.Zip", true)] + [InlineData("TestDataArchivesNested.Zip", false)] + public async Task TimeoutTestAsync(string fileName, bool parallel = false) { var extractor = new Extractor(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); From 4543935b89bc4e5f0594c70c7ddaa4ac4c556f3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 02:00:59 +0000 Subject: [PATCH 11/28] Fix pipeline: remove duplicate --results-directory argument Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- Pipelines/recursive-extractor-pr.yml | 4 ++-- Pipelines/recursive-extractor-release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Pipelines/recursive-extractor-pr.yml b/Pipelines/recursive-extractor-pr.yml index 60085fa..749ca2f 100644 --- a/Pipelines/recursive-extractor-pr.yml +++ b/Pipelines/recursive-extractor-pr.yml @@ -42,7 +42,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory TestResults --collect:"XPlat Code Coverage"' + dotnetTestArgs: '--collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory TestResults --collect:"XPlat Code Coverage"' + dotnetTestArgs: '--collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/Pipelines/recursive-extractor-release.yml b/Pipelines/recursive-extractor-release.yml index a278186..529f86a 100644 --- a/Pipelines/recursive-extractor-release.yml +++ b/Pipelines/recursive-extractor-release.yml @@ -43,7 +43,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory TestResults --collect:"XPlat Code Coverage"' + dotnetTestArgs: '--collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--logger trx --results-directory TestResults --collect:"XPlat Code Coverage"' + dotnetTestArgs: '--collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' From d91b6ce933bacba41bc936b1af25f4aac1a2a527 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 02:15:28 +0000 Subject: [PATCH 12/28] Add coverlet.runsettings for code coverage HTML report compatibility Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- Pipelines/recursive-extractor-pr.yml | 4 ++-- Pipelines/recursive-extractor-release.yml | 4 ++-- coverlet.runsettings | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 coverlet.runsettings diff --git a/Pipelines/recursive-extractor-pr.yml b/Pipelines/recursive-extractor-pr.yml index 749ca2f..c153cc2 100644 --- a/Pipelines/recursive-extractor-pr.yml +++ b/Pipelines/recursive-extractor-pr.yml @@ -42,7 +42,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--collect:"XPlat Code Coverage"' + dotnetTestArgs: '--settings coverlet.runsettings --collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--collect:"XPlat Code Coverage"' + dotnetTestArgs: '--settings coverlet.runsettings --collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/Pipelines/recursive-extractor-release.yml b/Pipelines/recursive-extractor-release.yml index 529f86a..ee27134 100644 --- a/Pipelines/recursive-extractor-release.yml +++ b/Pipelines/recursive-extractor-release.yml @@ -43,7 +43,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--collect:"XPlat Code Coverage"' + dotnetTestArgs: '--settings coverlet.runsettings --collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' @@ -57,7 +57,7 @@ extends: poolName: MSSecurity-1ES-Build-Agents-Pool poolImage: MSSecurity-1ES-Windows-2022 poolOs: windows - dotnetTestArgs: '--collect:"XPlat Code Coverage"' + dotnetTestArgs: '--settings coverlet.runsettings --collect:"XPlat Code Coverage"' includeNuGetOrg: false nugetFeedsToUse: 'config' nugetConfigPath: 'nuget.config' diff --git a/coverlet.runsettings b/coverlet.runsettings new file mode 100644 index 0000000..d7c98c2 --- /dev/null +++ b/coverlet.runsettings @@ -0,0 +1,17 @@ + + + + + + + cobertura,opencover + **/obj/**,**/bin/** + false + true + false + true + + + + + From d85415e9ec4d3c92382d92a89a6dcc704c439215 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 18:15:32 -0800 Subject: [PATCH 13/28] Wrapped the Directory.Delete cleanup calls in both EnsureDisposedWithExtractToDirectory(string) and EnsureDisposedWithExtractToDirectoryAsync(string) with try-catch (DirectoryNotFoundException), matching the defensive pattern already used in TestPathHelpers.DeleteTestDirectory(). This makes the test cleanup resilient to the race with parallel test disposal. --- .../ExtractorTests/DisposeBehaviorTests.cs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs index eb2dcad..d1bbd34 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs @@ -132,13 +132,22 @@ public void EnsureDisposedWithExtractToDirectory(string fileName) var extractor = new Extractor(); extractor.ExtractToDirectory(directory, copyPath); File.Delete(copyPath); - if (Directory.Exists(directory)) + try { - Directory.Delete(directory, true); + if (Directory.Exists(directory)) + { + Directory.Delete(directory, true); + } } - if (Directory.Exists(copyDirectory)) { - Directory.Delete(copyDirectory, true); + catch (DirectoryNotFoundException) { } + try + { + if (Directory.Exists(copyDirectory)) + { + Directory.Delete(copyDirectory, true); + } } + catch (DirectoryNotFoundException) { } } [Theory] @@ -155,13 +164,21 @@ public async Task EnsureDisposedWithExtractToDirectoryAsync(string fileName) var extractor = new Extractor(); await extractor.ExtractToDirectoryAsync(directory, copyPath); File.Delete(copyPath); - if (Directory.Exists(directory)) + try { - Directory.Delete(directory, true); + if (Directory.Exists(directory)) + { + Directory.Delete(directory, true); + } } - if (Directory.Exists(copyDirectory)) + catch (DirectoryNotFoundException) { } + try { - Directory.Delete(copyDirectory, true); + if (Directory.Exists(copyDirectory)) + { + Directory.Delete(copyDirectory, true); + } } + catch (DirectoryNotFoundException) { } } } \ No newline at end of file From d1365c6fb01fb329fdbc1f2295d2f1050d8317ec Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 18:32:38 -0800 Subject: [PATCH 14/28] Refactor test cleanup to use process-unique temp dirs Refactored test infrastructure to use a process-unique subdirectory for temporary test files, preventing cross-process interference. Introduced TestCleanupFixture with xUnit's IClassFixture for one-time-per-class cleanup, replacing per-test Dispose logic in BaseExtractorTestClass. Updated DeleteTestDirectory to only remove the process-specific directory. --- .../ExtractorTests/BaseExtractorTestClass.cs | 19 +++++++++++++------ RecursiveExtractor.Tests/TestPathHelpers.cs | 7 +++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs index b6cdd50..a503106 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs @@ -3,10 +3,22 @@ using NLog; using NLog.Config; using NLog.Targets; +using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -public class BaseExtractorTestClass : IDisposable +/// +/// Shared fixture that cleans up temp directories once after all tests in a class complete. +/// +public class TestCleanupFixture : IDisposable +{ + public void Dispose() + { + TestPathHelpers.DeleteTestDirectory(); + } +} + +public class BaseExtractorTestClass : IClassFixture { protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); @@ -22,9 +34,4 @@ static BaseExtractorTestClass() LogManager.Configuration = config; } - - public void Dispose() - { - TestPathHelpers.DeleteTestDirectory(); - } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/TestPathHelpers.cs b/RecursiveExtractor.Tests/TestPathHelpers.cs index d87e168..f854934 100644 --- a/RecursiveExtractor.Tests/TestPathHelpers.cs +++ b/RecursiveExtractor.Tests/TestPathHelpers.cs @@ -9,7 +9,10 @@ public static class TestPathHelpers { public const string TestTempFolderName = "RE_Tests"; - public static string TestDirectoryPath => Path.Combine(Path.GetTempPath(), TestTempFolderName); + // Use a process-unique subdirectory to avoid cross-TFM/cross-process interference + private static readonly string ProcessId = System.Diagnostics.Process.GetCurrentProcess().Id.ToString(); + + public static string TestDirectoryPath => Path.Combine(Path.GetTempPath(), TestTempFolderName, ProcessId); public static string GetFreshTestDirectory() { @@ -20,7 +23,7 @@ public static void DeleteTestDirectory() { try { - Directory.Delete(Path.Combine(TestDirectoryPath), true); + Directory.Delete(TestDirectoryPath, true); } catch (DirectoryNotFoundException) { From 2999ca8f704e074aef221ce0a73c8a68d398e05f Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 18:43:34 -0800 Subject: [PATCH 15/28] Refactor test data to use TheoryData in xUnit tests Switch ArchiveData and NoRecursionData to TheoryData for improved type safety and xUnit integration. Simplify test case syntax, make ExtractorOptions fields readonly with new() initializers, and replace results.Count() with results.Count for clarity and efficiency. --- .../ExtractorTests/ExpectedNumFilesTests.cs | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs index 2989287..dec4ef3 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs @@ -17,32 +17,32 @@ public class ExpectedNumFilesTests : BaseExtractorTestClass /// /// Mapping from Test archive name to expected number of files to extract /// - public static IEnumerable ArchiveData + public static TheoryData ArchiveData { get { - return new[] + return new TheoryData { - new object[] { "100Trees.7z", 101 }, - new object[] { "TestData.zip", 5 }, - new object[] { "TestData.7z",3 }, - new object[] { "TestData.tar", 6 }, - new object[] { "TestData.rar",3 }, - new object[] { "TestData.rar4",3 }, - new object[] { "TestData.tar.bz2", 6 }, - new object[] { "TestData.tar.gz", 6 }, - new object[] { "TestData.tar.xz",3 }, - new object[] { "sysvbanner_1.0-17fakesync1_amd64.deb", 8 }, - new object[] { "TestData.a",3 }, - new object[] { "TestData.bsd.ar",3 }, - new object[] { "TestData.iso",3 }, - new object[] { "TestData.vhdx",3 }, - new object[] { "TestData.wim",3 }, - new object[] { "EmptyFile.txt", 1 }, - new object[] { "TestDataArchivesNested.Zip", 54 }, - new object[] { "UdfTest.iso", 3 }, - new object[] { "UdfTestWithMultiSystem.iso", 3 }, -// new object[] { "HfsSampleUDCO.dmg", 2 } + { "100Trees.7z", 101 }, + { "TestData.zip", 5 }, + { "TestData.7z",3 }, + { "TestData.tar", 6 }, + { "TestData.rar",3 }, + { "TestData.rar4",3 }, + { "TestData.tar.bz2", 6 }, + { "TestData.tar.gz", 6 }, + { "TestData.tar.xz",3 }, + { "sysvbanner_1.0-17fakesync1_amd64.deb", 8 }, + { "TestData.a",3 }, + { "TestData.bsd.ar",3 }, + { "TestData.iso",3 }, + { "TestData.vhdx",3 }, + { "TestData.wim",3 }, + { "EmptyFile.txt", 1 }, + { "TestDataArchivesNested.Zip", 54 }, + { "UdfTest.iso", 3 }, + { "UdfTestWithMultiSystem.iso", 3 }, +// { "HfsSampleUDCO.dmg", 2 } }; } } @@ -50,31 +50,31 @@ public static IEnumerable ArchiveData /// /// Mapping from Test archive name to expected number of files to extract when recursion is disabled /// - public static IEnumerable NoRecursionData + public static TheoryData NoRecursionData { get { - return new[] + return new TheoryData { - new object[] { "100Trees.7z", 101 }, - new object[] { "TestData.zip", 5 }, - new object[] { "TestData.7z", 3 }, - new object[] { "TestData.tar", 6 }, - new object[] { "TestData.rar", 3 }, - new object[] { "TestData.rar4", 3 }, - new object[] { "TestData.tar.bz2", 1 }, - new object[] { "TestData.tar.gz", 1 }, - new object[] { "TestData.tar.xz", 1 }, - new object[] { "sysvbanner_1.0-17fakesync1_amd64.deb", 2 }, - new object[] { "TestData.a", 3 }, - new object[] { "TestData.bsd.ar", 3 }, - new object[] { "TestData.iso", 3 }, - new object[] { "TestData.vhdx", 3 }, - new object[] { "TestData.wim", 3 }, - new object[] { "EmptyFile.txt", 1 }, - new object[] { "TestDataArchivesNested.Zip", 14 }, - new object[] { "UdfTestWithMultiSystem.iso", 3 }, -// new object[] { "HfsSampleUDCO.dmg", 2 } + { "100Trees.7z", 101 }, + { "TestData.zip", 5 }, + { "TestData.7z", 3 }, + { "TestData.tar", 6 }, + { "TestData.rar", 3 }, + { "TestData.rar4", 3 }, + { "TestData.tar.bz2", 1 }, + { "TestData.tar.gz", 1 }, + { "TestData.tar.xz", 1 }, + { "sysvbanner_1.0-17fakesync1_amd64.deb", 2 }, + { "TestData.a", 3 }, + { "TestData.bsd.ar", 3 }, + { "TestData.iso", 3 }, + { "TestData.vhdx", 3 }, + { "TestData.wim", 3 }, + { "EmptyFile.txt", 1 }, + { "TestDataArchivesNested.Zip", 14 }, + { "UdfTestWithMultiSystem.iso", 3 }, +// { "HfsSampleUDCO.dmg", 2 } }; } } @@ -84,8 +84,8 @@ private ExtractorOptions GetExtractorOptions(bool parallel = false) return parallel ? defaultExtractorTestOptionsParallel : defaultExtractorTestOptions; } - private ExtractorOptions defaultExtractorTestOptions = new ExtractorOptions() { MaxExtractedBytesRatio = 300 }; - private ExtractorOptions defaultExtractorTestOptionsParallel = new ExtractorOptions() { Parallel = true, MaxExtractedBytesRatio = 300 }; + private readonly ExtractorOptions defaultExtractorTestOptions = new() { MaxExtractedBytesRatio = 300 }; + private readonly ExtractorOptions defaultExtractorTestOptionsParallel = new() { Parallel = true, MaxExtractedBytesRatio = 300 }; [Theory] [MemberData(nameof(ArchiveData))] @@ -194,7 +194,7 @@ public void ExtractArchiveParallel(string fileName, int expectedNumFiles) var results = extractor.Extract(path, GetExtractorOptions(true)).ToList(); var names = results.Select(x => x.FullPath); var stringOfNames = string.Join("\n", names); - Assert.Equal(expectedNumFiles, results.Count()); + Assert.Equal(expectedNumFiles, results.Count); } [Theory] From 5a3f43f873618f383029657dea0b102d8336374c Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 18:44:34 -0800 Subject: [PATCH 16/28] Refactor CliTests: static methods, C# 12 collections Refactored ExtractArchive to be static and updated all calls. Replaced .ToArray() with C# 12 collection expressions for file lists. Switched AllowFilters and DenyFilters to use collection expressions. Removed unused static Logger field. --- .../CliTests/CliTests.cs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs b/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs index acde6ff..a649bd2 100644 --- a/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs +++ b/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs @@ -33,10 +33,10 @@ public class CliTests : BaseExtractorTestClass [InlineData("TestDataArchivesNested.Zip", 54)] public void ExtractArchiveParallel(string fileName, int expectedNumFiles = 3) { - ExtractArchive(fileName, expectedNumFiles, false); + CliTests.ExtractArchive(fileName, expectedNumFiles, false); } - internal void ExtractArchive(string fileName, int expectedNumFiles, bool singleThread) + internal static void ExtractArchive(string fileName, int expectedNumFiles, bool singleThread) { var directory = TestPathHelpers.GetFreshTestDirectory(); var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); @@ -45,7 +45,7 @@ internal void ExtractArchive(string fileName, int expectedNumFiles, bool singleT Thread.Sleep(100); if (Directory.Exists(directory)) { - files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); + files = [.. Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)]; } Assert.Equal(expectedNumFiles, files.Length); } @@ -69,7 +69,7 @@ internal void ExtractArchive(string fileName, int expectedNumFiles, bool singleT [InlineData("TestDataArchivesNested.Zip", 54)] public void ExtractArchiveSingleThread(string fileName, int expectedNumFiles = 3) { - ExtractArchive(fileName, expectedNumFiles, true); + CliTests.ExtractArchive(fileName, expectedNumFiles, true); } [Theory] @@ -85,15 +85,15 @@ public void ExtractArchiveWithAllowFilters(string fileName, int expectedNumFiles Input = newpath, Output = directory, Verbose = true, - AllowFilters = new string[] - { + AllowFilters = + [ "*.cs" - } + ] }); var files = Array.Empty(); if (Directory.Exists(directory)) { - files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); + files = [.. Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)]; } Assert.Equal(expectedNumFiles, files.Length); } @@ -111,15 +111,15 @@ public void ExtractArchiveWithDenyFilters(string fileName, int expectedNumFiles Input = newpath, Output = directory, Verbose = true, - DenyFilters = new string[] - { + DenyFilters = + [ "*.cs" - } + ] }); var files = Array.Empty(); if (Directory.Exists(directory)) { - files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); + files = [.. Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)]; } Assert.Equal(expectedNumFiles, files.Length); } @@ -134,10 +134,8 @@ public void ExtractEncryptedArchive(string fileName, int expectedNumFiles = 3) var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName); var passwords = EncryptedArchiveTests.TestArchivePasswords.Values.SelectMany(x => x); RecursiveExtractorClient.ExtractCommand(new ExtractCommandOptions() { Input = path, Output = directory, Verbose = true, Passwords = passwords }); - string[] files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray(); + string[] files = [.. Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)]; Assert.Equal(expectedNumFiles, files.Length); - } - - protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + } } } \ No newline at end of file From fcdff493ea4b8c06a6357f9b35e14fe210dd8107 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 02:51:12 +0000 Subject: [PATCH 17/28] Add PublishCodeCoverageResults@2 task via postTest parameter Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- Pipelines/recursive-extractor-pr.yml | 8 ++++++++ Pipelines/recursive-extractor-release.yml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/Pipelines/recursive-extractor-pr.yml b/Pipelines/recursive-extractor-pr.yml index c153cc2..97a4514 100644 --- a/Pipelines/recursive-extractor-pr.yml +++ b/Pipelines/recursive-extractor-pr.yml @@ -48,6 +48,10 @@ extends: nugetConfigPath: 'nuget.config' onInit: - task: NuGetAuthenticate@1 + postTest: + - task: PublishCodeCoverageResults@2 + inputs: + summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' - template: dotnet-test-job.yml@templates parameters: @@ -63,6 +67,10 @@ extends: nugetConfigPath: 'nuget.config' onInit: - task: NuGetAuthenticate@1 + postTest: + - task: PublishCodeCoverageResults@2 + inputs: + summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' - stage: Build dependsOn: diff --git a/Pipelines/recursive-extractor-release.yml b/Pipelines/recursive-extractor-release.yml index ee27134..6cdd928 100644 --- a/Pipelines/recursive-extractor-release.yml +++ b/Pipelines/recursive-extractor-release.yml @@ -49,6 +49,10 @@ extends: nugetConfigPath: 'nuget.config' onInit: - task: NuGetAuthenticate@1 + postTest: + - task: PublishCodeCoverageResults@2 + inputs: + summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' - template: dotnet-test-job.yml@templates parameters: jobName: 'cli_dotnet_test_windows' @@ -63,6 +67,10 @@ extends: nugetConfigPath: 'nuget.config' onInit: - task: NuGetAuthenticate@1 + postTest: + - task: PublishCodeCoverageResults@2 + inputs: + summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' - stage: Build dependsOn: From 0372d9989dfd010897eac3779c6d268b56774083 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 19:02:06 -0800 Subject: [PATCH 18/28] Ensure reliable stream reads with ReadExactly polyfill Replaced all Stream.Read calls with ReadExactly to guarantee full reads and prevent partial data issues. Added a ReadExactly extension polyfill for pre-.NET 7.0 compatibility. Improved archive extractor robustness by handling null entry keys. Updated stream comparison logic for correctness. Clarified some comments and documentation. These changes enhance reliability and consistency when parsing archive formats. --- RecursiveExtractor/ArFile.cs | 32 +++++++++---------- RecursiveExtractor/DebArchiveFile.cs | 6 ++-- RecursiveExtractor/Extractor.cs | 16 ++++++---- RecursiveExtractor/Extractors/RarExtractor.cs | 4 +-- .../Extractors/SevenZipExtractor.cs | 4 +-- RecursiveExtractor/Extractors/ZipExtractor.cs | 2 +- RecursiveExtractor/MiniMagic.cs | 20 ++++++------ .../StreamReadExactlyPolyfill.cs | 25 +++++++++++++++ 8 files changed, 69 insertions(+), 40 deletions(-) create mode 100644 RecursiveExtractor/StreamReadExactlyPolyfill.cs diff --git a/RecursiveExtractor/ArFile.cs b/RecursiveExtractor/ArFile.cs index 01845d9..6672806 100644 --- a/RecursiveExtractor/ArFile.cs +++ b/RecursiveExtractor/ArFile.cs @@ -32,7 +32,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract var headerBuffer = new byte[60]; while (fileEntry.Content.Length - fileEntry.Content.Position >= 60) { - fileEntry.Content.Read(headerBuffer, 0, 60); + fileEntry.Content.ReadExactly(headerBuffer, 0, 60); var headerString = Encoding.ASCII.GetString(headerBuffer); if (long.TryParse(Encoding.ASCII.GetString(headerBuffer[48..58]), out var size))// header size in bytes { @@ -46,7 +46,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract // This should just be a list of names, size should be safe to load in memory and cast // to int var fileNamesBytes = new byte[size]; - fileEntry.Content.Read(fileNamesBytes, 0, (int)size); + fileEntry.Content.ReadExactly(fileNamesBytes, 0, (int)size); var name = new StringBuilder(); var index = 0; @@ -76,7 +76,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract var nameSpan = new byte[nameLength]; // This should move us right to the file - fileEntry.Content.Read(nameSpan, 0, nameLength); + fileEntry.Content.ReadExactly(nameSpan, 0, nameLength); var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, size - nameLength); @@ -93,7 +93,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract // terminated strings "symbol name" (possibly filename) var tableContents = new byte[size]; - fileEntry.Content.Read(tableContents, 0, (int)size); + fileEntry.Content.ReadExactly(tableContents, 0, (int)size); var numEntries = IntFromBigEndianBytes(tableContents[0..4]); var filePositions = new int[numEntries]; @@ -124,7 +124,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract foreach (var entry in fileEntries) { fileEntry.Content.Position = entry.Item1; - fileEntry.Content.Read(headerBuffer, 0, 60); + fileEntry.Content.ReadExactly(headerBuffer, 0, 60); if (long.TryParse(Encoding.ASCII.GetString(headerBuffer[48..58]), out var innerSize))// header size in bytes { @@ -162,14 +162,14 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract // strings "symbol name" (possibly filename) var buffer = new byte[8]; - fileEntry.Content.Read(buffer, 0, 8); + fileEntry.Content.ReadExactly(buffer, 0, 8); var numEntries = Int64FromBigEndianBytes(buffer); var filePositions = new long[numEntries]; for (var i = 0; i < numEntries; i++) { - fileEntry.Content.Read(buffer, 0, 8); + fileEntry.Content.ReadExactly(buffer, 0, 8); filePositions[i] = Int64FromBigEndianBytes(buffer); } @@ -179,7 +179,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract while (fileEntry.Content.Position < size) { - fileEntry.Content.Read(buffer, 0, 1); + fileEntry.Content.ReadExactly(buffer, 0, 1); if (buffer[0] == '\0') { fileEntries.Add((filePositions[index++], sb.ToString())); @@ -195,7 +195,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract { fileEntry.Content.Position = innerEntry.Item1; - fileEntry.Content.Read(headerBuffer, 0, 60); + fileEntry.Content.ReadExactly(headerBuffer, 0, 60); if (long.TryParse(Encoding.ASCII.GetString(headerBuffer[48..58]), out var innerSize))// header size in bytes { @@ -217,9 +217,9 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract { filename = innerEntry.Item2; } + var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, innerSize); CopyStreamBytes(fileEntry.Content, entryStream, innerSize); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); } } @@ -281,7 +281,7 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi var headerBuffer = new byte[60]; while (fileEntry.Content.Length - fileEntry.Content.Position >= 60) { - fileEntry.Content.Read(headerBuffer, 0, 60); + fileEntry.Content.ReadExactly(headerBuffer, 0, 60); if (long.TryParse(Encoding.ASCII.GetString(headerBuffer[48..58]), out var size))// header size in bytes { governor.CheckResourceGovernor(size); @@ -339,7 +339,7 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi // terminated strings "symbol name" (possibly filename) var tableContents = new byte[size]; - fileEntry.Content.Read(tableContents, 0, (int)size); + fileEntry.Content.ReadExactly(tableContents, 0, (int)size); var numEntries = IntFromBigEndianBytes(tableContents[0..4]); var filePositions = new int[numEntries]; @@ -407,14 +407,14 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi // strings "symbol name" (possibly filename) var buffer = new byte[8]; - fileEntry.Content.Read(buffer, 0, 8); + fileEntry.Content.ReadExactly(buffer, 0, 8); var numEntries = Int64FromBigEndianBytes(buffer); var filePositions = new long[numEntries]; for (var i = 0; i < numEntries; i++) { - fileEntry.Content.Read(buffer, 0, 8); + fileEntry.Content.ReadExactly(buffer, 0, 8); filePositions[i] = Int64FromBigEndianBytes(buffer); } @@ -424,7 +424,7 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi while (fileEntry.Content.Position < size) { - fileEntry.Content.Read(buffer, 0, 1); + fileEntry.Content.ReadExactly(buffer, 0, 1); if (buffer[0] == '\0') { fileEntries.Add((filePositions[index++], sb.ToString())); @@ -440,7 +440,7 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi { fileEntry.Content.Position = innerEntry.Item1; - fileEntry.Content.Read(headerBuffer, 0, 60); + fileEntry.Content.ReadExactly(headerBuffer, 0, 60); if (long.TryParse(Encoding.ASCII.GetString(headerBuffer[48..58]), out var innerSize))// header size in bytes { diff --git a/RecursiveExtractor/DebArchiveFile.cs b/RecursiveExtractor/DebArchiveFile.cs index a7d4260..2513964 100644 --- a/RecursiveExtractor/DebArchiveFile.cs +++ b/RecursiveExtractor/DebArchiveFile.cs @@ -30,7 +30,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract while (fileEntry.Content.Length - fileEntry.Content.Position >= 60) { - fileEntry.Content.Read(headerBytes, 0, 60); + fileEntry.Content.ReadExactly(headerBytes, 0, 60); var filename = Encoding.ASCII.GetString(headerBytes[0..16]).Trim(); // filename is 16 bytes var fileSizeBytes = headerBytes[48..58]; // File size is decimal-encoded, 10 bytes long if (int.TryParse(Encoding.ASCII.GetString(fileSizeBytes).Trim(), out var fileSize)) @@ -39,7 +39,7 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract governor.AdjustRemainingBytes(-fileSize); var entryContent = new byte[fileSize]; - fileEntry.Content.Read(entryContent, 0, fileSize); + fileEntry.Content.ReadExactly(entryContent, 0, fileSize); var stream = new MemoryStream(entryContent); yield return new FileEntry(filename, stream, fileEntry, true); } @@ -70,7 +70,7 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi while (fileEntry.Content.Length - fileEntry.Content.Position >= 60) { - fileEntry.Content.Read(headerBytes, 0, 60); + fileEntry.Content.ReadExactly(headerBytes, 0, 60); var filename = Encoding.ASCII.GetString(headerBytes[0..16]).Trim(); // filename is 16 bytes var fileSizeBytes = headerBytes[48..58]; // File size is decimal-encoded, 10 bytes long if (int.TryParse(Encoding.ASCII.GetString(fileSizeBytes).Trim(), out var fileSize)) diff --git a/RecursiveExtractor/Extractor.cs b/RecursiveExtractor/Extractor.cs index 15dafc5..8b14fe2 100644 --- a/RecursiveExtractor/Extractor.cs +++ b/RecursiveExtractor/Extractor.cs @@ -148,13 +148,17 @@ public static bool AreIdentical(FileEntry fileEntry1, FileEntry fileEntry2) var bytesRemaining = stream2.Length; while (bytesRemaining > 0) { - stream1.Read(buffer1, 0, bufferSize); - stream2.Read(buffer2, 0, bufferSize); - if (!buffer1.SequenceEqual(buffer2)) + var bytesToRead = (int)Math.Min(bufferSize, bytesRemaining); + stream1.ReadExactly(buffer1, 0, bytesToRead); + stream2.ReadExactly(buffer2, 0, bytesToRead); + for (int i = 0; i < bytesToRead; i++) { - stream1.Position = position1; - stream2.Position = position2; - return false; + if (buffer1[i] != buffer2[i]) + { + stream1.Position = position1; + stream2.Position = position2; + return false; + } } bytesRemaining = stream2.Length - stream2.Position; } diff --git a/RecursiveExtractor/Extractors/RarExtractor.cs b/RecursiveExtractor/Extractors/RarExtractor.cs index e8b3140..e109658 100644 --- a/RecursiveExtractor/Extractors/RarExtractor.cs +++ b/RecursiveExtractor/Extractors/RarExtractor.cs @@ -104,7 +104,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra foreach (var entry in rarArchive.Entries.Where(x => x.IsComplete && !x.IsDirectory)) { governor.CheckResourceGovernor(entry.Size); - var name = entry.Key.Replace('/', Path.DirectorySeparatorChar); + var name = (entry.Key ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); var newFileEntry = await FileEntry.FromStreamAsync(name, entry.OpenEntryStream(), fileEntry, entry.CreatedTime, entry.LastModifiedTime, entry.LastAccessedTime, memoryStreamCutoff: options.MemoryStreamCutoff).ConfigureAwait(false); if (newFileEntry != null) { @@ -149,7 +149,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti try { var stream = entry.OpenEntryStream(); - var name = entry.Key.Replace('/', Path.DirectorySeparatorChar); + var name = (entry.Key ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); newFileEntry = new FileEntry(name, stream, fileEntry, false, entry.CreatedTime, entry.LastModifiedTime, entry.LastAccessedTime, memoryStreamCutoff: options.MemoryStreamCutoff); } catch (Exception e) diff --git a/RecursiveExtractor/Extractors/SevenZipExtractor.cs b/RecursiveExtractor/Extractors/SevenZipExtractor.cs index 50228a5..2f1e3a5 100644 --- a/RecursiveExtractor/Extractors/SevenZipExtractor.cs +++ b/RecursiveExtractor/Extractors/SevenZipExtractor.cs @@ -41,7 +41,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra foreach (var entry in sevenZipArchive.Entries.Where(x => !x.IsDirectory && x.IsComplete).ToList()) { governor.CheckResourceGovernor(entry.Size); - var name = entry.Key.Replace('/', Path.DirectorySeparatorChar); + var name = (entry.Key ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); var newFileEntry = await FileEntry.FromStreamAsync(name, entry.OpenEntryStream(), fileEntry, entry.CreatedTime, entry.LastModifiedTime, entry.LastAccessedTime, memoryStreamCutoff: options.MemoryStreamCutoff).ConfigureAwait(false); if (newFileEntry != null) @@ -154,7 +154,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti foreach (var entry in entries) { governor.CheckResourceGovernor(entry.Size); - var name = entry.Key.Replace('/', Path.DirectorySeparatorChar); + var name = (entry.Key ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); var newFileEntry = new FileEntry(name, entry.OpenEntryStream(), fileEntry, createTime: entry.CreatedTime, modifyTime: entry.LastModifiedTime, accessTime: entry.LastAccessedTime, memoryStreamCutoff: options.MemoryStreamCutoff); if (options.Recurse || topLevel) diff --git a/RecursiveExtractor/Extractors/ZipExtractor.cs b/RecursiveExtractor/Extractors/ZipExtractor.cs index 0c78623..5b706b4 100644 --- a/RecursiveExtractor/Extractors/ZipExtractor.cs +++ b/RecursiveExtractor/Extractors/ZipExtractor.cs @@ -50,7 +50,7 @@ public ZipExtractor(Extractor context) using var testStream = testEntry.OpenEntryStream(); // If we can read without exception, password is correct var buffer = new byte[1]; - testStream.Read(buffer, 0, 1); + testStream.ReadExactly(buffer, 0, 1); return password; } } diff --git a/RecursiveExtractor/MiniMagic.cs b/RecursiveExtractor/MiniMagic.cs index 932ac5d..64c914d 100644 --- a/RecursiveExtractor/MiniMagic.cs +++ b/RecursiveExtractor/MiniMagic.cs @@ -81,7 +81,7 @@ public enum ArchiveFileType /// VMDK, /// - /// A DMG disc image. + /// A DMG disc image. /// DMG, /// @@ -128,7 +128,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) { var dmgFooterMagic = new byte[] { 0x6b, 0x6f, 0x6c, 0x79 }; fileStream.Position = fileStream.Length - 0x200; // Footer position - fileStream.Read(buffer, 0, 4); + fileStream.ReadExactly(buffer, 0, 4); fileStream.Position = initialPosition; if (dmgFooterMagic.SequenceEqual(buffer[0..4])) @@ -140,7 +140,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) if (fileStream.Length >= 9) { fileStream.Position = 0; - fileStream.Read(buffer, 0, 9); + fileStream.ReadExactly(buffer, 0, 9); fileStream.Position = initialPosition; if (buffer[0] == 0x50 && buffer[1] == 0x4B && buffer[2] == 0x03 && buffer[3] == 0x04) @@ -181,7 +181,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) { fileStream.Position = 512; var secondToken = new byte[21]; - fileStream.Read(secondToken, 0, 21); + fileStream.ReadExactly(secondToken, 0, 21); fileStream.Position = initialPosition; if (Encoding.ASCII.GetString(secondToken) == "# Disk DescriptorFile") @@ -194,7 +194,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) { // .deb https://manpages.debian.org/unstable/dpkg-dev/deb.5.en.html fileStream.Position = 68; - fileStream.Read(buffer, 0, 4); + fileStream.ReadExactly(buffer, 0, 4); fileStream.Position = initialPosition; var encoding = new ASCIIEncoding(); @@ -208,7 +208,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) // Created by GNU ar https://en.wikipedia.org/wiki/Ar_(Unix)#System_V_(or_GNU)_variant fileStream.Position = 8; - fileStream.Read(headerBuffer, 0, 60); + fileStream.ReadExactly(headerBuffer, 0, 60); fileStream.Position = initialPosition; // header size in bytes @@ -232,7 +232,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) if (fileStream.Length >= 262) { fileStream.Position = 257; - fileStream.Read(buffer, 0, 5); + fileStream.ReadExactly(buffer, 0, 5); fileStream.Position = initialPosition; if (buffer[0] == 0x75 && buffer[1] == 0x73 && buffer[2] == 0x74 && buffer[3] == 0x61 && buffer[4] == 0x72) @@ -245,7 +245,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) if (fileStream.Length > 32768 + 2048) { fileStream.Position = 32769; - fileStream.Read(buffer, 0, 5); + fileStream.ReadExactly(buffer, 0, 5); fileStream.Position = initialPosition; if (buffer[0] == 'C' && buffer[1] == 'D' && buffer[2] == '0' && buffer[3] == '0' && buffer[4] == '1') @@ -266,7 +266,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) var vhdFooterCookie = new byte[] { 0x63, 0x6F, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x78 }; fileStream.Position = fileStream.Length - 0x200; // Footer position - fileStream.Read(buffer, 0, 8); + fileStream.ReadExactly(buffer, 0, 8); fileStream.Position = initialPosition; if (vhdFooterCookie.SequenceEqual(buffer[0..8])) @@ -275,7 +275,7 @@ public static ArchiveFileType DetectFileType(Stream fileStream) } fileStream.Position = fileStream.Length - 0x1FF; //If created on legacy platform footer is 511 bytes instead - fileStream.Read(buffer, 0, 8); + fileStream.ReadExactly(buffer, 0, 8); fileStream.Position = initialPosition; if (vhdFooterCookie.SequenceEqual(buffer[0..8])) diff --git a/RecursiveExtractor/StreamReadExactlyPolyfill.cs b/RecursiveExtractor/StreamReadExactlyPolyfill.cs new file mode 100644 index 0000000..1a2a0f5 --- /dev/null +++ b/RecursiveExtractor/StreamReadExactlyPolyfill.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. + +#if !NET7_0_OR_GREATER +using System.IO; + +namespace Microsoft.CST.RecursiveExtractor +{ + internal static class StreamReadExactlyPolyfill + { + internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) + { + int totalRead = 0; + while (totalRead < count) + { + int read = stream.Read(buffer, offset + totalRead, count - totalRead); + if (read == 0) + { + throw new EndOfStreamException(); + } + totalRead += read; + } + } + } +} +#endif From cb99764c67233ab104e514e3d8dd73059fe005d0 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 21:24:27 -0800 Subject: [PATCH 19/28] Refactor test base class and remove cleanup fixture Removed the TestCleanupFixture and related IClassFixture usage from BaseExtractorTestClass. Cleaned up and reorganized using directives, removing unnecessary usings and adding specific NLog usings. The file now only contains BaseExtractorTestClass with its logger initialization. --- .../ExtractorTests/BaseExtractorTestClass.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs index a503106..b493c3a 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs @@ -1,24 +1,10 @@ -using System; -using Microsoft.CST.RecursiveExtractor.Tests; -using NLog; +using NLog; using NLog.Config; using NLog.Targets; -using Xunit; namespace RecursiveExtractor.Tests.ExtractorTests; -/// -/// Shared fixture that cleans up temp directories once after all tests in a class complete. -/// -public class TestCleanupFixture : IDisposable -{ - public void Dispose() - { - TestPathHelpers.DeleteTestDirectory(); - } -} - -public class BaseExtractorTestClass : IClassFixture +public class BaseExtractorTestClass { protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); From 3a348b6f72d3e8402ad820ff496c54ad9ce23885 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 22:29:44 -0800 Subject: [PATCH 20/28] Refactor tests to use XUnit fixture pattern Test classes now use IClassFixture for improved setup/teardown and isolation. BaseExtractorTestClass implements IDisposable for proper cleanup. Added documentation and updated using statements. Minor formatting and code organization improvements. --- .../CliTests/CliTests.cs | 2 +- .../ExtractorTests/BaseExtractorTestClass.cs | 16 ++++++++++++++-- .../ExtractorTests/DisposeBehaviorTests.cs | 2 +- .../ExtractorTests/EncryptedArchiveTests.cs | 2 +- .../ExtractorTests/ExpectedNumFilesTests.cs | 2 +- .../ExtractorTests/FilterTests.cs | 2 +- .../ExtractorTests/MiniMagicTests.cs | 2 +- .../ExtractorTests/TestQuinesAndSlip.cs | 2 +- .../ExtractorTests/TimeOutTests.cs | 6 +++--- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs b/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs index a649bd2..a2d633a 100644 --- a/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs +++ b/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs @@ -12,7 +12,7 @@ namespace RecursiveExtractor.Tests.CliTests { - public class CliTests : BaseExtractorTestClass + public class CliTests : IClassFixture { [Theory] [InlineData("TestData.zip", 5)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs index b493c3a..2d107ba 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs @@ -1,10 +1,15 @@ -using NLog; +using Microsoft.CST.RecursiveExtractor.Tests; +using NLog; using NLog.Config; using NLog.Targets; +using System; namespace RecursiveExtractor.Tests.ExtractorTests; -public class BaseExtractorTestClass +/// +/// XUnit test fixture class for extractor tests. Sets up logging and test directories. Tests should inherit from this class to get the benefits of the setup and teardown. +/// +public class BaseExtractorTestClass : IDisposable { protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); @@ -20,4 +25,11 @@ static BaseExtractorTestClass() LogManager.Configuration = config; } + + // + public void Dispose() + { + TestPathHelpers.DeleteTestDirectory(); + GC.SuppressFinalize(this); + } } \ No newline at end of file diff --git a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs index d1bbd34..f54895f 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs @@ -8,7 +8,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class DisposeBehaviorTests : BaseExtractorTestClass +public class DisposeBehaviorTests : IClassFixture { [Theory] [InlineData("TestData.7z", 3, false)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs index 5b960d9..2c28668 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs @@ -8,7 +8,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class EncryptedArchiveTests : BaseExtractorTestClass +public class EncryptedArchiveTests : IClassFixture { [Theory] [InlineData("TestDataEncryptedZipCrypto.zip")] diff --git a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs index dec4ef3..b77fc17 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs @@ -11,7 +11,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests { - public class ExpectedNumFilesTests : BaseExtractorTestClass + public class ExpectedNumFilesTests : IClassFixture { /// diff --git a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs index 30e9d86..9fe6d67 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs @@ -7,7 +7,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class FilterTests : BaseExtractorTestClass +public class FilterTests : IClassFixture { [Theory] [InlineData("TestData.zip")] diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs index 46a26d7..6b7f15b 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs @@ -4,7 +4,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class MiniMagicTests : BaseExtractorTestClass +public class MiniMagicTests : IClassFixture { [Theory] [InlineData("TestData.zip", ArchiveFileType.ZIP)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs index d9416cf..78032b6 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs @@ -9,7 +9,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class TestQuinesAndSlip : BaseExtractorTestClass +public class TestQuinesAndSlip : IClassFixture { public static IEnumerable ZipSlipNames { diff --git a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs index 5537a21..a01aac0 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs @@ -1,4 +1,4 @@ -using Microsoft.CST.RecursiveExtractor; +using Microsoft.CST.RecursiveExtractor; using System; using System.IO; using System.Threading.Tasks; @@ -6,7 +6,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class TimeOutTests : BaseExtractorTestClass +public class TimeOutTests : IClassFixture { [Theory] [InlineData("TestData.7z", false)] @@ -115,4 +115,4 @@ await Assert.ThrowsAsync(async () => Assert.Fail("Should have thrown TimeoutException"); }); } -} \ No newline at end of file +} From 5ce022dcdd692223e7dca8efa9b8fb32a71ed449 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 22:47:47 -0800 Subject: [PATCH 21/28] Use shared test collection fixture for extractor tests Replaced individual class fixtures with a shared ExtractorTestCollection using Xunit's [CollectionDefinition] and ICollectionFixture. This ensures all extractor test classes share a single fixture instance, preventing race conditions and premature deletion of shared resources during parallel test execution. --- .../ExtractorTests/DisposeBehaviorTests.cs | 3 ++- .../ExtractorTests/EncryptedArchiveTests.cs | 3 ++- .../ExtractorTests/ExpectedNumFilesTests.cs | 3 ++- .../ExtractorTests/ExtractorTestCollection.cs | 16 ++++++++++++++++ .../ExtractorTests/FilterTests.cs | 3 ++- .../ExtractorTests/MiniMagicTests.cs | 3 ++- .../ExtractorTests/TestQuinesAndSlip.cs | 3 ++- .../ExtractorTests/TimeOutTests.cs | 3 ++- 8 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 RecursiveExtractor.Tests/ExtractorTests/ExtractorTestCollection.cs diff --git a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs index f54895f..4d7a627 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs @@ -8,7 +8,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class DisposeBehaviorTests : IClassFixture +[Collection(ExtractorTestCollection.Name)] +public class DisposeBehaviorTests { [Theory] [InlineData("TestData.7z", 3, false)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs index 2c28668..167ce78 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs @@ -8,7 +8,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class EncryptedArchiveTests : IClassFixture +[Collection(ExtractorTestCollection.Name)] +public class EncryptedArchiveTests { [Theory] [InlineData("TestDataEncryptedZipCrypto.zip")] diff --git a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs index b77fc17..7535716 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/ExpectedNumFilesTests.cs @@ -11,7 +11,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests { - public class ExpectedNumFilesTests : IClassFixture + [Collection(ExtractorTestCollection.Name)] + public class ExpectedNumFilesTests { /// diff --git a/RecursiveExtractor.Tests/ExtractorTests/ExtractorTestCollection.cs b/RecursiveExtractor.Tests/ExtractorTests/ExtractorTestCollection.cs new file mode 100644 index 0000000..08796b5 --- /dev/null +++ b/RecursiveExtractor.Tests/ExtractorTests/ExtractorTestCollection.cs @@ -0,0 +1,16 @@ +using Xunit; + +namespace RecursiveExtractor.Tests.ExtractorTests; + +/// +/// Defines a shared test collection so that all extractor test classes share a single +/// fixture instance. The fixture is created once +/// before the first test runs and disposed once after the last test completes, +/// avoiding the race condition where parallel class-level fixtures prematurely +/// delete the shared temp directory while other classes are still running. +/// +[CollectionDefinition(Name)] +public class ExtractorTestCollection : ICollectionFixture +{ + public const string Name = "Extractor Tests"; +} diff --git a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs index 9fe6d67..4aaad2b 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs @@ -7,7 +7,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class FilterTests : IClassFixture +[Collection(ExtractorTestCollection.Name)] +public class FilterTests { [Theory] [InlineData("TestData.zip")] diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs index 6b7f15b..b1f6095 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs @@ -4,7 +4,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class MiniMagicTests : IClassFixture +[Collection(ExtractorTestCollection.Name)] +public class MiniMagicTests { [Theory] [InlineData("TestData.zip", ArchiveFileType.ZIP)] diff --git a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs index 78032b6..42a4bb6 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs @@ -9,7 +9,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class TestQuinesAndSlip : IClassFixture +[Collection(ExtractorTestCollection.Name)] +public class TestQuinesAndSlip { public static IEnumerable ZipSlipNames { diff --git a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs index a01aac0..3ed075b 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs @@ -6,7 +6,8 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -public class TimeOutTests : IClassFixture +[Collection(ExtractorTestCollection.Name)] +public class TimeOutTests { [Theory] [InlineData("TestData.7z", false)] From 2eb47a3dde37e0584b536dca9c3c7dc33f672a01 Mon Sep 17 00:00:00 2001 From: Giulia Stocco <98900+gfs@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:48:08 -0800 Subject: [PATCH 22/28] Update RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ExtractorTests/BaseExtractorTestClass.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs index 2d107ba..3345bd8 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs @@ -26,7 +26,7 @@ static BaseExtractorTestClass() LogManager.Configuration = config; } - // + /// public void Dispose() { TestPathHelpers.DeleteTestDirectory(); From 04abdc0828d4e1a666fa98ca7086e46e9b7bb38a Mon Sep 17 00:00:00 2001 From: Giulia Stocco <98900+gfs@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:48:19 -0800 Subject: [PATCH 23/28] Update RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ExtractorTests/BaseExtractorTestClass.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs index 3345bd8..3c6ad5e 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs @@ -7,7 +7,7 @@ namespace RecursiveExtractor.Tests.ExtractorTests; /// -/// XUnit test fixture class for extractor tests. Sets up logging and test directories. Tests should inherit from this class to get the benefits of the setup and teardown. +/// XUnit test fixture class for extractor tests. Sets up logging and test directories. Tests should use this class as a fixture via IClassFixture<BaseExtractorTestClass> to get the benefits of the setup and teardown. /// public class BaseExtractorTestClass : IDisposable { From 89b5e72123f689ca4752866870311c5087e33cae Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 22:51:52 -0800 Subject: [PATCH 24/28] Refactor test data to use TheoryData in xUnit Replaced IEnumerable with TheoryData for ZipSlipNames and QuineBombNames, improving type safety and clarity in parameterized tests. Removed unused using directives for cleaner code. --- .../ExtractorTests/TestQuinesAndSlip.cs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs index 42a4bb6..4f57182 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs @@ -1,7 +1,5 @@ using Microsoft.CST.RecursiveExtractor; -using SharpCompress.Archives.Tar; using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -12,16 +10,16 @@ namespace RecursiveExtractor.Tests.ExtractorTests; [Collection(ExtractorTestCollection.Name)] public class TestQuinesAndSlip { - public static IEnumerable ZipSlipNames + public static TheoryData ZipSlipNames { get { - return new[] + return new TheoryData { - new object [] { "zip-slip-win.zip" }, - new object [] { "zip-slip-win.tar" }, - new object [] { "zip-slip.zip" }, - new object [] { "zip-slip.tar" } + { "zip-slip-win.zip" }, + { "zip-slip-win.tar" }, + { "zip-slip.zip" }, + { "zip-slip.tar" } }; } } @@ -46,19 +44,19 @@ public async Task TestZipSlipAsync(string fileName) Assert.True(results.All(x => !x.FullPath.Contains(".."))); } - public static IEnumerable QuineBombNames + public static TheoryData QuineBombNames { get { - return new[] + return new TheoryData { - new object [] { "10GB.7z.bz2" }, - new object [] { "10GB.gz.bz2" }, - new object [] { "10GB.rar.bz2" }, - new object [] { "10GB.xz.bz2" }, - new object [] { "10GB.zip.bz2" }, - new object [] { "zblg.zip" }, - new object [] { "zbsm.zip" } + { "10GB.7z.bz2" }, + { "10GB.gz.bz2" }, + { "10GB.rar.bz2" }, + { "10GB.xz.bz2" }, + { "10GB.zip.bz2" }, + { "zblg.zip" }, + { "zbsm.zip" } }; } } From 129bb9ed2ed5b1d05a5e93cfd2810d82bffd3322 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 23:00:28 -0800 Subject: [PATCH 25/28] Suppress CS1685 warning and remove DiscUtils packages Suppressed CS1685 warning in the test project due to a RuntimeHelpers polyfill conflict on net48. Removed DiscUtils Btrfs, HfsPlus, SquashFs, and Xfs package references from the test project - they are already imported when needed in the main project which is then imported into the test project to test it --- RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj b/RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj index d1300e5..5b57be5 100644 --- a/RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj +++ b/RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj @@ -5,13 +5,11 @@ false enable 10.0 + + $(NoWarn);CS1685 - - - - From d43b4c07a52f4abd65ea9826c732b2bc3af13534 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 23:06:47 -0800 Subject: [PATCH 26/28] Remove shared test collection from several test classes Removed the [Collection(ExtractorTestCollection.Name)] attribute from EncryptedArchiveTests, FilterTests, MiniMagicTests, TestQuinesAndSlip, and TimeOutTests. These test classes are no longer grouped in a shared test collection - the collection was used to protect against premature deletion of the temp directory but these tsts don't extract files to disc. --- RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs | 1 - RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs | 1 - RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs | 1 - RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs | 1 - RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs | 1 - 5 files changed, 5 deletions(-) diff --git a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs index 167ce78..50a5d00 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs @@ -8,7 +8,6 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -[Collection(ExtractorTestCollection.Name)] public class EncryptedArchiveTests { [Theory] diff --git a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs index 4aaad2b..a7442d0 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs @@ -7,7 +7,6 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -[Collection(ExtractorTestCollection.Name)] public class FilterTests { [Theory] diff --git a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs index b1f6095..20e2d95 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs @@ -4,7 +4,6 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -[Collection(ExtractorTestCollection.Name)] public class MiniMagicTests { [Theory] diff --git a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs index 4f57182..7182376 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs @@ -7,7 +7,6 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -[Collection(ExtractorTestCollection.Name)] public class TestQuinesAndSlip { public static TheoryData ZipSlipNames diff --git a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs index 3ed075b..980922c 100644 --- a/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs +++ b/RecursiveExtractor.Tests/ExtractorTests/TimeOutTests.cs @@ -6,7 +6,6 @@ namespace RecursiveExtractor.Tests.ExtractorTests; -[Collection(ExtractorTestCollection.Name)] public class TimeOutTests { [Theory] From 01357d3a367d73a7bf765800ea9a207b5b3a1ad1 Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 23:15:54 -0800 Subject: [PATCH 27/28] Remove Index and Range polyfills; use Bcl.Memory Index and Range polyfills were removed since they are now provided by Microsoft.Bcl.Memory as a transitive dependency. The file retains only the RuntimeHelpers.GetSubArray polyfill for array range syntax support on netstandard2.0. --- RecursiveExtractor/Range.cs | 235 +----------------------------------- 1 file changed, 2 insertions(+), 233 deletions(-) diff --git a/RecursiveExtractor/Range.cs b/RecursiveExtractor/Range.cs index ac5b214..1863866 100644 --- a/RecursiveExtractor/Range.cs +++ b/RecursiveExtractor/Range.cs @@ -1,238 +1,7 @@ -// https://github.com/dotnet/corefx/blob/1597b894a2e9cac668ce6e484506eca778a85197/src/Common/src/CoreLib/System/Index.cs -// https://github.com/dotnet/corefx/blob/1597b894a2e9cac668ce6e484506eca778a85197/src/Common/src/CoreLib/System/Range.cs +// Polyfill for RuntimeHelpers.GetSubArray needed by the C# compiler for array range syntax on netstandard2.0. +// Index and Range types are now provided by Microsoft.Bcl.Memory (transitive dependency). #if NETSTANDARD2_0 -using System.Runtime.CompilerServices; - -namespace System -{ - /// Represent a type can be used to index a collection either from the start or the end. - /// - /// Index is used by the C# compiler to support the new index syntax - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; - /// int lastElement = someArray[^1]; // lastElement = 5 - /// - /// - internal readonly struct Index : IEquatable - { - private readonly int _value; - - /// Construct an Index using a value and indicating if the index is from the start or from the end. - /// The index value. it has to be zero or positive number. - /// Indicating if the index is from the start or from the end. - /// - /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Index(int value, bool fromEnd = false) - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - - if (fromEnd) - _value = ~value; - else - _value = value; - } - - // The following private constructors mainly created for perf reason to avoid the checks - private Index(int value) - { - _value = value; - } - - /// Create an Index pointing at first element. - public static Index Start => new Index(0); - - /// Create an Index pointing at beyond last element. - public static Index End => new Index(~0); - - /// Create an Index from the start at the position indicated by the value. - /// The index value from the start. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromStart(int value) - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - - return new Index(value); - } - - /// Create an Index from the end at the position indicated by the value. - /// The index value from the end. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromEnd(int value) - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - - return new Index(~value); - } - - /// Returns the index value. - public int Value - { - get - { - if (_value < 0) - { - return ~_value; - } - else - { - return _value; - } - } - } - - /// Indicates whether the index is from the start or the end. - public bool IsFromEnd => _value < 0; - - /// Calculate the offset from the start using the giving collection length. - /// The length of the collection that the Index will be used with. length has to be a positive value - /// - /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. - /// we don't validate either the returned offset is greater than the input length. - /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and - /// then used to index a collection will get out of range exception which will be same affect as the validation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetOffset(int length) - { - var offset = _value; - if (IsFromEnd) - { - // offset = length - (~value) - // offset = length + (~(~value) + 1) - // offset = length + value + 1 - - offset += length + 1; - } - return offset; - } - - /// Indicates whether the current Index object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; - - /// Indicates whether the current Index object is equal to another Index object. - /// An object to compare with this object - public bool Equals(Index other) => _value == other._value; - - /// Returns the hash code for this instance. - public override int GetHashCode() => _value; - - /// Converts integer number to an Index. - public static implicit operator Index(int value) => FromStart(value); - - /// Converts the value of the current Index object to its equivalent string representation. - public override string ToString() - { - if (IsFromEnd) - return "^" + ((uint)Value).ToString(); - - return ((uint)Value).ToString(); - } - } - - /// Represent a range has start and end indexes. - /// - /// Range is used by the C# compiler to support the range syntax. - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; - /// int[] subArray1 = someArray[0..2]; // { 1, 2 } - /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } - /// - /// - internal readonly struct Range : IEquatable - { - /// Represent the inclusive start index of the Range. - public Index Start { get; } - - /// Represent the exclusive end index of the Range. - public Index End { get; } - - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } - - /// Indicates whether the current Range object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => - value is Range r && - r.Start.Equals(Start) && - r.End.Equals(End); - - /// Indicates whether the current Range object is equal to another Range object. - /// An object to compare with this object - public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); - - /// Returns the hash code for this instance. - public override int GetHashCode() - { - return Start.GetHashCode() * 31 + End.GetHashCode(); - } - - /// Converts the value of the current Range object to its equivalent string representation. - public override string ToString() - { - return Start + ".." + End; - } - - /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); - - /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); - - /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); - - /// Calculate the start offset and length of range object using a collection length. - /// The length of the collection that the range will be used with. length has to be a positive value. - /// - /// For performance reason, we don't validate the input length parameter against negative values. - /// It is expected Range will be used with collections which always have non negative length/count. - /// We validate the range is inside the length scope though. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Offset, int Length) GetOffsetAndLength(int length) - { - int start; - var startIndex = Start; - if (startIndex.IsFromEnd) - start = length - startIndex.Value; - else - start = startIndex.Value; - - int end; - var endIndex = End; - if (endIndex.IsFromEnd) - end = length - endIndex.Value; - else - end = endIndex.Value; - - if ((uint)end > (uint)length || (uint)start > (uint)end) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - return (start, end - start); - } - } -} namespace System.Runtime.CompilerServices { From 2dd6e2d4e714219138605172f9e0cfd0ad90433f Mon Sep 17 00:00:00 2001 From: Giulia Stocco Date: Thu, 5 Feb 2026 23:19:55 -0800 Subject: [PATCH 28/28] Add null-forgiving operator to name in TarExtractor Added the null-forgiving operator (`!`) to the `name` variable in the `while` loop condition to suppress potential null reference warnings. `name` is expected to be non-null at this point in the code becauses of the previous IsNullorEmpty check - however that isn't tagged on netstandard2.0. --- RecursiveExtractor/Extractors/TarExtractor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RecursiveExtractor/Extractors/TarExtractor.cs b/RecursiveExtractor/Extractors/TarExtractor.cs index 8553da0..9de5179 100644 --- a/RecursiveExtractor/Extractors/TarExtractor.cs +++ b/RecursiveExtractor/Extractors/TarExtractor.cs @@ -70,7 +70,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra continue; } // Remove leading ./ - while (name.StartsWith($".{Path.DirectorySeparatorChar}")) + while (name!.StartsWith($".{Path.DirectorySeparatorChar}")) { name = name[2..]; } @@ -140,7 +140,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti continue; } // Remove leading ./ - while (name.StartsWith($".{Path.DirectorySeparatorChar}")) + while (name!.StartsWith($".{Path.DirectorySeparatorChar}")) { name = name[2..]; }