@@ -8,6 +8,7 @@ namespace Microsoft.ComponentDetection.Detectors.Tests;
88using System . Reflection ;
99using System . Runtime . InteropServices ;
1010using System . Text . Json ;
11+ using System . Text . RegularExpressions ;
1112using System . Threading ;
1213using System . Threading . Tasks ;
1314using FluentAssertions ;
@@ -62,7 +63,7 @@ public DotNetComponentDetectorTests()
6263 this . mockDirectoryUtilityService . Setup ( x => x . Exists ( It . IsAny < string > ( ) ) ) . Returns ( ( string p ) => this . DirectoryExists ( p ) ) ;
6364
6465 // ignore pattern and search option since we don't really need them for tests
65- this . mockDirectoryUtilityService . Setup ( x => x . EnumerateFiles ( It . IsAny < string > ( ) , It . IsAny < string > ( ) , It . IsAny < SearchOption > ( ) ) ) . Returns ( ( string d , string p , SearchOption s ) => this . EnumerateFilesRecursive ( d ) ) ;
66+ this . mockDirectoryUtilityService . Setup ( x => x . EnumerateFiles ( It . IsAny < string > ( ) , It . IsAny < string > ( ) , It . IsAny < SearchOption > ( ) ) ) . Returns ( ( string d , string p , SearchOption s ) => this . EnumerateFilesRecursive ( d , p ) ) ;
6667
6768 this . mockPathUtilityService . Setup ( x => x . NormalizePath ( It . IsAny < string > ( ) ) ) . Returns ( ( string p ) => p ) ; // don't do normalization
6869 this . mockPathUtilityService . Setup ( x => x . GetParentDirectory ( It . IsAny < string > ( ) ) ) . Returns ( ( string p ) => Path . GetDirectoryName ( p ) ) ;
@@ -91,24 +92,30 @@ private Stream OpenFile(string path)
9192
9293 private bool DirectoryExists ( string directory ) => this . files . ContainsKey ( directory ) ;
9394
94- private IEnumerable < string > EnumerateFilesRecursive ( string directory )
95+ private IEnumerable < string > EnumerateFilesRecursive ( string directory , string pattern )
9596 {
9697 if ( this . files . TryGetValue ( directory , out var fileNames ) )
9798 {
99+ // a basic approximation of globbing
100+ var patternRegex = new Regex ( pattern . Replace ( "." , "\\ ." ) . Replace ( "*" , ".*" ) ) ;
101+
98102 foreach ( var fileName in fileNames . Keys )
99103 {
100104 var filePath = Path . Combine ( directory , fileName ) ;
101105
102106 if ( fileName . EndsWith ( Path . DirectorySeparatorChar ) )
103107 {
104- foreach ( var subFile in this . EnumerateFilesRecursive ( Path . TrimEndingDirectorySeparator ( filePath ) ) )
108+ foreach ( var subFile in this . EnumerateFilesRecursive ( Path . TrimEndingDirectorySeparator ( filePath ) , pattern ) )
105109 {
106110 yield return subFile ;
107111 }
108112 }
109113 else
110114 {
111- yield return filePath ;
115+ if ( patternRegex . IsMatch ( fileName ) )
116+ {
117+ yield return filePath ;
118+ }
112119 }
113120 }
114121 }
@@ -267,6 +274,54 @@ public async Task TestDotNetDetectorGlobalJsonRollForward_ReturnsSDKVersion()
267274 discoveredComponents . Where ( component => component . Component . Id == "8.0.808 net8.0 unknown - DotNet" ) . Should ( ) . ContainSingle ( ) ;
268275 }
269276
277+ [ TestMethod ]
278+ public async Task TestDotNetDetectorGlobalJsonDotNetVersionFails_ReturnsSDKVersion ( )
279+ {
280+ var projectPath = Path . Combine ( RootDir , "path" , "to" , "project" ) ;
281+ var projectAssets = ProjectAssets ( "projectName" , "does-not-exist" , projectPath , "net8.0" ) ;
282+ var globalJson = GlobalJson ( "8.0.100" ) ;
283+ this . AddFile ( projectPath , null ) ;
284+ this . AddFile ( Path . Combine ( RootDir , "path" , "global.json" ) , globalJson ) ;
285+ this . SetCommandResult ( - 1 ) ;
286+
287+ var ( scanResult , componentRecorder ) = await this . DetectorTestUtility
288+ . WithFile ( "project.assets.json" , projectAssets )
289+ . ExecuteDetectorAsync ( ) ;
290+
291+ scanResult . ResultCode . Should ( ) . Be ( ProcessingResultCode . Success ) ;
292+
293+ var detectedComponents = componentRecorder . GetDetectedComponents ( ) ;
294+ detectedComponents . Should ( ) . HaveCount ( 2 ) ;
295+
296+ var discoveredComponents = detectedComponents . ToArray ( ) ;
297+ discoveredComponents . Where ( component => component . Component . Id == "8.0.100 unknown unknown - DotNet" ) . Should ( ) . ContainSingle ( ) ;
298+ discoveredComponents . Where ( component => component . Component . Id == "8.0.100 net8.0 unknown - DotNet" ) . Should ( ) . ContainSingle ( ) ;
299+ }
300+
301+ [ TestMethod ]
302+ public async Task TestDotNetDetectorGlobalJsonDotNetVersionThrows_ReturnsSDKVersion ( )
303+ {
304+ var projectPath = Path . Combine ( RootDir , "path" , "to" , "project" ) ;
305+ var projectAssets = ProjectAssets ( "projectName" , "does-not-exist" , projectPath , "net8.0" ) ;
306+ var globalJson = GlobalJson ( "8.0.100" ) ;
307+ this . AddFile ( projectPath , null ) ;
308+ this . AddFile ( Path . Combine ( RootDir , "path" , "global.json" ) , globalJson ) ;
309+ this . SetCommandResult ( ( c , d ) => throw new InvalidOperationException ( ) ) ;
310+
311+ var ( scanResult , componentRecorder ) = await this . DetectorTestUtility
312+ . WithFile ( "project.assets.json" , projectAssets )
313+ . ExecuteDetectorAsync ( ) ;
314+
315+ scanResult . ResultCode . Should ( ) . Be ( ProcessingResultCode . Success ) ;
316+
317+ var detectedComponents = componentRecorder . GetDetectedComponents ( ) ;
318+ detectedComponents . Should ( ) . HaveCount ( 2 ) ;
319+
320+ var discoveredComponents = detectedComponents . ToArray ( ) ;
321+ discoveredComponents . Where ( component => component . Component . Id == "8.0.100 unknown unknown - DotNet" ) . Should ( ) . ContainSingle ( ) ;
322+ discoveredComponents . Where ( component => component . Component . Id == "8.0.100 net8.0 unknown - DotNet" ) . Should ( ) . ContainSingle ( ) ;
323+ }
324+
270325 [ TestMethod ]
271326 public async Task TestDotNetDetectorNoGlobalJson_ReturnsDotNetVersion ( )
272327 {
@@ -388,6 +443,40 @@ public async Task TestDotNetDetectorMultipleProjectsWithDifferentOutputTypeAndSd
388443 discoveredComponents . Where ( component => component . Component . Id == "1.2.3 net48 application - DotNet" ) . Should ( ) . ContainSingle ( ) ;
389444 }
390445
446+ [ TestMethod ]
447+ public async Task TestDotNetDetectorExe ( )
448+ {
449+ // dotnet from global.json will be 4.5.6
450+ var globalJson = GlobalJson ( "4.5.6" ) ;
451+ var globalJsonDir = Path . Combine ( RootDir , "path" ) ;
452+ this . AddFile ( Path . Combine ( globalJsonDir , "global.json" ) , globalJson ) ;
453+
454+ this . SetCommandResult ( 0 , "4.5.6" ) ;
455+
456+ // set up an application - not under global.json
457+ var applicationProjectName = "application" ;
458+ var applicationProjectPath = Path . Combine ( RootDir , "path" , "to" , "project" , $ "{ applicationProjectName } .csproj") ;
459+ this . AddFile ( applicationProjectPath , null ) ;
460+ var applicationOutputPath = Path . Combine ( Path . GetDirectoryName ( applicationProjectPath ) , "obj" ) ;
461+ var applicationAssetsPath = Path . Combine ( applicationOutputPath , "project.assets.json" ) ;
462+ var applicationAssets = ProjectAssets ( "application" , applicationOutputPath , applicationProjectPath , "net4.8" ) ;
463+ var applicationAssemblyStream = File . OpenRead ( Assembly . GetEntryAssembly ( ) . Location ) ;
464+ this . AddFile ( Path . Combine ( applicationOutputPath , "Release" , "net4.8" , "application.exe" ) , applicationAssemblyStream ) ;
465+
466+ var ( scanResult , componentRecorder ) = await this . DetectorTestUtility
467+ . WithFile ( applicationAssetsPath , applicationAssets )
468+ . ExecuteDetectorAsync ( ) ;
469+
470+ scanResult . ResultCode . Should ( ) . Be ( ProcessingResultCode . Success ) ;
471+
472+ var detectedComponents = componentRecorder . GetDetectedComponents ( ) ;
473+ detectedComponents . Should ( ) . HaveCount ( 2 ) ;
474+
475+ var discoveredComponents = detectedComponents . ToArray ( ) ;
476+ discoveredComponents . Where ( component => component . Component . Id == "4.5.6 unknown unknown - DotNet" ) . Should ( ) . ContainSingle ( ) ;
477+ discoveredComponents . Where ( component => component . Component . Id == "4.5.6 net48 application - DotNet" ) . Should ( ) . ContainSingle ( ) ;
478+ }
479+
391480 [ TestMethod ]
392481 public async Task TestDotNetDetectorInvalidOutputAssembly ( )
393482 {
@@ -458,4 +547,52 @@ public async Task TestDotNetDetectorNoGlobalJsonSourceRoot()
458547 var discoveredComponents = detectedComponents . ToArray ( ) ;
459548 discoveredComponents . Where ( component => component . Component . Id == "0.0.0 net8.0 unknown - DotNet" ) . Should ( ) . ContainSingle ( ) ;
460549 }
550+
551+ [ TestMethod ]
552+ public async Task TestDotNetDetectorRebasePaths ( )
553+ {
554+ // DetectorTestUtility runs under Path.GetTempPath()
555+ var scanRoot = Path . TrimEndingDirectorySeparator ( Path . GetTempPath ( ) ) ;
556+
557+ // dotnet from global.json will be 4.5.6
558+ var globalJson = GlobalJson ( "4.5.6" ) ;
559+ var globalJsonDir = Path . Combine ( scanRoot , "path" ) ;
560+ this . AddFile ( Path . Combine ( globalJsonDir , "global.json" ) , globalJson ) ;
561+
562+ // make sure we find global.json and read it
563+ this . SetCommandResult ( - 1 ) ;
564+
565+ // set up a library project - under global.json
566+ var libraryProjectName = "library" ;
567+
568+ var libraryProjectPath = Path . Combine ( scanRoot , "path" , "to" , "project" , $ "{ libraryProjectName } .csproj") ;
569+ var libraryBuildProjectPath = Path . Combine ( RootDir , "path" , "to" , "project" , $ "{ libraryProjectName } .csproj") ;
570+ this . AddFile ( libraryProjectPath , null ) ;
571+
572+ var libraryOutputPath = Path . Combine ( Path . GetDirectoryName ( libraryProjectPath ) , "obj" ) ;
573+ var libraryBuildOutputPath = Path . Combine ( Path . GetDirectoryName ( libraryBuildProjectPath ) , "obj" ) ;
574+ var libraryAssetsPath = Path . Combine ( libraryOutputPath , "project.assets.json" ) ;
575+
576+ // use "build" paths to simulate an Assets file that has a different root. Here the build assets have RootDir, but the scanned filesystem has scanRoot.
577+ var libraryAssets = ProjectAssets ( "library" , libraryBuildOutputPath , libraryBuildProjectPath , "net8.0" , "net6.0" , "netstandard2.0" ) ;
578+ var libraryAssemblyStream = File . OpenRead ( typeof ( DotNetComponent ) . Assembly . Location ) ;
579+ this . AddFile ( Path . Combine ( libraryOutputPath , "Release" , "net8.0" , "library.dll" ) , libraryAssemblyStream ) ;
580+ this . AddFile ( Path . Combine ( libraryOutputPath , "Release" , "net6.0" , "library.dll" ) , libraryAssemblyStream ) ;
581+ this . AddFile ( Path . Combine ( libraryOutputPath , "Release" , "netstandard2.0" , "library.dll" ) , libraryAssemblyStream ) ;
582+
583+ var ( scanResult , componentRecorder ) = await this . DetectorTestUtility
584+ . WithFile ( libraryAssetsPath , libraryAssets )
585+ . ExecuteDetectorAsync ( ) ;
586+
587+ scanResult . ResultCode . Should ( ) . Be ( ProcessingResultCode . Success ) ;
588+
589+ var detectedComponents = componentRecorder . GetDetectedComponents ( ) ;
590+ detectedComponents . Should ( ) . HaveCount ( 4 ) ;
591+
592+ var discoveredComponents = detectedComponents . ToArray ( ) ;
593+ discoveredComponents . Where ( component => component . Component . Id == "4.5.6 unknown unknown - DotNet" ) . Should ( ) . ContainSingle ( ) ;
594+ discoveredComponents . Where ( component => component . Component . Id == "4.5.6 net8.0 library - DotNet" ) . Should ( ) . ContainSingle ( ) ;
595+ discoveredComponents . Where ( component => component . Component . Id == "4.5.6 net6.0 library - DotNet" ) . Should ( ) . ContainSingle ( ) ;
596+ discoveredComponents . Where ( component => component . Component . Id == "4.5.6 netstandard2.0 library - DotNet" ) . Should ( ) . ContainSingle ( ) ;
597+ }
461598}
0 commit comments