dependabot-nuget 0.292.0 → 0.294.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Packages.props +2 -1
  4. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Correlator.cs +197 -0
  5. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/DotNetPackageCorrelation.csproj +12 -0
  6. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageMapper.cs +68 -0
  7. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageSet.cs +11 -0
  8. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/Release.cs +25 -0
  9. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/ReleasesFile.cs +9 -0
  10. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/RuntimePackages.cs +11 -0
  11. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/Sdk.cs +13 -0
  12. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/SemVerComparer.cs +16 -0
  13. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/SemVersionConverter.cs +42 -0
  14. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/DotNetPackageCorrelation.Cli.csproj +16 -0
  15. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/Program.cs +32 -0
  16. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/CorrelatorTests.cs +99 -0
  17. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/DotNetPackageCorrelation.Test.csproj +18 -0
  18. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/EndToEndTests.cs +30 -0
  19. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/RuntimePackagesTests.cs +206 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +6 -4
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +8 -7
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +4 -4
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +17 -5
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +7 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +46 -6
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +8 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +8 -17
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +4 -4
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Extensions.cs +1 -1
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +16 -5
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +2 -1
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +2 -2
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +7 -20
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +3 -3
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +99 -2
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EnsureDotNetPackageCorrelation.targets +25 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +9 -22
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +5 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs +2 -1
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +3 -3
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +3 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Advisory.cs +2 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +7 -3
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotParseable.cs +15 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +24 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +33 -30
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/UnparseableFileException.cs +12 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +21 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +6 -30
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DotNetPackageCorrelationManager.cs +46 -0
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +51 -27
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +15 -4
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +70 -9
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +20 -12
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +108 -0
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +16 -12
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -1
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +15 -28
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +61 -0
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +5 -4
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +9 -1
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs +24 -0
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/ExpectedUpdateOperationResult.cs +1 -1
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +11 -15
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +1 -1
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +14 -8
  68. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +148 -3
  69. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +12 -14
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.sln +18 -1
  71. data/lib/dependabot/nuget/native_helpers.rb +41 -16
  72. metadata +25 -6
  73. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +0 -12
@@ -4,6 +4,8 @@ using Microsoft.Language.Xml;
4
4
 
5
5
  using NuGet.Versioning;
6
6
 
7
+ using NuGetUpdater.Core.Utilities;
8
+
7
9
  namespace NuGetUpdater.Core;
8
10
 
9
11
  /// <summary>
@@ -36,6 +38,25 @@ internal static class PackageReferenceUpdater
36
38
 
37
39
  // Get the set of all top-level dependencies in the current project
38
40
  var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
41
+ var isDependencyTopLevel = topLevelDependencies.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
42
+ if (isDependencyTopLevel)
43
+ {
44
+ var packageMapper = DotNetPackageCorrelationManager.GetPackageMapper();
45
+ // TODO: this is slow
46
+ var isSdkReplacementPackage = packageMapper.RuntimePackages.Runtimes.Any(r =>
47
+ {
48
+ return r.Value.Packages.Any(p => dependencyName.Equals(p.Key, StringComparison.Ordinal));
49
+ });
50
+ if (isSdkReplacementPackage)
51
+ {
52
+ // If we're updating a top level SDK replacement package, the version listed in the project file won't
53
+ // necessarily match the resolved version that caused the update because the SDK might have replaced
54
+ // the package. To handle this scenario, we pretend the version we're searching for is the actual
55
+ // version in the file, not the resolved version. This allows us to keep a strict equality check when
56
+ // finding the file to update.
57
+ previousDependencyVersion = topLevelDependencies.First(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase)).Version!;
58
+ }
59
+ }
39
60
 
40
61
  if (!await DoesDependencyRequireUpdateAsync(repoRootPath, projectPath, tfms, topLevelDependencies, dependencyName, newDependencyVersion, experimentsManager, logger))
