dependabot-nuget 0.290.0 → 0.292.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.editorconfig +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Build.props +1 -0
  4. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +1 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +15 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +2 -2
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +1 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +2 -1
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +87 -3
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +1 -1
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +14 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +2 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +19 -1
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/BadRequirementException.cs +9 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +39 -8
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +111 -17
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +2 -2
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +2 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +19 -11
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +2 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +31 -5
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +1 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/BadRequirement.cs +10 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CommitOptions.cs +1 -1
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +3 -2
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +1 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobRepoNotFound.cs +1 -4
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +6 -2
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdateNotPossible.cs +1 -1
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +9 -3
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +3 -2
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +43 -18
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +1 -1
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +12 -1
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +0 -7
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +48 -17
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +2 -2
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +45 -7
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProjectHelper.cs +4 -4
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +60 -2
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +10 -1
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +56 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +41 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +2 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +1 -1
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +2 -1
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +76 -40
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +20 -2
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +2 -2
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.LockFile.cs +251 -0
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +6 -6
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +63 -5
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +277 -73
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/ProjectHelperTests.cs +65 -0
  59. data/helpers/lib/NuGetUpdater/global.json +1 -1
  60. data/lib/dependabot/nuget/file_fetcher.rb +1 -0
  61. data/lib/dependabot/nuget/file_parser.rb +90 -0
  62. data/lib/dependabot/nuget/language.rb +98 -0
  63. data/lib/dependabot/nuget/native_helpers.rb +25 -0
  64. data/lib/dependabot/nuget/package_manager.rb +51 -0
  65. metadata +12 -6
@@ -34,7 +34,8 @@ internal static partial class MSBuildHelper
34
34
  // Ensure MSBuild types are registered before calling a method that loads the types
35
35
  if (!IsMSBuildRegistered)
36
36
  {
37
- SidelineGlobalJsonAsync(currentDirectory, rootDirectory, () =>
37
+ var experimentsManager = new ExperimentsManager() { InstallDotnetSdks = false }; // `global.json` definitely needs to be moved for this operation
38
+ HandleGlobalJsonAsync(currentDirectory, rootDirectory, experimentsManager, () =>
38
39
  {
39
40
  var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
40
41
  MSBuildPath = defaultInstance.MSBuildPath;
@@ -44,9 +45,23 @@ internal static partial class MSBuildHelper
44
45
  }
45
46
  }
46
47
 
47
- public static async Task<T> SidelineGlobalJsonAsync<T>(string currentDirectory, string rootDirectory, Func<Task<T>> action, ILogger? logger = null, bool retainMSBuildSdks = false)
48
+ public static async Task<T> HandleGlobalJsonAsync<T>(
49
+ string currentDirectory,
50
+ string rootDirectory,
51
+ ExperimentsManager experimentsManager,
52
+ Func<Task<T>> action,
53
+ ILogger? logger = null,
54
+ bool retainMSBuildSdks = false
55
+ )
48
56
  {
49
57
  logger ??= new ConsoleLogger();
58
+ if (experimentsManager.InstallDotnetSdks)
59
+ {
60
+ logger.Info($"{nameof(ExperimentsManager.InstallDotnetSdks)} == true; retaining `global.json` contents.");
61
+ var result = await action();
62
+ return result;
63
+ }
64
+
50
65
  var candidateDirectories = PathHelper.GetAllDirectoriesToRoot(currentDirectory, rootDirectory);
51
66
  var globalJsonPaths = candidateDirectories.Select(d => Path.Combine(d, "global.json")).Where(File.Exists).Select(p => (p, p + Guid.NewGuid().ToString())).ToArray();
52
67
  foreach (var (globalJsonPath, tempGlobalJsonPath) in globalJsonPaths)
@@ -322,13 +337,13 @@ internal static partial class MSBuildHelper
322
337
  return false;
323
338
  }
324
339
 
325
- internal static async Task<bool> DependenciesAreCoherentAsync(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, ILogger logger)
340
+ internal static async Task<bool> DependenciesAreCoherentAsync(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, ExperimentsManager experimentsManager, ILogger logger)
326
341
  {
327
342
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
328
343
  try
329
344
  {
330
345
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
331
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
346
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
332
347
 
333
348
  // NU1608: Detected package version outside of dependency constraint
334
349
 
@@ -340,7 +355,7 @@ internal static partial class MSBuildHelper
340
355
  }
341
356
  }
342
357
 
343
- internal static async Task<Dependency[]?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Dependency[] update, ILogger logger)
358
+ internal static async Task<Dependency[]?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Dependency[] update, ExperimentsManager experimentsManager, ILogger logger)
344
359
  {
345
360
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
346
361
  PackageManager packageManager = new PackageManager(repoRoot, projectPath);
@@ -348,7 +363,7 @@ internal static partial class MSBuildHelper
348
363
  try
349
364
  {
350
365
  string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
351
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
366
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
352
367
 
353
368
  // Add Dependency[] packages to List<PackageToUpdate> existingPackages
354
369
  List<PackageToUpdate> existingPackages = packages
@@ -498,13 +513,13 @@ internal static partial class MSBuildHelper
498
513
  }
499
514
  }
