11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . Diagnostics . CodeAnalysis ;
45using System . IO . Compression ;
6+ using NuGet . Packaging ;
7+ using NuGet . Packaging . Core ;
58
69namespace Microsoft . DotNet . PackageInstall . Tests
710{
@@ -312,35 +315,116 @@ .. expectedRids.Select(rid => $"{toolSettings.ToolPackageId}.{rid}.{toolSettings
312315 . And . HaveStdOutContaining ( "Hello Tool!" ) ;
313316 }
314317
315- private void EnsurePackageIsFdd ( string packagePath )
318+ [ Fact ]
319+ public void StripsPackageTypesFromInnerToolPackages ( )
320+ {
321+ var toolSettings = new TestToolBuilder . TestToolSettings ( )
322+ {
323+ RidSpecific = true ,
324+ AdditionalPackageTypes = [ "TestPackageType" ]
325+ } ;
326+ string toolPackagesPath = ToolBuilder . CreateTestTool ( Log , toolSettings , collectBinlogs : true ) ;
327+
328+ var packages = Directory . GetFiles ( toolPackagesPath , "*.nupkg" ) ;
329+ var packageIdentifier = toolSettings . ToolPackageId ;
330+ var expectedRids = ToolsetInfo . LatestRuntimeIdentifiers . Split ( ';' ) ;
331+
332+ packages . Length . Should ( ) . Be ( expectedRids . Length + 1 , "There should be one package for the tool-wrapper and one for each RID" ) ;
333+ foreach ( string rid in expectedRids )
334+ {
335+ var packageName = $ "{ toolSettings . ToolPackageId } .{ rid } .{ toolSettings . ToolPackageVersion } ";
336+ var package = packages . FirstOrDefault ( p => p . EndsWith ( packageName + ".nupkg" ) ) ;
337+ package . Should ( )
338+ . NotBeNull ( $ "Package { packageName } should be present in the tool packages directory")
339+ . And . Satisfy < string > ( EnsurePackageIsAnExecutable )
340+ . And . Satisfy < string > ( EnsurePackageOnlyHasToolRidPackageType ) ;
341+ }
342+
343+ // top-level package should declare all of the rids
344+ var topLevelPackage = packages . First ( p => p . EndsWith ( $ "{ packageIdentifier } .{ toolSettings . ToolPackageVersion } .nupkg") ) ;
345+ topLevelPackage . Should ( ) . NotBeNull ( $ "Package { packageIdentifier } .{ toolSettings . ToolPackageVersion } .nupkg should be present in the tool packages directory")
346+ . And . Satisfy < string > ( EnsurePackageHasNoRunner )
347+ . And . Satisfy < string > ( EnsurePackageHasToolPackageTypeAnd ( toolSettings . AdditionalPackageTypes ! ) ) ;
348+ var foundRids = GetRidsInSettingsFile ( topLevelPackage ) ;
349+ foundRids . Should ( ) . BeEquivalentTo ( expectedRids , "The top-level package should declare all of the RIDs for the tools it contains" ) ;
350+ }
351+
352+ private Action < string > EnsurePackageHasToolPackageTypeAnd ( string [ ] additionalPackageTypes ) => ( string packagePath ) =>
353+ {
354+ var nuspec = GetPackageNuspec ( packagePath ) ;
355+ var packageTypes = nuspec . GetPackageTypes ( ) ;
356+ PackageType [ ] expectedPackageTypes = [ new PackageType ( "DotnetTool" , PackageType . EmptyVersion ) , .. additionalPackageTypes . Select ( t => new PackageType ( t , PackageType . EmptyVersion ) ) ] ;
357+ packageTypes . Should ( ) . NotBeNull ( "The PackageType element should not be null." )
358+ . And . HaveCount ( 1 + additionalPackageTypes . Length , "The package should have a PackageType element for each additional type." )
359+ . And . BeEquivalentTo ( expectedPackageTypes , "The PackageType should be 'DotnetTool'." ) ;
360+ } ;
361+
362+ static void EnsurePackageOnlyHasToolRidPackageType ( string packagePath )
363+ {
364+ var nuspec = GetPackageNuspec ( packagePath ) ;
365+ var packageTypes = nuspec . GetPackageTypes ( ) ;
366+ packageTypes . Should ( ) . NotBeNull ( "The PackageType element should not be null." )
367+ . And . HaveCount ( 1 , "The package should only have a single PackageType element." )
368+ . And . Contain ( new PackageType ( "DotnetToolRidPackage" , PackageType . EmptyVersion ) , "The PackageType should be 'DotnetToolRidPackage'." ) ;
369+ }
370+
371+ private static NuspecReader GetPackageNuspec ( string packagePath )
372+ {
373+ using var zipArchive = ZipFile . OpenRead ( packagePath ) ;
374+ var nuspecEntry = zipArchive . Entries . First ( e => e . Name . EndsWith ( "nuspec" ) ! ) ;
375+ var stream = nuspecEntry . Open ( ) ;
376+ return new NuspecReader ( stream ) ;
377+ }
378+
379+ static void EnsurePackageIsFdd ( string packagePath )
316380 {
317381 var settingsXml = GetToolSettingsFile ( packagePath ) ;
318382 var runner = GetRunnerFromSettingsFile ( settingsXml ) ;
319383 runner . Should ( ) . Be ( "dotnet" , "The tool should be packaged as a framework-dependent executable (FDD) with a 'dotnet' runner." ) ;
320384 }
321385
322- private void EnsurePackageIsAnExecutable ( string packagePath )
386+ static void EnsurePackageHasNoRunner ( string packagePath )
387+ {
388+ var settingsXml = GetToolSettingsFile ( packagePath ) ;
389+ if ( TryGetRunnerFromSettingsFile ( settingsXml , out _ ) )
390+ {
391+ throw new Exception ( "The tool settings file should not contain a 'Runner' attribute." ) ;
392+ }
393+ }
394+
395+ static void EnsurePackageIsAnExecutable ( string packagePath )
323396 {
324397 var settingsXml = GetToolSettingsFile ( packagePath ) ;
325398 var runner = GetRunnerFromSettingsFile ( settingsXml ) ;
326399 runner . Should ( ) . Be ( "executable" , "The tool should be packaged as a executable with an 'executable' runner." ) ;
327400 }
328401
329- private object GetRunnerFromSettingsFile ( XElement settingsXml )
402+ static string GetRunnerFromSettingsFile ( XElement settingsXml )
403+ {
404+ if ( TryGetRunnerFromSettingsFile ( settingsXml , out string ? runner ) )
405+ {
406+ return runner ;
407+ } else
408+ {
409+ throw new InvalidOperationException ( "The tool settings file does not contain a 'Runner' attribute." ) ;
410+ }
411+ }
412+ static bool TryGetRunnerFromSettingsFile ( XElement settingsXml , [ NotNullWhen ( true ) ] out string ? runner )
330413 {
331- return settingsXml . Elements ( "Commands" ) . First ( ) . Elements ( "Command" ) . First ( ) . Attribute ( "Runner" ) ? . Value
332- ?? throw new InvalidOperationException ( "The tool settings file does not contain a 'Runner' attribute." ) ;
414+ var commandNode = settingsXml . Elements ( "Commands" ) . First ( ) . Elements ( "Command" ) . First ( ) ;
415+ runner = commandNode . Attributes ( ) . FirstOrDefault ( a => a . Name == "Runner" ) ? . Value ;
416+ return runner is not null ;
333417 }
334418
335- private string [ ] GetRidsInSettingsFile ( string packagePath )
419+ static string [ ] GetRidsInSettingsFile ( string packagePath )
336420 {
337421 var settingsXml = GetToolSettingsFile ( packagePath ) ;
338422 var rids = GetRidsInSettingsFile ( settingsXml ) ;
339423 rids . Should ( ) . NotBeEmpty ( "The tool settings file should contain at least one RuntimeIdentifierPackage element." ) ;
340424 return rids ;
341425 }
342426
343- private string [ ] GetRidsInSettingsFile ( XElement settingsXml )
427+ static string [ ] GetRidsInSettingsFile ( XElement settingsXml )
344428 {
345429 var nodes = ( settingsXml . Nodes ( )
346430 . First ( n => n is XElement e && e . Name == "RuntimeIdentifierPackages" ) as XElement ) ! . Nodes ( )
@@ -350,7 +434,7 @@ private string[] GetRidsInSettingsFile(XElement settingsXml)
350434 return nodes ;
351435 }
352436
353- private XElement GetToolSettingsFile ( string packagePath )
437+ static XElement GetToolSettingsFile ( string packagePath )
354438 {
355439 using var zipArchive = ZipFile . OpenRead ( packagePath ) ;
356440 var nuspecEntry = zipArchive . Entries . First ( e => e . Name == "DotnetToolSettings.xml" ) ! ;
@@ -363,7 +447,7 @@ private XElement GetToolSettingsFile(string packagePath)
363447 /// <summary>
364448 /// Opens the nupkg and verifies that it does not contain a dependency on the given dll.
365449 /// </summary>
366- private void EnsurePackageLacksTrimmedDependency ( string packagePath , string dll )
450+ static void EnsurePackageLacksTrimmedDependency ( string packagePath , string dll )
367451 {
368452 using var zipArchive = ZipFile . OpenRead ( packagePath ) ;
369453 zipArchive . Entries . Should ( ) . NotContain (
0 commit comments