dependabot-nuget 0.287.0 → 0.288.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Build.targets +17 -0
  4. data/helpers/lib/NuGetUpdater/Directory.Packages.props +10 -3
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +7 -3
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +72 -51
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +1 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.props +7 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +10 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +36 -19
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +6 -2
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscoveryResult.cs +2 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +5 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +386 -12
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +1 -1
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +11 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +7 -2
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +23 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +0 -4
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/TargetFrameworkReporter.targets +13 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +1 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +2 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/CollectionExtensions.cs +17 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +57 -4
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathComparer.cs +31 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +30 -2
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +50 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +74 -38
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +50 -4
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Proj.cs +5 -5
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +728 -253
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +322 -78
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +2 -6
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +472 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +46 -1
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +0 -1
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +33 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +3 -2
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +4 -2
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +3 -2
  43. data/lib/dependabot/nuget/file_parser.rb +7 -1
  44. data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +0 -3
  45. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +7 -10
  46. data/lib/dependabot/nuget/native_helpers.rb +13 -4
  47. metadata +12 -8
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs +0 -69
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscoveryResult.cs +0 -11
  50. data/lib/dependabot/nuget/native_discovery/native_directory_packages_props_discovery.rb +0 -44
@@ -1,5 +1,6 @@
1
1
  using System.Collections.Immutable;
2
2
  using System.Diagnostics.CodeAnalysis;
3
+ using System.Reflection;
3
4
  using System.Text;
4
5
  using System.Text.Json;
5
6
  using System.Text.Json.Nodes;
@@ -13,7 +14,6 @@ using Microsoft.Build.Exceptions;
13
14
  using Microsoft.Build.Locator;
14
15
  using Microsoft.Extensions.FileSystemGlobbing;
15
16
 
16
- using NuGet;
17
17
  using NuGet.Configuration;
18
18
  using NuGet.Frameworks;
19
19
  using NuGet.Versioning;
@@ -39,12 +39,12 @@ internal static partial class MSBuildHelper
39
39
  var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
40
40
  MSBuildPath = defaultInstance.MSBuildPath;
41
41
  MSBuildLocator.RegisterInstance(defaultInstance);
42
- return Task.CompletedTask;
42
+ return Task.FromResult(0);
43
43
  }).Wait();
44
44
  }
45
45
  }
46
46
 