41
62
  {
@@ -3,6 +3,7 @@ using System.Text.Json;
3
3
  using System.Text.Json.Serialization;
4
4
 
5
5
  using NuGetUpdater.Core.Analyze;
6
+ using NuGetUpdater.Core.Run.ApiModel;
6
7
  using NuGetUpdater.Core.Updater;
7
8
  using NuGetUpdater.Core.Utilities;
8
9
 
@@ -10,6 +11,7 @@ namespace NuGetUpdater.Core;
10
11
 
11
12
  public class UpdaterWorker : IUpdaterWorker
12
13
  {
14
+ private readonly string _jobId;
13
15
  private readonly ExperimentsManager _experimentsManager;
14
16
  private readonly ILogger _logger;
15
17
  private readonly HashSet<string> _processedProjectPaths = new(StringComparer.OrdinalIgnoreCase);
@@ -20,8 +22,9 @@ public class UpdaterWorker : IUpdaterWorker
20
22
  Converters = { new JsonStringEnumConverter() },
21
23
  };
22
24
 
23
- public UpdaterWorker(ExperimentsManager experimentsManager, ILogger logger)
25
+ public UpdaterWorker(string jobId, ExperimentsManager experimentsManager, ILogger logger)
24
26
  {
27
+ _jobId = jobId;
25
28
  _experimentsManager = experimentsManager;
26
29
  _logger = logger;
27
30
  }
@@ -43,42 +46,15 @@ public class UpdaterWorker : IUpdaterWorker
43
46
  {
44
47
  result = await RunAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
45
48
  }
46
- catch (HttpRequestException ex)
47
- when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
49
+ catch (Exception ex)
48
50
  {
49
51
  if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
50
52
  {
51
53
  workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
52
54
  }
53
-
54
- result = new()
55
- {
56
- ErrorType = ErrorType.AuthenticationFailure,
57
- ErrorDetails = "(" + string.Join("|", NuGetContext.GetPackageSourceUrls(workspacePath)) + ")",
58
- };
59
- }
60
- catch (MissingFileException ex)
61
- {
62
- result = new()
63
- {
64
- ErrorType = ErrorType.MissingFile,
65
- ErrorDetails = ex.FilePath,
66
- };
67
- }
68
- catch (UpdateNotPossibleException ex)
69
- {
70
- result = new()
71
- {
72
- ErrorType = ErrorType.UpdateNotPossible,
73
- ErrorDetails = ex.Dependencies,
74
- };
75
- }
76
- catch (Exception ex)
77
- {
78
55
  result = new()
79
56
  {
80
- ErrorType = ErrorType.Unknown,
81
- ErrorDetails = ex.ToString(),
57
+ Error = JobErrorBase.ErrorFromException(ex, _jobId, workspacePath),
82
58
  };
83
59
  }
84
60
 
@@ -0,0 +1,46 @@
1
+ using System.Text.Json;
2
+
3
+ using DotNetPackageCorrelation;
4
+
5
+ using NuGetUpdater.Core.Discover;
6
+
7
+ namespace NuGetUpdater.Core.Utilities;
8
+
9
+ internal static class DotNetPackageCorrelationManager
10
+ {
11
+ private static readonly PackageMapper _packageMapper;
12
+ private static readonly Dictionary<string, PackageMapper> _packageMapperByOverrideFile = new();
13
+
14
+ static DotNetPackageCorrelationManager()
15
+ {
16
+ var packageCorrelationPath = Path.Combine(Path.GetDirectoryName(typeof(SdkProjectDiscovery).Assembly.Location)!, "dotnet-package-correlation.json");
17
+ var runtimePackages = LoadRuntimePackagesFromFile(packageCorrelationPath);
18
+ _packageMapper = PackageMapper.Load(runtimePackages);
19
+ }
20
+
21
+ public static PackageMapper GetPackageMapper()
22
+ {
23
+ var packageCorrelationFileOverride = Environment.GetEnvironmentVariable("DOTNET_PACKAGE_CORRELATION_FILE_PATH");
24
+ if (packageCorrelationFileOverride is not null)
25
+ {
26
+ // this is used as a test hook to allow unit tests to be SDK agnostic
27
+ if (_packageMapperByOverrideFile.TryGetValue(packageCorrelationFileOverride, out var packageMapper))
28
+ {
29
+ return packageMapper;
30
+ }
31
+
32
+ var runtimePackages = LoadRuntimePackagesFromFile(packageCorrelationFileOverride);
33
+ packageMapper = PackageMapper.Load(runtimePackages);
34
+ _packageMapperByOverrideFile[packageCorrelationFileOverride] = packageMapper;
35
+ return packageMapper;
36
+ }
37
+
38
+ return _packageMapper;
39
+ }
40
+
41
+ private static RuntimePackages LoadRuntimePackagesFromFile(string filePath)
42
+ {
43
+ var packageCorrelationJson = File.ReadAllText(filePath);
44
+ return JsonSerializer.Deserialize<RuntimePackages>(packageCorrelationJson, Correlator.SerializerOptions)!;
45
+ }
46
+ }
@@ -19,6 +19,7 @@ using NuGet.Frameworks;
19
19
  using NuGet.Versioning;
20
20
 
21
21
  using NuGetUpdater.Core.Analyze;
22
+ using NuGetUpdater.Core.Discover;
22
23
  using NuGetUpdater.Core.Utilities;
23
24
 
24
25
  namespace NuGetUpdater.Core;
@@ -342,7 +343,7 @@ internal static partial class MSBuildHelper
342
343
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
343
344
  try
344
345
  {
345
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
346
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger);
346
347
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
347
348
 
348
349
  // NU1608: Detected package version outside of dependency constraint
@@ -362,7 +363,7 @@ internal static partial class MSBuildHelper
362
363
 
363
364
  try
364
365
  {
365
- string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
366
+ string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger);
366
367
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
367
368
 
368
369
  // Add Dependency[] packages to List<PackageToUpdate> existingPackages
@@ -518,7 +519,7 @@ internal static partial class MSBuildHelper
518
519
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
519
520
  try
520
521
  {
521
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
522
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger);
522
523
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
523
524
  ThrowOnUnauthenticatedFeed(stdOut);
524
525
 
@@ -668,12 +669,22 @@ internal static partial class MSBuildHelper
668
669
  string projectPath,
669
670
  string targetFramework,
670
671
  IReadOnlyCollection<Dependency> packages,
672
+ ExperimentsManager experimentsManager,
671
673
  ILogger logger,
672
674
  bool usePackageDownload = false)