500
515
 
501
- internal static async Task<Dependency[]?> ResolveDependencyConflictsWithBruteForce(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, ILogger logger)
516
+ internal static async Task<Dependency[]?> ResolveDependencyConflictsWithBruteForce(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, ExperimentsManager experimentsManager, ILogger logger)
502
517
  {
503
518
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
504
519
  try
505
520
  {
506
521
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
507
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
522
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
508
523
  ThrowOnUnauthenticatedFeed(stdOut);
509
524
 
510
525
  // simple cases first
@@ -529,6 +544,7 @@ internal static partial class MSBuildHelper
529
544
  foreach ((string PackageName, NuGetVersion packageVersion) in badPackagesAndVersions)
530
545
  {
531
546
  // this command dumps a JSON object with all versions of the specified package from all package sources
547
+ // not using the `dotnet` execution method because we want to force the latest MSBuild and SDK to be used
532
548
  (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["package", "search", PackageName, "--exact-match", "--format", "json"], workingDirectory: tempDirectory.FullName);
533
549
  if (exitCode != 0)
534
550
  {
@@ -591,7 +607,7 @@ internal static partial class MSBuildHelper
591
607
  return p;
592
608
  }).ToArray();
593
609
 
594
- if (await DependenciesAreCoherentAsync(repoRoot, projectPath, targetFramework, candidatePackages, logger))
610
+ if (await DependenciesAreCoherentAsync(repoRoot, projectPath, targetFramework, candidatePackages, experimentsManager, logger))
595
611
  {
596
612
  // return as soon as we find a coherent set
597
613
  return candidatePackages;
@@ -749,14 +765,23 @@ internal static partial class MSBuildHelper
749
765
  return tempProjectPath;
750
766
  }
751
767
 
752
- internal static async Task<ImmutableArray<string>> GetTargetFrameworkValuesFromProject(string repoRoot, string projectPath, ILogger logger)
768
+ internal static async Task<ImmutableArray<string>> GetTargetFrameworkValuesFromProject(string repoRoot, string projectPath, ExperimentsManager experimentsManager, ILogger logger)
753
769
  {
754
- // TODO: once the updater image has all relevant SDKs installed, we won't have to sideline global.json anymore
755
770
  var projectDirectory = Path.GetDirectoryName(projectPath)!;
756
- var (exitCode, stdOut, stdErr) = await SidelineGlobalJsonAsync(projectDirectory, repoRoot, async () =>
771
+ var (exitCode, stdOut, stdErr) = await HandleGlobalJsonAsync(projectDirectory, repoRoot, experimentsManager, async () =>
757
772
  {
758
773
  var targetsHelperPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "TargetFrameworkReporter.targets");
759
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["build", projectPath, "/t:ReportTargetFramework", $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={targetsHelperPath};CustomAfterMicrosoftCommonTargets={targetsHelperPath}"], workingDirectory: Path.GetDirectoryName(projectPath));
774
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(
775
+ [
776
+ "build",
777
+ projectPath,
778
+ "/t:ReportTargetFramework",
779
+ $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={targetsHelperPath}",
780
+ $"/p:CustomAfterMicrosoftCommonTargets={targetsHelperPath}"
781
+ ],
782
+ projectDirectory,
783
+ experimentsManager
784
+ );
760
785
  return (exitCode, stdOut, stdErr);
761
786
  });
762
787
  ThrowOnUnauthenticatedFeed(stdOut);
@@ -806,6 +831,7 @@ internal static partial class MSBuildHelper
806
831
  string projectPath,
807
832
  string targetFramework,
808
833
  IReadOnlyCollection<Dependency> packages,
834
+ ExperimentsManager experimentsManager,
809
835
  ILogger logger)
810
836
  {
811
837
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_");
@@ -814,7 +840,7 @@ internal static partial class MSBuildHelper
814
840
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
815
841
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
816
842
 
817
- var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", ["build", tempProjectPath, "/t:_ReportDependencies"], workingDirectory: tempDirectory.FullName);
843
+ var (exitCode, stdout, stderr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["build", tempProjectPath, "/t:_ReportDependencies"], tempDirectory.FullName, experimentsManager);
818
844
  ThrowOnUnauthenticatedFeed(stdout);
819
845
 
820
846
  if (exitCode == 0)
@@ -860,6 +886,7 @@ internal static partial class MSBuildHelper
860
886
  "The plugin credential provider could not acquire credentials",
861
887
  "401 (Unauthorized)",
862
888
  "error NU1301: Unable to load the service index for source",
889
+ "Response status code does not indicate success: 403",
863
890
  };
864
891
  if (unauthorizedMessageSnippets.Any(stdout.Contains))
865
892
  {
@@ -869,9 +896,13 @@ internal static partial class MSBuildHelper
869
896
 
870
897
  internal static string? GetMissingFile(string output)
871
898
  {
872
- var missingFilePattern = new Regex(@"The imported project \""(?<FilePath>.*)\"" was not found");
873
- var match = missingFilePattern.Match(output);
874
- if (match.Success)
899
+ var missingFilePatterns = new[]
900
+ {
901
+ new Regex(@"The imported project \""(?<FilePath>.*)\"" was not found"),
902
+ new Regex(@"The imported file \""(?<FilePath>.*)\"" does not exist"),
903
+ };
904
+ var match = missingFilePatterns.Select(p => p.Match(output)).Where(m => m.Success).FirstOrDefault();
905
+ if (match is not null)
875
906
  {
876
907
  return match.Groups["FilePath"].Value;
877
908
  }
@@ -4,13 +4,13 @@ namespace NuGetUpdater.Core;
4
4
 
5
5
  internal static class NuGetHelper
6
6
  {
7
- internal static async Task<bool> DownloadNuGetPackagesAsync(string repoRoot, string projectPath, IReadOnlyCollection<Dependency> packages, ILogger logger)
7
+ internal static async Task<bool> DownloadNuGetPackagesAsync(string repoRoot, string projectPath, IReadOnlyCollection<Dependency> packages, ExperimentsManager experimentsManager, ILogger logger)
8
8
  {
9
9
  var tempDirectory = Directory.CreateTempSubdirectory("msbuild_sdk_restore_");
10
10
  try
11
11
  {
12
12
  var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, logger, usePackageDownload: true);
13
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath]);
13
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
14
14
 
15
15
  return exitCode == 0;
16
16
  }
@@ -5,19 +5,57 @@ namespace NuGetUpdater.Core;
5
5
 
6
6
  public static class ProcessEx
7
7
  {
8
- public static Task<(int ExitCode, string Output, string Error)> RunAsync(string fileName, IEnumerable<string>? arguments = null, string? workingDirectory = null)
8
+ /// <summary>
9
+ /// Run the `dotnet` command with the given values. This will exclude all `MSBuild*` environment variables from the execution.
10
+ /// </summary>
11
+ public static Task<(int ExitCode, string Output, string Error)> RunDotnetWithoutMSBuildEnvironmentVariablesAsync(IEnumerable<string> arguments, string workingDirectory, ExperimentsManager experimentsManager)
12
+ {
13
+ var environmentVariablesToUnset = new List<string>();
14
+ if (experimentsManager.InstallDotnetSdks)
15
+ {
16
+ // If using the SDK specified by a `global.json` file, these environment variables need to be unset to
17
+ // allow the new process to discover the correct MSBuild binaries to load, and not load the ones that
18
+ // this process is using.
19
+ environmentVariablesToUnset.Add("MSBuildExtensionsPath");
20
+ environmentVariablesToUnset.Add("MSBuildLoadMicrosoftTargetsReadOnly");
21
+ environmentVariablesToUnset.Add("MSBUILDLOGIMPORTS");
22
+ environmentVariablesToUnset.Add("MSBuildSDKsPath");
23
+ environmentVariablesToUnset.Add("MSBUILDTARGETOUTPUTLOGGING");
24
+ environmentVariablesToUnset.Add("MSBUILD_EXE_PATH");
25
+ }
26
+
27
+ var environmentVariableOverrides = environmentVariablesToUnset.Select(name => (name, (string?)null));
28
+ return RunAsync("dotnet",
29
+ arguments,
30
+ workingDirectory,
31
+ environmentVariableOverrides
32
+ );
33
+ }
34
+
35
+ public static Task<(int ExitCode, string Output, string Error)> RunAsync(
36
+ string fileName,
37
+ IEnumerable<string>? arguments = null,
38
+ string? workingDirectory = null,
39
+ IEnumerable<(string Name, string? Value)>? environmentVariableOverrides = null
40
+ )
9
41
  {
10
42
  var tcs = new TaskCompletionSource<(int, string, string)>();
11
43
 
12
44
  var redirectInitiated = new ManualResetEventSlim();
45
+ var psi = new ProcessStartInfo(fileName, arguments ?? [])
46
+ {
47
+ UseShellExecute = false, // required to redirect output and set environment variables
48
+ RedirectStandardOutput = true,
49
+ RedirectStandardError = true,
50
+ };
51
+ foreach (var (name, value) in environmentVariableOverrides ?? [])
52
+ {
53
+ psi.EnvironmentVariables[name] = value;
54
+ }
55
+
13
56
  var process = new Process
14
57
  {
15
- StartInfo = new ProcessStartInfo(fileName, arguments ?? [])
16
- {
17
- UseShellExecute = false, // required to redirect output
18
- RedirectStandardOutput = true,
19
- RedirectStandardError = true,
20
- },
58
+ StartInfo = psi,
21
59
  EnableRaisingEvents = true
22
60
  };
23
61
 
@@ -77,11 +77,11 @@ internal static class ProjectHelper
77
77
  var itemPath = projectRootElement.Items
78
78
  .Where(i => i.ElementName.Equals("None", StringComparison.OrdinalIgnoreCase) ||
79
79
  i.ElementName.Equals("Content", StringComparison.OrdinalIgnoreCase))
80
- .Where(i => Path.GetFileName(i.Include).Equals(itemFileName, StringComparison.OrdinalIgnoreCase))
81
- .Select(i => Path.GetFullPath(Path.Combine(projectDirectory, i.Include)))
80
+ .Where(i => !string.IsNullOrEmpty(i.Include))
81
+ .Select(i => Path.GetFullPath(Path.Combine(projectDirectory, i.Include.NormalizePathToUnix())))
82
+ .Where(p => Path.GetFileName(p).Equals(itemFileName, StringComparison.OrdinalIgnoreCase))
82
83
  .Where(File.Exists)
83
- .FirstOrDefault()
84
- ?.NormalizePathToUnix();
84
+ .FirstOrDefault();
85
85
  return itemPath;
86
86
  }
87
87
 
@@ -95,6 +95,65 @@ public class CloneWorkerTests
95
95
  );
96
96
  }
97
97
 
98
+ [Fact]
99
+ public async Task JobFileParseErrorIsReported_InvalidJson()
100
+ {
101
+ // arrange
102
+ var testApiHandler = new TestApiHandler();
103
+ var testGitCommandHandler = new TestGitCommandHandler();
104
+ var cloneWorker = new CloneWorker("JOB-ID", testApiHandler, testGitCommandHandler);
105
+ using var testDirectory = new TemporaryDirectory();
106
+ var jobFilePath = Path.Combine(testDirectory.DirectoryPath, "job.json");
107
+ await File.WriteAllTextAsync(jobFilePath, "not json");
108
+
109
+ // act
110
+ var result = await cloneWorker.RunAsync(new FileInfo(jobFilePath), new DirectoryInfo(testDirectory.DirectoryPath));
111
+
112
+ // assert
113
+ Assert.Equal(1, result);
114
+ var expectedParseErrorObject = testApiHandler.ReceivedMessages.Single(m => m.Type == typeof(UnknownError));
115
+ var unknownError = (UnknownError)expectedParseErrorObject.Object;
116
+ Assert.Equal("JsonException", unknownError.Details["error-class"]);
117
+ }
118
+
119
+ [Fact]
120
+ public async Task JobFileParseErrorIsReported_BadRequirement()
121
+ {
122
+ // arrange
123
+ var testApiHandler = new TestApiHandler();
124
+ var testGitCommandHandler = new TestGitCommandHandler();
125
+ var cloneWorker = new CloneWorker("JOB-ID", testApiHandler, testGitCommandHandler);
126
+ using var testDirectory = new TemporaryDirectory();
127
+ var jobFilePath = Path.Combine(testDirectory.DirectoryPath, "job.json");
128
+
129
+ // write a job file with a valid shape, but invalid requirement
130
+ await File.WriteAllTextAsync(jobFilePath, """
131
+ {
132
+ "job": {
133
+ "source": {
134
+ "provider": "github",
135
+ "repo": "test/repo"
136
+ },
137
+ "security-advisories": [
138
+ {
139
+ "dependency-name": "Some.Dependency",
140
+ "affected-versions": ["not a valid requirement"]
141
+ }
142
+ ]
143
+ }
144
+ }
145
+ """);
146
+
147
+ // act
148
+ var result = await cloneWorker.RunAsync(new FileInfo(jobFilePath), new DirectoryInfo(testDirectory.DirectoryPath));
149
+
150
+ // assert
151
+ Assert.Equal(1, result);
152
+ var expectedParseErrorObject = testApiHandler.ReceivedMessages.Single(m => m.Type == typeof(BadRequirement));
153
+ var badRequirement = (BadRequirement)expectedParseErrorObject.Object;
154
+ Assert.Equal("not a valid requirement", badRequirement.Details["message"]);
155
+ }
156
+
98
157
  private class TestGitCommandHandlerWithOutputs : TestGitCommandHandler
99
158
  {
100
159
  private readonly string _stdout;
@@ -134,8 +193,7 @@ public class CloneWorkerTests
134
193
  // arrange
135
194
  var testApiHandler = new TestApiHandler();
136
195
  testGitCommandHandler ??= new TestGitCommandHandler();
137
- var testLogger = new TestLogger();
138
- var worker = new CloneWorker(testApiHandler, testGitCommandHandler, testLogger);
196
+ var worker = new CloneWorker("TEST-JOB-ID", testApiHandler, testGitCommandHandler);
139
197
 
140
198
  // act
141
199
  var job = new Job()
@@ -45,7 +45,16 @@ public class DiscoveryWorkerTestBase : TestBase
45
45
  ValidateProjectResults(expectedResult.Projects, actualResult.Projects, experimentsManager);
46
46
  Assert.Equal(expectedResult.ExpectedProjectCount ?? expectedResult.Projects.Length, actualResult.Projects.Length);
47
47
  Assert.Equal(expectedResult.ErrorType, actualResult.ErrorType);
48
- Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
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
58
 
50
59
  return;
51
60
 
@@ -115,5 +115,61 @@ public partial class DiscoveryWorkerTests
115
115
  }
116
116
  );
117
117
  }
118
+
119
+ [Theory]
120
+ [InlineData(true)]
121
+ [InlineData(false)]
122
+ public async Task DiscoveryIsMergedWithPackageReferences(bool useDirectDiscovery)
123
+ {
124
+ await TestDiscoveryAsync(
125
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = useDirectDiscovery },
126
+ packages:
127
+ [
128
+ MockNuGetPackage.CreateSimplePackage("Package.A", "1.0.0", "net46"),
129
+ MockNuGetPackage.CreateSimplePackage("Package.B", "2.0.0", "net46"),
130
+ ],
131
+ workspacePath: "src",
132
+ files: [
133
+ ("src/myproj.csproj", """
134
+ <Project Sdk="Microsoft.NET.Sdk">
135
+ <PropertyGroup>
136
+ <TargetFramework>net46</TargetFramework>
137
+ </PropertyGroup>
138
+ <ItemGroup>
139
+ <None Include="..\unexpected-directory\packages.config" />
140
+ <PackageReference Include="Package.B" Version="2.0.0" />
141
+ </ItemGroup>
142
+ </Project>
143
+ """),
144
+ ("unexpected-directory/packages.config", """
145
+ <?xml version="1.0" encoding="utf-8"?>
146
+ <packages>
147
+ <package id="Package.A" version="1.0.0" targetFramework="net46" />
148
+ </packages>
149
+ """),
150
+ ],
151
+ expectedResult: new()
152
+ {
153
+ Path = "src",
154
+ Projects = [
155
+ new()
156
+ {
157
+ FilePath = "myproj.csproj",
158
+ Properties = [new("TargetFramework", "net46", "src/myproj.csproj")],
159
+ TargetFrameworks = ["net46"],
160
+ Dependencies = [
161
+ new("Package.A", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
162
+ new("Package.B", "2.0.0", DependencyType.PackageReference, IsDirect: true, TargetFrameworks: ["net46"]),
163
+ ],
164
+ ReferencedProjectPaths = [],
165
+ ImportedFiles = [],
166
+ AdditionalFiles = [
167
+ "../unexpected-directory/packages.config"
168
+ ],
169
+ }
170
+ ],
171
+ }
172
+ );
173
+ }
118
174
  }
119
175
  }
@@ -1094,6 +1094,47 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
1094
1094
  );
1095
1095
  }