47
- public static async Task SidelineGlobalJsonAsync(string currentDirectory, string rootDirectory, Func<Task> action, ILogger? logger = null, bool retainMSBuildSdks = false)
47
+ public static async Task<T> SidelineGlobalJsonAsync<T>(string currentDirectory, string rootDirectory, Func<Task<T>> action, ILogger? logger = null, bool retainMSBuildSdks = false)
48
48
  {
49
49
  logger ??= new ConsoleLogger();
50
50
  var candidateDirectories = PathHelper.GetAllDirectoriesToRoot(currentDirectory, rootDirectory);
@@ -73,7 +73,8 @@ internal static partial class MSBuildHelper
73
73
 
74
74
  try
75
75
  {
76
- await action();
76
+ var result = await action();
77
+ return result;
77
78
  }
78
79
  finally
79
80
  {
@@ -747,6 +748,58 @@ internal static partial class MSBuildHelper
747
748
  return tempProjectPath;
748
749
  }
749
750
 
751
+ internal static async Task<ImmutableArray<string>> GetTargetFrameworkValuesFromProject(string repoRoot, string projectPath, ILogger logger)
752
+ {
753
+ // TODO: once the updater image has all relevant SDKs installed, we won't have to sideline global.json anymore
754
+ var projectDirectory = Path.GetDirectoryName(projectPath)!;
755
+ var (exitCode, stdOut, stdErr) = await SidelineGlobalJsonAsync(projectDirectory, repoRoot, async () =>
756
+ {
757
+ var targetsHelperPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "TargetFrameworkReporter.targets");
758
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["build", projectPath, "/t:ReportTargetFramework", $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={targetsHelperPath};CustomAfterMicrosoftCommonTargets={targetsHelperPath}"], workingDirectory: Path.GetDirectoryName(projectPath));
759
+ return (exitCode, stdOut, stdErr);
760
+ });
761
+ ThrowOnUnauthenticatedFeed(stdOut);
762
+ if (exitCode != 0)
763
+ {
764
+ logger.Log($"Error determining target frameworks.\nSTDOUT:\n{stdOut}\nSTDERR:\n{stdErr}");
765
+ }
766
+
767
+ // There are 3 return values, all uses slightly differently. Only one will be set, the others will be blank
768
+ // ProjectData::TargetFrameworkVersion=.NETFramework,Version=v4.5 // non-SDK projects, commonly with `packages.config`
769
+ // ProjectData::TargetFramework=net7.0 // SDK-style projects
770
+ // ProjectData::TargetFrameworks=net7.0;net8.0
771
+ var tfmPatterns = new Regex[]
772
+ {
773
+ new Regex("ProjectData::TargetFrameworkVersion=(?<Value>.*)$", RegexOptions.Multiline),
774
+ new Regex("ProjectData::TargetFramework=(?<Value>.*)$", RegexOptions.Multiline),
775
+ new Regex("ProjectData::TargetFrameworks=(?<Value>.*)$", RegexOptions.Multiline),
776
+ };
777
+ var tfms = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
778
+ foreach (var tfmPattern in tfmPatterns)
779
+ {
780
+ var candidateTfms = tfmPattern.Matches(stdOut)
781
+ .Select(m => m.Groups["Value"].Value)
782
+ .SelectMany(v => v.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
783
+ .Where(v => !string.IsNullOrWhiteSpace(v))
784
+ .Select(v =>
785
+ {
786
+ try
787
+ {
788
+ var framework = NuGetFramework.Parse(v);
789
+ return framework.GetShortFolderName();
790
+ }
791
+ catch
792
+ {
793
+ return string.Empty;
794
+ }
795
+ })
796
+ .Where(tfm => !string.IsNullOrEmpty(tfm));
797
+ tfms.AddRange(candidateTfms);
798
+ }
799
+
800
+ return tfms.ToImmutableArray();
801
+ }
802
+
750
803
  internal static async Task<Dependency[]> GetAllPackageDependenciesAsync(
751
804
  string repoRoot,
752
805
  string projectPath,
@@ -0,0 +1,31 @@
1
+ using System.Diagnostics.CodeAnalysis;
2
+
3
+ namespace NuGetUpdater.Core.Utilities;
4
+
5
+ public class PathComparer : IEqualityComparer<string>
6
+ {
7
+ public static PathComparer Instance { get; } = new PathComparer();
8
+
9
+ public bool Equals(string? x, string? y)
10
+ {
11
+ x = x?.NormalizePathToUnix();
12
+ y = y?.NormalizePathToUnix();
13
+
14
+ if (x is null && y is null)
15
+ {
16
+ return true;
17
+ }
18
+
19
+ if (x is null || y is null)
20
+ {
21
+ return false;
22
+ }
23
+
24
+ return x.Equals(y, StringComparison.OrdinalIgnoreCase);
25
+ }
26
+
27
+ public int GetHashCode([DisallowNull] string obj)
28
+ {
29
+ return obj.NormalizePathToUnix().GetHashCode();
30
+ }
31
+ }
@@ -1,3 +1,6 @@
1
+ using System.Runtime.InteropServices;
2
+ using System.Text.RegularExpressions;
3
+
1
4
  namespace NuGetUpdater.Core;
2
5
 
3
6
  internal static class PathHelper
@@ -63,7 +66,17 @@ internal static class PathHelper
63
66
  return result;
64
67
  }
65
68
 
66
- public static string FullyNormalizedRootedPath(this string path) => path.NormalizePathToUnix().NormalizeUnixPathParts().EnsurePrefix("/");
69
+ public static string FullyNormalizedRootedPath(this string path)
70
+ {
71
+ var normalizedPath = path.NormalizePathToUnix().NormalizeUnixPathParts();
72
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Regex.IsMatch(normalizedPath, @"^[a-z]:", RegexOptions.IgnoreCase))
73
+ {
74
+ // Windows path is ready to go
75
+ return normalizedPath;
76
+ }
77
+
78
+ return normalizedPath.EnsurePrefix("/");
79
+ }
67
80
 
68
81
  public static string GetFullPathFromRelative(string rootPath, string relativePath)
69
82
  => Path.GetFullPath(JoinPath(rootPath, relativePath.NormalizePathToUnix()));
@@ -133,7 +146,7 @@ internal static class PathHelper
133
146
  currentPath = nextPath;
134
147
  }