673
675
  {
674
676
  var projectDirectory = Path.GetDirectoryName(projectPath);
675
677
  projectDirectory ??= repoRoot;
676
- var topLevelFiles = Directory.GetFiles(repoRoot);
678
+
679
+ if (experimentsManager.InstallDotnetSdks)
680
+ {
681
+ var globalJsonPath = PathHelper.GetFileInDirectoryOrParent(projectPath, repoRoot, "global.json", caseSensitive: true);
682
+ if (globalJsonPath is not null)
683
+ {
684
+ File.Copy(globalJsonPath, Path.Combine(tempDir.FullName, "global.json"));
685
+ }
686
+ }
687
+
677
688
  var nugetConfigPath = PathHelper.GetFileInDirectoryOrParent(projectPath, repoRoot, "NuGet.Config", caseSensitive: false);
678
689
  if (nugetConfigPath is not null)
679
690
  {
@@ -832,40 +843,53 @@ internal static partial class MSBuildHelper
832
843
  string targetFramework,
833
844
  IReadOnlyCollection<Dependency> packages,
834
845
  ExperimentsManager experimentsManager,
835
- ILogger logger)
846
+ ILogger logger
847
+ )
836
848
  {
837
849
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_");
838
850
  try
839
851
  {
840
852
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
841
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
853
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger);
842
854
 
843
- var (exitCode, stdout, stderr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["build", tempProjectPath, "/t:_ReportDependencies"], tempDirectory.FullName, experimentsManager);
844
- ThrowOnUnauthenticatedFeed(stdout);
845
-
846
- if (exitCode == 0)
855
+ Dependency[] allDependencies;
856
+ if (experimentsManager.UseDirectDiscovery)
847
857
  {
848
- ImmutableArray<string> tfms = [targetFramework];
849
- var lines = stdout.Split('\n').Select(line => line.Trim());
850
- var pattern = PackagePattern();
851
- var allDependencies = lines
852
- .Select(line => pattern.Match(line))
853
- .Where(match => match.Success)
854
- .Select(match =>
855
- {
856
- var PackageName = match.Groups["PackageName"].Value;
857
- var isTransitive = !topLevelPackagesNames.Contains(PackageName);
858
- return new Dependency(PackageName, match.Groups["PackageVersion"].Value, DependencyType.Unknown, TargetFrameworks: tfms, IsTransitive: isTransitive);
859
- })
860
- .ToArray();
861
-
862
- return allDependencies;
858
+ var projectDiscovery = await SdkProjectDiscovery.DiscoverAsync(repoRoot, tempDirectory.FullName, tempProjectPath, experimentsManager, logger);
859
+ allDependencies = projectDiscovery
860
+ .Where(p => p.FilePath == Path.GetFileName(tempProjectPath))
861
+ .FirstOrDefault()
862
+ ?.Dependencies.ToArray() ?? [];
863
863
  }
864
864
  else
865
865
  {
866
- logger?.Warn($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}");
867
- return [];
866
+ var (exitCode, stdout, stderr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["build", tempProjectPath, "/t:_ReportDependencies"], tempDirectory.FullName, experimentsManager);
867
+ ThrowOnUnauthenticatedFeed(stdout);
868
+
869
+ if (exitCode == 0)
870
+ {
871
+ ImmutableArray<string> tfms = [targetFramework];
872
+ var lines = stdout.Split('\n').Select(line => line.Trim());
873
+ var pattern = PackagePattern();
874
+ allDependencies = lines
875
+ .Select(line => pattern.Match(line))
876
+ .Where(match => match.Success)
877
+ .Select(match =>
878
+ {
879
+ var PackageName = match.Groups["PackageName"].Value;
880
+ var isTransitive = !topLevelPackagesNames.Contains(PackageName);
881
+ return new Dependency(PackageName, match.Groups["PackageVersion"].Value, DependencyType.Unknown, TargetFrameworks: tfms, IsTransitive: isTransitive);
882
+ })
883
+ .ToArray();
884
+ }
885
+ else
886
+ {
887
+ logger?.Warn($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}");
888
+ allDependencies = [];
889
+ }
868
890
  }
891
+
892
+ return allDependencies;
869
893
  }