1096
1096
 
1097
+ [Theory]
1098
+ [InlineData(true)]
1099
+ [InlineData(false)]
1100
+ public async Task DiscoveryReportsDependencyFileNotParseable(bool useDirectDiscovery)
1101
+ {
1102
+ var experimentsManager = new ExperimentsManager() { UseDirectDiscovery = useDirectDiscovery };
1103
+ await TestDiscoveryAsync(
1104
+ experimentsManager: experimentsManager,
1105
+ workspacePath: "",
1106
+ files:
1107
+ [
1108
+ ("project.csproj", """
1109
+ <Project Sdk="Microsoft.NET.Sdk">
1110
+ <PropertyGroup>
1111
+ <TargetFramework>net8.0</TargetFramework>
1112
+ </PropertyGroup>
1113
+ <ItemGroup>
1114
+ <PackageReference Include="Some.Package" Version="1.2.3" />
1115
+ </ItemGroup>
1116
+ </Project>
1117
+ """),
1118
+ ("project2.csproj", """
1119
+ <Project Sdk="Microsoft.NET.Sdk">
1120
+ <PropertyGroup>
1121
+ <TargetFramework>net8.0</TargetFramework>
1122
+ </PropertyGroup>
1123
+ <ItemGroup>
1124
+ <PackageReference: Include="Some.Package2" Version="1.2.3" />
1125
+ </ItemGroup>
1126
+ </Project>
1127
+ """),
1128
+ ],
1129
+ expectedResult: new()
1130
+ {
1131
+ Path = "",
1132
+ Projects = [],
1133
+ ErrorType = ErrorType.DependencyFileNotParseable,
1134
+ ErrorDetails = "project2.csproj",
1135
+ });
1136
+ }
1137
+
1097
1138
  [Fact]
