From eb75e9e98d7f6dbf307bb953efdf2b14d0a9459f Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Wed, 17 Dec 2025 10:17:38 -0800 Subject: [PATCH] Add support for .snupkg --- README.md | 18 ++++++--- Tests/Verify.Nupkg.Tests/ModuleInitializer.cs | 5 ++- .../SamplePackages/PackageCreator.cs | 40 +++++++++++++++++++ .../SamplePackages/SamplePackages.cs | 2 + .../SimplePackageWithSymbols.cs | 29 ++++++++++++++ ...ests.BasicSnupkgTest#contents.verified.txt | 5 +++ ...s.BasicSnupkgTest#manifest.verified.nuspec | 14 +++++++ ...tomFileExclusionTest#contents.verified.txt | 7 ++++ ...FileExclusionTest#manifest.verified.nuspec | 14 +++++++ ...nlyOptInScrubbersRun#contents.verified.txt | 5 +++ ...OptInScrubbersRun#manifest.verified.nuspec | 14 +++++++ Tests/Verify.Nupkg.Tests/SnupkgTests.cs | 34 ++++++++++++++++ .../Verify.Nupkg.Tests.csproj | 10 +++++ Verify.Nupkg/VerifyNupkg.cs | 38 ++++++++++-------- 14 files changed, 211 insertions(+), 24 deletions(-) create mode 100644 Tests/Verify.Nupkg.Tests/SamplePackages/SimplePackageWithSymbols.cs create mode 100644 Tests/Verify.Nupkg.Tests/SnupkgTests.BasicSnupkgTest#contents.verified.txt create mode 100644 Tests/Verify.Nupkg.Tests/SnupkgTests.BasicSnupkgTest#manifest.verified.nuspec create mode 100644 Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithCustomFileExclusionTest#contents.verified.txt create mode 100644 Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithCustomFileExclusionTest#manifest.verified.nuspec create mode 100644 Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithOnlyOptInScrubbersRun#contents.verified.txt create mode 100644 Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithOnlyOptInScrubbersRun#manifest.verified.nuspec create mode 100644 Tests/Verify.Nupkg.Tests/SnupkgTests.cs diff --git a/README.md b/README.md index fdf2921..3120f33 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ![Nuget](https://img.shields.io/nuget/v/Verify.Nupkg) [![Downloads](https://img.shields.io/nuget/dt/Verify.Nupkg)](https://nuget.org/packages/Verify.Nupkg) -Extends [Verify](https://github.com/VerifyTests/Verify) to allow verification of [NuGet .nupkg](https://learn.microsoft.com/en-us/nuget/what-is-nuget) files. +Extends [Verify](https://github.com/VerifyTests/Verify) to allow verification of [NuGet .nupkg](https://learn.microsoft.com/en-us/nuget/what-is-nuget) and [.snupkg (symbol package)](https://learn.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg) files. The plugin does not do a naive binary comparison, as that would cause a large amount of verification churn. Instead, the contents of the .nuspec file are verified, along with a tree view of the package files. @@ -50,8 +50,7 @@ If you want to follow packing best practices (validating a README, reproducible ```csharp [ModuleInitializer] -public static void Initialize() => - VerifyNupkg.Initialize(); +public static void Initialize() => VerifyNupkg.Initialize(); ``` ### File path @@ -60,7 +59,7 @@ public static void Initialize() => [Fact] public Task VerifyNupkgFile() { - string packagePath = "path/to/package.nupkg"; + string packagePath = "path/to/package.nupkg"; // or .snupkg VerifySettings settings = new(); settings.UseUniqueDirectory(); // Optional; group files into a directory @@ -96,8 +95,15 @@ VerifySettings settings = new(); settings.ScrubNuspec(); ``` -which itself is a convenience method for `ScrubNuspecVersion()` and `ScrubNuspecCommit()`. Feel free to use them -separately if you'd like to verify either of these values. +which itself is a convenience method for these scrubbers: + +- `ScrubNuspecVersion()` +- `ScrubNuspecCommit()` +- `ScrubNuspecSchema()` +- `ScrubNuspecRepositoryUrl()` +- `ScrubNuspecBranch()` + +Feel free to use them separately if you'd like to verify any of these values. ### Referencing / locating a package built in the same solution diff --git a/Tests/Verify.Nupkg.Tests/ModuleInitializer.cs b/Tests/Verify.Nupkg.Tests/ModuleInitializer.cs index 5331dd2..977f45e 100644 --- a/Tests/Verify.Nupkg.Tests/ModuleInitializer.cs +++ b/Tests/Verify.Nupkg.Tests/ModuleInitializer.cs @@ -1,5 +1,6 @@ -using Microsoft.Build.Utilities.ProjectCreation; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; + +using Microsoft.Build.Utilities.ProjectCreation; namespace Verify.Nupkg.Tests; diff --git a/Tests/Verify.Nupkg.Tests/SamplePackages/PackageCreator.cs b/Tests/Verify.Nupkg.Tests/SamplePackages/PackageCreator.cs index 6f8eb45..237e61a 100644 --- a/Tests/Verify.Nupkg.Tests/SamplePackages/PackageCreator.cs +++ b/Tests/Verify.Nupkg.Tests/SamplePackages/PackageCreator.cs @@ -67,4 +67,44 @@ public IFileInfo Create(IDirectoryInfo destinationDirectory) return destinationPackage; } + + /// + /// Creates the package with symbols in a temp directory and copies both .nupkg and .snupkg to the specified destination directory. + /// + /// The directory to copy the packages to. + /// A tuple containing for the .nupkg and .snupkg files. + /// If the build fails. + public (IFileInfo Nupkg, IFileInfo Snupkg) CreateWithSymbols(IDirectoryInfo destinationDirectory) + { + IFileInfo destinationPackage = _fs.FileInfo.New(_fs.Path.Combine(destinationDirectory.FullName, $"{Name}.nupkg")); + IFileInfo destinationSymbolPackage = _fs.FileInfo.New(_fs.Path.Combine(destinationDirectory.FullName, $"{Name}.snupkg")); + + using (_fs.CreateDisposableDirectory(RetryableTempDirectory.GetRandomTempPath(), dirInfo => new RetryableTempDirectory(dirInfo), out IDirectoryInfo temp)) + { + using (PackageRepository.Create(temp.FullName, feeds: new Uri("https://api.nuget.org/v3/index.json"))) + { + CreateCore(temp) + .Target(name: "CopyPackageForTests", afterTargets: "Pack") + .Task(name: "Copy", parameters: new Dictionary + { + { "SourceFiles", @"$(OutputPath)..\$(PackageId).$(PackageVersion).nupkg" }, + { "DestinationFiles", destinationPackage.FullName }, + }) + .Task(name: "Copy", parameters: new Dictionary + { + { "SourceFiles", @"$(OutputPath)..\$(PackageId).$(PackageVersion).snupkg" }, + { "DestinationFiles", destinationSymbolPackage.FullName }, + }) + .Save(_fs.Path.Combine(temp.FullName, $"{Name}.csproj")) + .TryBuild(restore: true, target: "Pack", out bool result, out BuildOutput buildOutput, out IDictionary? outputs); + + if (!result) + { + throw new Exception($"Failed to build in path '{temp.FullName}'. Errors: {string.Join(Environment.NewLine, buildOutput.Errors)}"); + } + } + } + + return (destinationPackage, destinationSymbolPackage); + } } diff --git a/Tests/Verify.Nupkg.Tests/SamplePackages/SamplePackages.cs b/Tests/Verify.Nupkg.Tests/SamplePackages/SamplePackages.cs index e032881..f340af3 100644 --- a/Tests/Verify.Nupkg.Tests/SamplePackages/SamplePackages.cs +++ b/Tests/Verify.Nupkg.Tests/SamplePackages/SamplePackages.cs @@ -19,6 +19,7 @@ internal class SamplePackages public Lazy PackageWithoutRepoUrl { get; private set; } public Lazy PackageWithoutRepoCommit { get; private set; } public Lazy PackageWithoutRepoBranch { get; private set; } + public Lazy<(IFileInfo Nupkg, IFileInfo Snupkg)> SimplePackageWithSymbols { get; private set; } private SamplePackages() { @@ -32,6 +33,7 @@ private SamplePackages() PackageWithoutRepoUrl = new Lazy(() => new PackageWithoutRepoUrlOrCommitOrBranch().Create(workingDirectory)); PackageWithoutRepoCommit = new Lazy(() => new PackageWithoutRepoUrlOrCommitOrBranch().Create(workingDirectory)); PackageWithoutRepoBranch = new Lazy(() => new PackageWithoutRepoUrlOrCommitOrBranch().Create(workingDirectory)); + SimplePackageWithSymbols = new Lazy<(IFileInfo, IFileInfo)>(() => new SimplePackageWithSymbols().CreateWithSymbols(workingDirectory)); } private IDirectoryInfo GetWorkingDirectory() diff --git a/Tests/Verify.Nupkg.Tests/SamplePackages/SimplePackageWithSymbols.cs b/Tests/Verify.Nupkg.Tests/SamplePackages/SimplePackageWithSymbols.cs new file mode 100644 index 0000000..f1d19f5 --- /dev/null +++ b/Tests/Verify.Nupkg.Tests/SamplePackages/SimplePackageWithSymbols.cs @@ -0,0 +1,29 @@ +using Microsoft.Build.Utilities.ProjectCreation; +using System.IO.Abstractions; + +namespace Verify.Nupkg.Tests; + +internal class SimplePackageWithSymbols : PackageCreator +{ + public override string Name => GetType().Name; + + protected override ProjectCreator CreateCore(IDirectoryInfo workingDirectory) + { + File.WriteAllText(Path.Combine(workingDirectory.FullName, "README.md"), "Hello, World!"); + + return ProjectCreator.Templates.SdkCsproj() + .Property("TargetFramework", "net8.0") + .Property("PackageReadmeFile", "README.md") + .Property("RepositoryType", "git") + .Property("RepositoryUrl", "https://github.com/MattKotsenas/Verify.Nupkg") + .Property("RepositoryCommit", "0e4d1b598f350b3dc675018d539114d1328189ef") + .Property("RepositoryBranch", "dev") + .Property("IncludeSymbols", "true") + .Property("SymbolPackageFormat", "snupkg") + .ItemNone(include: "README.md", metadata: new Dictionary + { + { "Pack", "true" }, + { "PackagePath", "\\" }, + }); + } +} diff --git a/Tests/Verify.Nupkg.Tests/SnupkgTests.BasicSnupkgTest#contents.verified.txt b/Tests/Verify.Nupkg.Tests/SnupkgTests.BasicSnupkgTest#contents.verified.txt new file mode 100644 index 0000000..1971b67 --- /dev/null +++ b/Tests/Verify.Nupkg.Tests/SnupkgTests.BasicSnupkgTest#contents.verified.txt @@ -0,0 +1,5 @@ +/ +|-- SimplePackageWithSymbols.nuspec +|-- lib +| |-- net8.0 +| | |-- SimplePackageWithSymbols.pdb diff --git a/Tests/Verify.Nupkg.Tests/SnupkgTests.BasicSnupkgTest#manifest.verified.nuspec b/Tests/Verify.Nupkg.Tests/SnupkgTests.BasicSnupkgTest#manifest.verified.nuspec new file mode 100644 index 0000000..9c0e2e0 --- /dev/null +++ b/Tests/Verify.Nupkg.Tests/SnupkgTests.BasicSnupkgTest#manifest.verified.nuspec @@ -0,0 +1,14 @@ + + + SimplePackageWithSymbols + ******** + Package Description + + + + + + + + + \ No newline at end of file diff --git a/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithCustomFileExclusionTest#contents.verified.txt b/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithCustomFileExclusionTest#contents.verified.txt new file mode 100644 index 0000000..e097f84 --- /dev/null +++ b/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithCustomFileExclusionTest#contents.verified.txt @@ -0,0 +1,7 @@ +/ +|-- [Content_Types].xml +|-- _rels +| |-- .rels +|-- lib +| |-- net8.0 +| | |-- SimplePackageWithSymbols.pdb diff --git a/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithCustomFileExclusionTest#manifest.verified.nuspec b/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithCustomFileExclusionTest#manifest.verified.nuspec new file mode 100644 index 0000000..9c0e2e0 --- /dev/null +++ b/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithCustomFileExclusionTest#manifest.verified.nuspec @@ -0,0 +1,14 @@ + + + SimplePackageWithSymbols + ******** + Package Description + + + + + + + + + \ No newline at end of file diff --git a/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithOnlyOptInScrubbersRun#contents.verified.txt b/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithOnlyOptInScrubbersRun#contents.verified.txt new file mode 100644 index 0000000..1971b67 --- /dev/null +++ b/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithOnlyOptInScrubbersRun#contents.verified.txt @@ -0,0 +1,5 @@ +/ +|-- SimplePackageWithSymbols.nuspec +|-- lib +| |-- net8.0 +| | |-- SimplePackageWithSymbols.pdb diff --git a/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithOnlyOptInScrubbersRun#manifest.verified.nuspec b/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithOnlyOptInScrubbersRun#manifest.verified.nuspec new file mode 100644 index 0000000..ca17b91 --- /dev/null +++ b/Tests/Verify.Nupkg.Tests/SnupkgTests.SnupkgWithOnlyOptInScrubbersRun#manifest.verified.nuspec @@ -0,0 +1,14 @@ + + + SimplePackageWithSymbols + 1.0.0 + Package Description + + + + + + + + + \ No newline at end of file diff --git a/Tests/Verify.Nupkg.Tests/SnupkgTests.cs b/Tests/Verify.Nupkg.Tests/SnupkgTests.cs new file mode 100644 index 0000000..589ee47 --- /dev/null +++ b/Tests/Verify.Nupkg.Tests/SnupkgTests.cs @@ -0,0 +1,34 @@ +namespace Verify.Nupkg.Tests; + +[TestClass] +public partial class SnupkgTests +{ + private readonly string _snupkg = SamplePackages.Instance.SimplePackageWithSymbols.Value.Snupkg.FullName; + + [TestMethod] + public Task BasicSnupkgTest() + { + return VerifyFile(_snupkg).ScrubNuspec(); + } + + [TestMethod] + public Task SnupkgWithCustomFileExclusionTest() + { + return VerifyFile(_snupkg) + .ScrubNuspec() + .AddNupkgDiffSettings(settings => settings.ExcludedFiles = [new(@"\.psmdcp$"), new(@"\.nuspec$")]); + } + + [TestMethod] + public Task SnupkgWithOnlyOptInScrubbersRun() + { + // In this test we intentionally _do not_ include these scrubbers: + // - Version + // - Schema + // to validate that only scrubbers we opt-in to are applied. + return VerifyFile(_snupkg) + .ScrubNuspecCommit() + .ScrubNuspecRepositoryUrl() + .ScrubNuspecBranch(); + } +} diff --git a/Tests/Verify.Nupkg.Tests/Verify.Nupkg.Tests.csproj b/Tests/Verify.Nupkg.Tests/Verify.Nupkg.Tests.csproj index 64740b4..c2d39ff 100644 --- a/Tests/Verify.Nupkg.Tests/Verify.Nupkg.Tests.csproj +++ b/Tests/Verify.Nupkg.Tests/Verify.Nupkg.Tests.csproj @@ -6,6 +6,8 @@ $(TargetFrameworks);net8.0;net9.0 false true + + $(NoWarn);NU1605;NU1109 @@ -18,6 +20,14 @@ + + + + + +