870
894
  finally
871
895
  {
@@ -9,7 +9,7 @@ internal static class NuGetHelper
9
9
  var tempDirectory = Directory.CreateTempSubdirectory("msbuild_sdk_restore_");
10
10
  try
11
11
  {
12
- var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, logger, usePackageDownload: true);
12
+ var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, experimentsManager, logger, usePackageDownload: true);
13
13
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
14
14
 
15
15
  return exitCode == 0;
@@ -12,7 +12,7 @@ namespace NuGetUpdater.Core.Test.Analyze;
12
12
 
13
13
  using TestFile = (string Path, string Content);
14
14
 
15
- public class AnalyzeWorkerTestBase
15
+ public class AnalyzeWorkerTestBase : TestBase
16
16
  {
17
17
  protected static async Task TestAnalyzeAsync(
18
18
  WorkspaceDiscoveryResult discovery,
@@ -39,7 +39,7 @@ public class AnalyzeWorkerTestBase
39
39
  var discoveryPath = Path.GetFullPath(DiscoveryWorker.DiscoveryResultFileName, directoryPath);
40
40
  var dependencyPath = Path.GetFullPath(relativeDependencyPath, directoryPath);
41
41
 
42
- var worker = new AnalyzeWorker(experimentsManager, new TestLogger());
42
+ var worker = new AnalyzeWorker("TEST-JOB-ID", experimentsManager, new TestLogger());
43
43
  var result = await worker.RunWithErrorHandlingAsync(directoryPath, discoveryPath, dependencyPath);
44
44
  return result;
45
45
  });