1098
1139
  public async Task ResultFileHasCorrectShapeForAuthenticationFailure()
1099
1140
  {
@@ -12,6 +12,7 @@ public record ExpectedWorkspaceDiscoveryResult : NativeResult
12
12
  public int? ExpectedProjectCount { get; init; }
13
13
  public ExpectedDependencyDiscoveryResult? GlobalJson { get; init; }
14
14
  public ExpectedDependencyDiscoveryResult? DotNetToolsJson { get; init; }
15
+ public string? ErrorDetailsPattern { get; init; } = null;
15
16
  }
16
17
 
17
18
  public record ExpectedSdkProjectDiscoveryResult : ExpectedDependencyDiscoveryResult
@@ -21,6 +22,7 @@ public record ExpectedSdkProjectDiscoveryResult : ExpectedDependencyDiscoveryRes
21
22
  public required ImmutableArray<string> ReferencedProjectPaths { get; init; }
22
23
  public required ImmutableArray<string> ImportedFiles { get; init; }
23
24
  public required ImmutableArray<string> AdditionalFiles { get; init; }
25
+ public string? ErrorDetails { get; init; }
24
26
  }
25
27
 
26
28
  public record ExpectedDependencyDiscoveryResult : IDiscoveryResultWithDependencies
@@ -488,7 +488,7 @@ public class SdkProjectDiscoveryTests : DiscoveryWorkerTestBase
488
488
  var logger = new TestLogger();