135
148
 
136
- return currentPath; // Fully resolved path with correct casing
149
+ return currentPath.NormalizePathToUnix(); // Fully resolved path with correct casing
137
150
  }
138
151
 
139
152
  /// <summary>
@@ -212,4 +225,19 @@ internal static class PathHelper
212
225
 
213
226
  return false;
214
227
  }
228
+
229
+ public static bool IsFileUnderDirectory(DirectoryInfo directory, FileInfo candidateFile)
230
+ {
231
+ // n.b., using `DirectoryInfo` and `FileInfo` here to ensure that the callsite doesn't get confused with just strings
232
+ // the paths are then normalized to make the comparison easier.
233
+ var directoryPath = directory.FullName.NormalizePathToUnix();
234
+ if (!directoryPath.EndsWith("/"))
235
+ {
236
+ // ensuring a trailing slash means we can do a simple string check later on
237
+ directoryPath += "/";
238
+ }
239
+ var candidateFilePath = candidateFile.FullName.NormalizePathToUnix();
240
+
241
+ return candidateFilePath.StartsWith(directoryPath);
242
+ }
215
243
  }
@@ -347,6 +347,56 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
347
347
  );
348
348
  }
349
349
 
350
+ [Fact]
351
+ public async Task DuplicateTargetFrameworksWithCasingDifferencesAreCombined()
352
+ {
353
+ await TestAnalyzeAsync(
354
+ packages:
355
+ [
356
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
357
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.1.0", "net8.0"),
358
+ ],
359
+ discovery: new()
360
+ {
361
+ Path = "",
362
+ Projects = [
363
+ new()
364
+ {
365
+ FilePath = "projecta/projecta.csproj",
366
+ TargetFrameworks = ["net8.0"],
367
+ Dependencies = [
368
+ new("Some.Package", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net8.0"]),
369
+ ]
370
+ },
371
+ new()
372
+ {
373
+ FilePath = "projectb/projectb.csproj",
374
+ TargetFrameworks = ["NET8.0"],
375
+ Dependencies = [
376
+ new("Some.Package", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net8.0"]),
377
+ ]
378
+ }
379
+ ]
380
+ },
381
+ dependencyInfo: new()
382
+ {
383
+ Name = "Some.Package",
384
+ Version = "1.0.0",
385
+ IsVulnerable = false,
386
+ IgnoredVersions = [],
387
+ Vulnerabilities = [],
388
+ },
389
+ expectedResult: new()
390
+ {
391
+ UpdatedVersion = "1.1.0",
392
+ CanUpdate = true,
393
+ UpdatedDependencies = [
394
+ new("Some.Package", "1.1.0", DependencyType.Unknown, TargetFrameworks: ["net8.0"]),
395
+ ],
396
+ }
397
+ );
398
+ }
399
+
350
400
  [Fact]
351
401
  public async Task IgnoredVersionsCanHandleWildcardSpecification()
352
402
  {
@@ -1,10 +1,12 @@
1
1
  using System.Collections.Immutable;
2
2
  using System.Diagnostics.CodeAnalysis;
3
+ using System.Runtime.InteropServices;
3
4
  using System.Text.Json;
4
5
 
5
6
  using NuGetUpdater.Core.Discover;
6
7
  using NuGetUpdater.Core.Test.Update;
7
8
  using NuGetUpdater.Core.Test.Utilities;
9
+ using NuGetUpdater.Core.Utilities;
8
10
 
9
11
  using Xunit;
10
12
 
@@ -12,41 +14,43 @@ namespace NuGetUpdater.Core.Test.Discover;
12
14
 
13
15
  using TestFile = (string Path, string Content);
14
16
 
15
- public class DiscoveryWorkerTestBase
17
+ public class DiscoveryWorkerTestBase : TestBase
16
18
  {
17
19
  protected static async Task TestDiscoveryAsync(
18
20
  string workspacePath,
19
21
  TestFile[] files,
20
22
  ExpectedWorkspaceDiscoveryResult expectedResult,
21
- MockNuGetPackage[]? packages = null)
23
+ MockNuGetPackage[]? packages = null,
24
+ ExperimentsManager? experimentsManager = null)
22
25
  {
26
+ experimentsManager ??= new ExperimentsManager();
23
27
  var actualResult = await RunDiscoveryAsync(files, async directoryPath =>
24
28
  {
25
29
  await UpdateWorkerTestBase.MockNuGetPackagesInDirectory(packages, directoryPath);
26
30
 
27
- var worker = new DiscoveryWorker(new TestLogger());
31
+ var worker = new DiscoveryWorker(experimentsManager, new TestLogger());
28
32
  var result = await worker.RunWithErrorHandlingAsync(directoryPath, workspacePath);
29
33
  return result;
30
34
  });
31
35
 
32
- ValidateWorkspaceResult(expectedResult, actualResult);
36
+ ValidateWorkspaceResult(expectedResult, actualResult, experimentsManager);
33
37
  }
34
38
 
35
- protected static void ValidateWorkspaceResult(ExpectedWorkspaceDiscoveryResult expectedResult, WorkspaceDiscoveryResult actualResult)
39
+ protected static void ValidateWorkspaceResult(ExpectedWorkspaceDiscoveryResult expectedResult, WorkspaceDiscoveryResult actualResult, ExperimentsManager experimentsManager)
36
40
  {
37
41
  Assert.NotNull(actualResult);
38
42
  Assert.Equal(expectedResult.Path.NormalizePathToUnix(), actualResult.Path.NormalizePathToUnix());
39
- ValidateDirectoryPackagesProps(expectedResult.DirectoryPackagesProps, actualResult.DirectoryPackagesProps);
40
43
  ValidateResultWithDependencies(expectedResult.GlobalJson, actualResult.GlobalJson);
41
44
  ValidateResultWithDependencies(expectedResult.DotNetToolsJson, actualResult.DotNetToolsJson);
42
- ValidateProjectResults(expectedResult.Projects, actualResult.Projects);
45
+ ValidateProjectResults(expectedResult.Projects, actualResult.Projects, experimentsManager);
46
+ AssertEx.Equal(expectedResult.ImportedFiles, actualResult.ImportedFiles, PathComparer.Instance);
43
47
  Assert.Equal(expectedResult.ExpectedProjectCount ?? expectedResult.Projects.Length, actualResult.Projects.Length);
44
48
  Assert.Equal(expectedResult.ErrorType, actualResult.ErrorType);
45
49
  Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
46
50
 
47
51
  return;
48
52
 
49
- void ValidateResultWithDependencies(ExpectedDependencyDiscoveryResult? expectedResult, IDiscoveryResultWithDependencies? actualResult)
53
+ static void ValidateResultWithDependencies(ExpectedDependencyDiscoveryResult? expectedResult, IDiscoveryResultWithDependencies? actualResult)
50
54
  {
51
55
  if (expectedResult is null)
52
56
  {
@@ -62,50 +66,82 @@ public class DiscoveryWorkerTestBase
62
66
  ValidateDependencies(expectedResult.Dependencies, actualResult.Dependencies);
63
67
  Assert.Equal(expectedResult.ExpectedDependencyCount ?? expectedResult.Dependencies.Length, actualResult.Dependencies.Length);
64
68
  }
69
+ }
70
+
71
+ internal static void ValidateProjectResults(ImmutableArray<ExpectedSdkProjectDiscoveryResult> expectedProjects, ImmutableArray<ProjectDiscoveryResult> actualProjects, ExperimentsManager experimentsManager)
72
+ {
73
+ if (expectedProjects.IsDefaultOrEmpty)
74
+ {
75
+ return;
76
+ }
65
77
 
66
- void ValidateProjectResults(ImmutableArray<ExpectedSdkProjectDiscoveryResult> expectedProjects, ImmutableArray<ProjectDiscoveryResult> actualProjects)
78
+ foreach (var expectedProject in expectedProjects)
67
79
  {
68
- if (expectedProjects.IsDefaultOrEmpty)
80
+ var actualProject = actualProjects.SingleOrDefault(p => p.FilePath.NormalizePathToUnix() == expectedProject.FilePath.NormalizePathToUnix());
81
+ Assert.True(actualProject is not null, $"Unable to find project with path `{expectedProject.FilePath.NormalizePathToUnix()}` in collection [{string.Join(", ", actualProjects.Select(p => p.FilePath))}]");
82
+ Assert.Equal(expectedProject.FilePath.NormalizePathToUnix(), actualProject.FilePath.NormalizePathToUnix());
83
+
84
+ // some properties are byproducts of the older temporary project discovery process and shouldn't be returned
85
+ var actualProperties = actualProject.Properties;
86
+ if (!experimentsManager.UseDirectDiscovery)
69
87
  {
70
- return;
88
+ var forbiddenProperties = new HashSet<string>(["TargetFrameworkVersion"], StringComparer.OrdinalIgnoreCase);
89
+ actualProperties = actualProperties.Where(p => !forbiddenProperties.Contains(p.Name)).ToImmutableArray();
90
+ }
91
+
92
+ AssertEx.Equal(expectedProject.Properties, actualProperties, PropertyComparer.Instance);
93
+ AssertEx.Equal(expectedProject.TargetFrameworks, actualProject.TargetFrameworks);
94
+ AssertEx.Equal(expectedProject.ReferencedProjectPaths, actualProject.ReferencedProjectPaths);
95
+ if (expectedProject.ImportedFiles is not null)
96
+ {
97
+ AssertEx.Equal(expectedProject.ImportedFiles.Value.Select(PathHelper.NormalizePathToUnix), actualProject.ImportedFiles.Select(PathHelper.NormalizePathToUnix));
71
98
  }
72
99
 
73
- foreach (var expectedProject in expectedProjects)
100
+ // some dependencies are byproducts of the older temporary project discovery process and shouldn't be returned
101
+ var actualDependencies = actualProject.Dependencies;
102
+ if (!experimentsManager.UseDirectDiscovery)
74
103
  {
75
- var actualProject = actualProjects.Single(p => p.FilePath.NormalizePathToUnix() == expectedProject.FilePath.NormalizePathToUnix());
76
-
77
- Assert.Equal(expectedProject.FilePath.NormalizePathToUnix(), actualProject.FilePath.NormalizePathToUnix());
78
- AssertEx.Equal(expectedProject.Properties, actualProject.Properties, PropertyComparer.Instance);
79
- AssertEx.Equal(expectedProject.TargetFrameworks, actualProject.TargetFrameworks);
80
- AssertEx.Equal(expectedProject.ReferencedProjectPaths.Select(PathHelper.NormalizePathToUnix), actualProject.ReferencedProjectPaths.Select(PathHelper.NormalizePathToUnix));
81
- ValidateDependencies(expectedProject.Dependencies, actualProject.Dependencies);
82
- Assert.Equal(expectedProject.ExpectedDependencyCount ?? expectedProject.Dependencies.Length, actualProject.Dependencies.Length);
104
+ var forbiddenDependencies = new HashSet<string>(["Microsoft.NET.Sdk"], StringComparer.OrdinalIgnoreCase);
105
+ actualDependencies = actualDependencies.Where(d => !forbiddenDependencies.Contains(d.Name)).ToImmutableArray();
83
106
  }
107
+
108
+ // some dependencies are byproducts of the test framework and shouldn't be returned to make the tests more deterministic
109
+ var forbiddenTestDependencies = new HashSet<string>(["Microsoft.NETFramework.ReferenceAssemblies"], StringComparer.OrdinalIgnoreCase);
110
+ actualDependencies = actualDependencies.Where(d => !forbiddenTestDependencies.Contains(d.Name)).ToImmutableArray();
111
+
112
+ ValidateDependencies(expectedProject.Dependencies, actualDependencies);
113
+ Assert.Equal(expectedProject.ExpectedDependencyCount ?? expectedProject.Dependencies.Length, actualDependencies.Length);
84
114
  }
115
+ }
85
116
 
86
- void ValidateDirectoryPackagesProps(ExpectedDirectoryPackagesPropsDiscovertyResult? expected, DirectoryPackagesPropsDiscoveryResult? actual)
117
+ internal static void ValidateDependencies(ImmutableArray<Dependency> expectedDependencies, ImmutableArray<Dependency> actualDependencies)
118
+ {
119
+ if (expectedDependencies.IsDefault)
87
120
  {
88
- ValidateResultWithDependencies(expected, actual);
89
- Assert.Equal(expected?.IsTransitivePinningEnabled, actual?.IsTransitivePinningEnabled);
121
+ return;
90
122
  }
91
123
 
92
- void ValidateDependencies(ImmutableArray<Dependency> expectedDependencies, ImmutableArray<Dependency> actualDependencies)
124
+ foreach (var expectedDependency in expectedDependencies)
93
125
  {
94
- if (expectedDependencies.IsDefault)
126
+ var matchingDependencies = actualDependencies.Where(d =>
95
127
  {
96
- return;
97
- }
98
-
99
- foreach (var expectedDependency in expectedDependencies)
100
- {
101
- var actualDependency = actualDependencies.Single(d => d.Name == expectedDependency.Name && d.Type == expectedDependency.Type);
102
- Assert.Equal(expectedDependency.Name, actualDependency.Name);
103
- Assert.Equal(expectedDependency.Version, actualDependency.Version);
104
- Assert.Equal(expectedDependency.Type, actualDependency.Type);
105
- AssertEx.Equal(expectedDependency.TargetFrameworks, actualDependency.TargetFrameworks);
106
- Assert.Equal(expectedDependency.IsDirect, actualDependency.IsDirect);
107
- Assert.Equal(expectedDependency.IsTransitive, actualDependency.IsTransitive);
108
- }
128
+ return d.Name == expectedDependency.Name
129
+ && d.Type == expectedDependency.Type
130
+ && d.Version == expectedDependency.Version
131
+ && d.IsDirect == expectedDependency.IsDirect
132
+ && d.IsTransitive == expectedDependency.IsTransitive
133
+ && d.TargetFrameworks.SequenceEqual(expectedDependency.TargetFrameworks);
134
+ }).ToArray();
135
+ Assert.True(matchingDependencies.Length == 1, $"""
136
+ Unable to find 1 dependency matching; found {matchingDependencies.Length}:
137
+ Name: {expectedDependency.Name}
138
+ Type: {expectedDependency.Type}
139
+ Version: {expectedDependency.Version}
140
+ IsDirect: {expectedDependency.IsDirect}
141
+ IsTransitive: {expectedDependency.IsTransitive}
142
+ TargetFrameworks: {string.Join(", ", expectedDependency.TargetFrameworks ?? [])}
143
+ Found:{"\n\t"}{string.Join("\n\t", actualDependencies)}
144
+ """);
109
145
  }
110
146
  }
111
147
 
@@ -7,9 +7,10 @@ public partial class DiscoveryWorkerTests
7
7
  public class PackagesConfig : DiscoveryWorkerTestBase
8
8
  {
9
9
  [Fact]
10
- public async Task DiscoversDependencies()
10
+ public async Task DiscoversDependencies_DirectDiscovery()
11
11
  {
12
12
  await TestDiscoveryAsync(
13
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = true },
13
14
  packages:
14
15
  [
15
16
  MockNuGetPackage.CreateSimplePackage("Package.A", "1.0.0", "net46"),
@@ -25,7 +26,7 @@ public partial class DiscoveryWorkerTests
25
26
  </packages>
26
27
  """),
27
28
  ("myproj.csproj", """
28
- <Project>
29
+ <Project Sdk="Microsoft.NET.Sdk">
29
30
  <PropertyGroup>
30
31
  <TargetFramework>net46</TargetFramework>
31
32
  </PropertyGroup>
@@ -40,11 +41,56 @@ public partial class DiscoveryWorkerTests
40
41
  {
41
42
  FilePath = "myproj.csproj",
42
43
  Properties = [
43
- new("TargetFramework", "net46", "myproj.csproj"),
44
+ new("TargetFramework", "net46", "myproj.csproj")
44
45
  ],
45
46
  TargetFrameworks = ["net46"],
46
47
  Dependencies = [
47
- new("Microsoft.NETFramework.ReferenceAssemblies", "1.0.3", DependencyType.Unknown, TargetFrameworks: ["net46"], IsTransitive: true),
48
+ new("Package.A", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
49
+ new("Package.B", "2.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
50
+ ],
51
+ }
52
+ ],
53
+ }
54
+ );
55
+ }
56
+
57
+ [Fact]
58
+ public async Task DiscoversDependencies_TemporaryProjectDiscovery()
59
+ {
60
+ await TestDiscoveryAsync(
61
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = false },
62
+ packages:
63
+ [
64
+ MockNuGetPackage.CreateSimplePackage("Package.A", "1.0.0", "net46"),
65
+ MockNuGetPackage.CreateSimplePackage("Package.B", "2.0.0", "net46"),
66
+ ],
67
+ workspacePath: "",
68
+ files: [
69
+ ("packages.config", """
70
+ <?xml version="1.0" encoding="utf-8"?>
71
+ <packages>
72
+ <package id="Package.A" version="1.0.0" targetFramework="net46" />
73
+ <package id="Package.B" version="2.0.0" targetFramework="net46" />
74
+ </packages>
75
+ """),
76
+ ("myproj.csproj", """
77
+ <Project Sdk="Microsoft.NET.Sdk">
78
+ <PropertyGroup>
79
+ <TargetFramework>net46</TargetFramework>
80
+ </PropertyGroup>
81
+ </Project>
82
+ """)
83
+ ],
84
+ expectedResult: new()
85
+ {
86
+ Path = "",
87
+ Projects = [
88
+ new()
89
+ {
90
+ FilePath = "myproj.csproj",
91
+ Properties = [new("TargetFramework", "net46", "myproj.csproj")],
92
+ TargetFrameworks = ["net46"],
93
+ Dependencies = [
48
94
  new("Package.A", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
49
95
  new("Package.B", "2.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
50
96
  ],
@@ -12,7 +12,11 @@ public partial class DiscoveryWorkerTests
12
12
  public async Task DirsProjExpansion(string itemType)
13
13
  {
14
14
  await TestDiscoveryAsync(
15
- packages: [],
15
+ packages:
16
+ [
17
+ MockNuGetPackage.CreateSimplePackage("Package.A", "1.0.0", "net8.0"),
18
+ MockNuGetPackage.CreateSimplePackage("Package.B", "2.0.0", "net8.0"),
19
+ ],
16
20
  workspacePath: "dependabot",
17
21
  files:
18
22
  [
@@ -60,7 +64,6 @@ public partial class DiscoveryWorkerTests
60
64
  new()
61
65
  {
62
66
  FilePath = "../src/project1/project1.csproj",
63
- ExpectedDependencyCount = 2,
64
67
  Dependencies =
65
68
  [
66
69
  new("Package.A", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true),
@@ -70,12 +73,10 @@ public partial class DiscoveryWorkerTests
70
73
  new("TargetFramework", "net8.0", "src/project1/project1.csproj")
71
74
  ],
72
75
  TargetFrameworks = ["net8.0"],
73
- ReferencedProjectPaths = []
74
76
  },
75
77
  new()
76
78
  {
77
79
  FilePath = "../src/project2/project2.csproj",
78
- ExpectedDependencyCount = 2,
79
80
  Dependencies =
80
81
  [
81
82
  new("Package.B", "2.0.0", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true),
@@ -85,7 +86,6 @@ public partial class DiscoveryWorkerTests
85
86
  new("TargetFramework", "net8.0", "src/project2/project2.csproj")
86
87
  ],
87
88
  TargetFrameworks = ["net8.0"],
88
- ReferencedProjectPaths = []
89
89
  }
90
90
  ]
91
91
  }