@@ -55,8 +55,7 @@ public class AnalyzeWorkerTestBase
55
55
  Assert.Equal(expectedResult.VersionComesFromMultiDependencyProperty, actualResult.VersionComesFromMultiDependencyProperty);
56
56
  ValidateDependencies(expectedResult.UpdatedDependencies, actualResult.UpdatedDependencies);
57
57
  Assert.Equal(expectedResult.ExpectedUpdatedDependenciesCount ?? expectedResult.UpdatedDependencies.Length, actualResult.UpdatedDependencies.Length);
58
- Assert.Equal(expectedResult.ErrorType, actualResult.ErrorType);
59
- Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
58
+ ValidateResult(expectedResult, actualResult);
60
59
 
61
60
  return;
62
61
 
@@ -81,6 +80,18 @@ public class AnalyzeWorkerTestBase
81
80
  }
82
81
  }
83
82
 
83
+ protected static void ValidateResult(ExpectedAnalysisResult? expectedResult, AnalysisResult actualResult)
84
+ {
85
+ if (expectedResult?.Error is not null)
86
+ {
87
+ ValidateError(expectedResult.Error, actualResult.Error);
88
+ }
89
+ else
90
+ {
91
+ Assert.Null(actualResult.Error);
92
+ }
93
+ }
94
+
84
95
  protected static async Task<AnalysisResult> RunAnalyzerAsync(string dependencyName, TestFile[] files, Func<string, Task<AnalysisResult>> action)
85
96
  {
86
97
  // write initial files
@@ -4,6 +4,7 @@ using System.Text.Json;
4
4
  using NuGet;
5
5
 
6
6
  using NuGetUpdater.Core.Analyze;
7
+ using NuGetUpdater.Core.Run.ApiModel;
7
8
 
8
9
  using Xunit;
9
10
 
@@ -477,6 +478,61 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
477
478
  );
478
479
  }
479
480
 
481
+ [Fact]
482
+ public async Task SafeVersionsPropertyIsHonored()
483
+ {
484
+ await TestAnalyzeAsync(
485
+ packages:
486
+ [
487
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"), // initially this
488
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.1.0", "net8.0"), // should update to this due to `SafeVersions`
489
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.2.0", "net8.0"), // this should not be considered
490
+ ],
491
+ discovery: new()
492
+ {
493
+ Path = "/",
494
+ Projects = [
495
+ new()
496
+ {
497
+ FilePath = "./project.csproj",
498
+ TargetFrameworks = ["net8.0"],
499
+ Dependencies = [
500
+ new("Some.Package", "1.0.0", DependencyType.PackageReference),
501
+ ],
502
+ ReferencedProjectPaths = [],
503
+ ImportedFiles = [],
504
+ AdditionalFiles = [],
505
+ },
506
+ ],
507
+ },
508
+ dependencyInfo: new()
509
+ {
510
+ Name = "Some.Package",
511
+ Version = "1.0.0",
512
+ IgnoredVersions = [],
513
+ IsVulnerable = false,
514
+ Vulnerabilities = [
515
+ new()
516
+ {
517
+ DependencyName = "Some.Package",
518
+ PackageManager = "nuget",
519
+ VulnerableVersions = [Requirement.Parse(">= 1.0.0, < 1.1.0")],
520
+ SafeVersions = [Requirement.Parse("= 1.1.0")]
521
+ }
522
+ ],
523
+ },
524
+ expectedResult: new()
525
+ {
526
+ UpdatedVersion = "1.1.0",
527
+ CanUpdate = true,
528
+ VersionComesFromMultiDependencyProperty = false,
529
+ UpdatedDependencies = [
530
+ new("Some.Package", "1.1.0", DependencyType.Unknown, TargetFrameworks: ["net8.0"]),
531
+ ],
532
+ }
533
+ );
534
+ }
535
+
480
536
  [Fact]