489
489
  var fullProjectPath = Path.Combine(testDirectory.DirectoryPath, projectPath);
490
490
  var experimentsManager = new ExperimentsManager() { UseDirectDiscovery = true }; // the following method is direct discovery; this just makes the call to Validate... happy
491
- var projectDiscovery = await SdkProjectDiscovery.DiscoverWithBinLogAsync(testDirectory.DirectoryPath, Path.GetDirectoryName(fullProjectPath)!, fullProjectPath, logger);
491
+ var projectDiscovery = await SdkProjectDiscovery.DiscoverWithBinLogAsync(testDirectory.DirectoryPath, Path.GetDirectoryName(fullProjectPath)!, fullProjectPath, experimentsManager, logger);
492
492
  ValidateProjectResults(expectedProjects, projectDiscovery, experimentsManager);
493
493
  }
494
494
  }
@@ -318,7 +318,8 @@ namespace NuGetUpdater.Core.Test
318
318
  </Project>
319
319
  """
320
320
  );
321
- var (exitCode, stdout, stderr) = ProcessEx.RunAsync("dotnet", ["msbuild", projectPath, "/t:_ReportCurrentSdkVersion"]).Result;
321
+ var experimentsManager = new ExperimentsManager();
322
+ var (exitCode, stdout, stderr) = ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["msbuild", projectPath, "/t:_ReportCurrentSdkVersion"], projectDir.FullName, experimentsManager).Result;
322
323
  if (exitCode != 0)
323
324
  {
324
325
  throw new Exception($"Failed to report the current SDK version:\n{stdout}\n{stderr}");
@@ -1733,7 +1733,7 @@ public class RunWorkerTests
1733
1733
  analyzeWorker ??= new AnalyzeWorker(experimentsManager, logger);
1734
1734
  updaterWorker ??= new UpdaterWorker(experimentsManager, logger);
1735
1735
 
1736
- var worker = new RunWorker(testApiHandler, discoveryWorker, analyzeWorker, updaterWorker, logger);
1736
+ var worker = new RunWorker("TEST-JOB-ID", testApiHandler, discoveryWorker, analyzeWorker, updaterWorker, logger);
1737
1737
  var repoContentsPathDirectoryInfo = new DirectoryInfo(tempDirectory.DirectoryPath);
1738
1738
  var actualResult = await worker.RunAsync(job, repoContentsPathDirectoryInfo, "TEST-COMMIT-SHA");
1739
1739
  var actualApiMessages = testApiHandler.ReceivedMessages.ToArray();