481
537
  public async Task VersionFinderCanHandle404FromPackageSource_V2()
482
538
  {
@@ -1015,8 +1071,7 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
1015
1071
  using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync([]);
1016
1072
  await AnalyzeWorker.WriteResultsAsync(temporaryDirectory.DirectoryPath, "Some.Dependency", new()
1017
1073
  {
1018
- ErrorType = ErrorType.AuthenticationFailure,
1019
- ErrorDetails = "<some package feed>",
1074
+ Error = new PrivateSourceAuthenticationFailure(["<some package feed>"]),
1020
1075
  UpdatedVersion = "",
1021
1076
  UpdatedDependencies = [],
1022
1077
  }, new TestLogger());
@@ -1025,16 +1080,23 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
1025
1080
  // raw result file should look like this:
1026
1081
  // {
1027
1082
  // ...
1028
- // "ErrorType": "AuthenticationFailure",
1083
+ // "Error": {
1084
+ // "error-type": "private_source_authentication_failure",
1085
+ // "error-details": {
1086
+ // "source": "(<some package feed>)"
1087
+ // }
1088
+ // }
1029
1089
  // "ErrorDetails": "<some package feed>",
1030
1090
  // ...
1031
1091
  // }
1032
1092
  var jsonDocument = JsonDocument.Parse(discoveryContents);
1033
- var errorType = jsonDocument.RootElement.GetProperty("ErrorType");
1034
- var errorDetails = jsonDocument.RootElement.GetProperty("ErrorDetails");
1093
+ var error = jsonDocument.RootElement.GetProperty("Error");
1094
+ var errorType = error.GetProperty("error-type");
1095
+ var errorDetails = error.GetProperty("error-details");
1096
+ var errorSource = errorDetails.GetProperty("source");
1035
1097
 
1036
- Assert.Equal("AuthenticationFailure", errorType.GetString());
1037
- Assert.Equal("<some package feed>", errorDetails.GetString());
1098
+ Assert.Equal("private_source_authentication_failure", errorType.GetString());
1099
+ Assert.Equal("(<some package feed>)", errorSource.GetString());
1038
1100
  }
1039
1101
 
1040
1102
  [Fact]
@@ -1110,8 +1172,7 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
1110
1172
  },
1111
1173
  expectedResult: new()
1112
1174
  {
1113
- ErrorType = ErrorType.AuthenticationFailure,
1114
- ErrorDetails = $"({http.BaseUrl.TrimEnd('/')}/index.json)",
1175
+ Error = new PrivateSourceAuthenticationFailure([$"{http.BaseUrl.TrimEnd('/')}/index.json"]),
1115
1176
  UpdatedVersion = string.Empty,
1116
1177
  CanUpdate = false,
1117
1178
  UpdatedDependencies = [],
@@ -5,7 +5,9 @@ using System.Text.Json;
5
5
 
6
6
  using NuGetUpdater.Core.Discover;
7
7
  using NuGetUpdater.Core.Test.Update;
8
+ using NuGetUpdater.Core.Test.Updater;
8
9
  using NuGetUpdater.Core.Test.Utilities;
10
+ using NuGetUpdater.Core.Updater;
9
11
  using NuGetUpdater.Core.Utilities;
10
12
 
11
13
  using Xunit;
@@ -28,7 +30,7 @@ public class DiscoveryWorkerTestBase : TestBase
28
30
  {
29
31
  await UpdateWorkerTestBase.MockNuGetPackagesInDirectory(packages, directoryPath);
30
32
 
31
- var worker = new DiscoveryWorker(experimentsManager, new TestLogger());
33
+ var worker = new DiscoveryWorker("TEST-JOB-ID", experimentsManager, new TestLogger());
32
34
  var result = await worker.RunWithErrorHandlingAsync(directoryPath, workspacePath);
33
35
  return result;
34
36
  });
@@ -44,17 +46,7 @@ public class DiscoveryWorkerTestBase : TestBase
44
46
  ValidateResultWithDependencies(expectedResult.DotNetToolsJson, actualResult.DotNetToolsJson);
45
47
  ValidateProjectResults(expectedResult.Projects, actualResult.Projects, experimentsManager);
46
48
  Assert.Equal(expectedResult.ExpectedProjectCount ?? expectedResult.Projects.Length, actualResult.Projects.Length);
47
- Assert.Equal(expectedResult.ErrorType, actualResult.ErrorType);
48
- if (expectedResult.ErrorDetailsPattern is not null)
49
- {
50
- var errorDetails = actualResult.ErrorDetails?.ToString();
51
- Assert.NotNull(errorDetails);
52
- Assert.Matches(expectedResult.ErrorDetailsPattern, errorDetails);
53
- }
54
- else
55
- {
56
- Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
57
- }
49
+ ValidateDiscoveryOperationResult(expectedResult, actualResult);
58
50
 
59
51
  return;
60
52
 
@@ -76,6 +68,22 @@ public class DiscoveryWorkerTestBase : TestBase
76
68
  }
77
69
  }
78
70
 
71
+ protected static void ValidateDiscoveryOperationResult(ExpectedWorkspaceDiscoveryResult? expectedResult, WorkspaceDiscoveryResult actualResult)
72
+ {
73
+ if (expectedResult?.Error is not null)
74
+ {
75
+ ValidateError(expectedResult.Error, actualResult.Error);
76
+ }
77
+ else if (expectedResult?.ErrorRegex is not null)
78
+ {
79
+ ValidateErrorRegex(expectedResult.ErrorRegex, actualResult.Error);
80
+ }
81
+ else
82
+ {
83
+ Assert.Null(actualResult.Error);
84
+ }
85
+ }
86
+
79
87
  internal static void ValidateProjectResults(ImmutableArray<ExpectedSdkProjectDiscoveryResult> expectedProjects, ImmutableArray<ProjectDiscoveryResult> actualProjects, ExperimentsManager experimentsManager)
80
88
  {
81
89
  if (expectedProjects.IsDefaultOrEmpty)
@@ -1263,5 +1263,113 @@ public partial class DiscoveryWorkerTests
1263
1263
  }
1264
1264
  );
1265
1265
  }
1266
+
1267
+ [Fact]
1268
+ public async Task PackagesManagedAndRemovedByTheSdkAreReported()
1269
+ {
1270
+ // To avoid a unit test that's tightly coupled to the installed SDK, some files are faked.
1271
+ // First up, the `dotnet-package-correlation.json` is faked to have the appropriate shape to report a
1272
+ // package replacement. Doing this requires a temporary file and environment variable override.
1273
+ using var tempDirectory = new TemporaryDirectory();
1274
+ var packageCorrelationFile = Path.Combine(tempDirectory.DirectoryPath, "dotnet-package-correlation.json");
1275
+ await File.WriteAllTextAsync(packageCorrelationFile, """
1276
+ {
1277
+ "Runtimes": {
1278
+ "1.0.0": {
1279
+ "Packages": {
1280
+ "Dependabot.App.Core.Ref": "1.0.0",
1281
+ "Test.Only.Package": "1.0.0"
1282
+ }
1283
+ },
1284
+ "1.0.1": {
1285
+ "Packages": {
1286
+ "Dependabot.App.Core.Ref": "1.0.1",
1287
+ "Test.Only.Package": "1.0.99"
1288
+ }
1289
+ }
1290
+ }
1291
+ }
1292
+ """);
1293
+ using var tempEnvironment = new TemporaryEnvironment([("DOTNET_PACKAGE_CORRELATION_FILE_PATH", packageCorrelationFile)]);
1294
+
1295
+ // The SDK package handling is detected in a very specific circumstance; an assembly being removed from the
1296
+ // `@(References)` item group in the `_HandlePackageFileConflicts` target. Since we don't want to involve
1297
+ // the real SDK, we fake some required targets.
1298
+ await TestDiscoveryAsync(
1299
+ experimentsManager: new ExperimentsManager() { InstallDotnetSdks = true, UseDirectDiscovery = true },
1300
+ packages: [],
1301
+ workspacePath: "",
1302
+ files:
1303
+ [
1304
+ ("project.csproj", """
1305
+ <Project>
1306
+ <!-- note that the attribute `Sdk="Microsoft.NET.Sdk"` is missing because we don't want the real SDK interfering -->
1307
+
1308
+ <!-- this allows custom targets to be injected for dependency detection -->
1309
+ <Import Project="$(CustomAfterMicrosoftCommonTargets)" Condition="Exists('$(CustomAfterMicrosoftCommonTargets)')" />
1310
+
1311
+ <PropertyGroup>
1312
+ <TargetFramework>net8.0</TargetFramework>
1313
+ </PropertyGroup>
1314
+
1315
+ <ItemGroup>
1316
+ <!-- we need a value in this item group with the appropriate metadata to simulate it having been added by NuGet -->
1317
+ <RuntimeCopyLocalItems Include="TestOnlyAssembly.dll" NuGetPackageId="Test.Only.Package" NuGetPackageVersion="1.0.0" />
1318
+
1319
+ <!-- this represents the assemblies being extracted from the package -->
1320
+ <Reference Include="@(RuntimeCopyLocalItems)" />
1321
+ </ItemGroup>
1322
+
1323
+ <Target Name="_HandlePackageFileConflicts">
1324
+ <!-- this target needs to exist for discovery to work -->
1325
+ <ItemGroup>
1326
+ <!-- this removal is what triggers the package lookup in the correlation file -->
1327
+ <Reference Remove="TestOnlyAssembly.dll" />
1328
+
1329
+ <!-- this addition is what's used for the lookup -->
1330
+ <Reference Include="TestOnlyAssembly.dll" NuGetPackageId="Dependabot.App.Core.Ref" NuGetPackageVersion="1.0.1" />
1331
+ </ItemGroup>
1332
+ </Target>
1333
+
1334
+ <Target Name="ResolveAssemblyReferences" DependsOnTargets="_HandlePackageFileConflicts">
1335
+ <!-- this target needs to exist for discovery to work -->
1336
+ </Target>
1337
+
1338
+ <Target Name="GenerateBuildDependencyFile">
1339
+ <!-- this target needs to exist for discovery to work -->
1340
+ <ItemGroup>
1341
+ <!-- this removal is what removes the regular package reference from the project -->
1342
+ <RuntimeCopyLocalItems Remove="TestOnlyAssembly.dll" />
1343
+ </ItemGroup>
1344
+ </Target>
1345
+
1346
+ <Target Name="ResolvePackageAssets">
1347
+ <!-- this target needs to exist for discovery to work -->
1348
+ </Target>
1349
+ </Project>
1350
+ """)
1351
+ ],
1352
+ expectedResult: new()
1353
+ {
1354
+ Path = "",
1355
+ Projects = [
1356
+ new()
1357
+ {
1358
+ FilePath = "project.csproj",
1359
+ Dependencies = [
1360
+ new("Test.Only.Package", "1.0.99", DependencyType.Unknown, TargetFrameworks: ["net8.0"], IsTransitive: true)
1361
+ ],
1362
+ Properties = [
1363
+ new("TargetFramework", "net8.0", "project.csproj")
1364
+ ],
1365
+ TargetFrameworks = ["net8.0"],
1366
+ ReferencedProjectPaths = [],
1367
+ ImportedFiles = [],
1368
+ AdditionalFiles = [],
1369
+ }
1370
+ ]
1371
+ }
1372
+ );
1373
+ }
1266
1374
  }
1267
1375
  }