dependabot-nuget 0.252.0 → 0.254.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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +27 -9
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +1 -1
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +66 -65
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +21 -5
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +44 -6
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +1 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +4 -3
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +181 -93
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +18 -13
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +24 -6
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +18 -10
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +25 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs +10 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +1 -8
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +1 -1
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +0 -5
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +0 -5
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +0 -5
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +0 -5
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +55 -4
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +30 -23
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs +57 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +44 -9
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +1 -1
  26. data/lib/dependabot/nuget/discovery/discovery_json_reader.rb +0 -2
  27. data/lib/dependabot/nuget/file_parser.rb +14 -6
  28. metadata +16 -29
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
2
2
  using System.Diagnostics.CodeAnalysis;
3
3
  using System.Text;
4
4
  using System.Text.Json;
5
+ using System.Text.Json.Nodes;
5
6
  using System.Text.RegularExpressions;
6
7
  using System.Xml;
7
8
 
@@ -13,6 +14,7 @@ using Microsoft.Build.Locator;
13
14
  using Microsoft.Extensions.FileSystemGlobbing;
14
15
 
15
16
  using NuGet.Configuration;
17
+ using NuGet.Versioning;
16
18
 
17
19
  using NuGetUpdater.Core.Utilities;
18
20
 
@@ -24,106 +26,34 @@ internal static partial class MSBuildHelper
24
26
 
25
27
  public static bool IsMSBuildRegistered => MSBuildPath.Length > 0;
26
28
 
27
- static MSBuildHelper()
28
- {
29
- RegisterMSBuild();
30
- }
31
-
32
- public static void RegisterMSBuild()
29
+ public static void RegisterMSBuild(string currentDirectory, string rootDirectory)
33
30
  {
34
31
  // Ensure MSBuild types are registered before calling a method that loads the types
35
32
  if (!IsMSBuildRegistered)
36
33
  {
37
- var globalJsonPath = "global.json";
38
- var tempGlobalJsonPath = globalJsonPath + Guid.NewGuid().ToString();
39
- var globalJsonExists = File.Exists(globalJsonPath);
40
- try
34
+ var candidateDirectories = PathHelper.GetAllDirectoriesToRoot(currentDirectory, rootDirectory);
35
+ var globalJsonPaths = candidateDirectories.Select(d => Path.Combine(d, "global.json")).Where(File.Exists).Select(p => (p, p + Guid.NewGuid().ToString())).ToArray();
36
+ foreach (var (globalJsonPath, tempGlobalJsonPath) in globalJsonPaths)
41
37
  {
42
- if (globalJsonExists)
43
- {
44
- Console.WriteLine("Temporarily removing `global.json` for MSBuild detection.");
45
- File.Move(globalJsonPath, tempGlobalJsonPath);
46
- }
38
+ Console.WriteLine($"Temporarily removing `global.json` from `{Path.GetDirectoryName(globalJsonPath)}` for MSBuild detection.");
39
+ File.Move(globalJsonPath, tempGlobalJsonPath);
40
+ }
47
41
 
42
+ try
43
+ {
48
44
  var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
49
45
  MSBuildPath = defaultInstance.MSBuildPath;
50
46
  MSBuildLocator.RegisterInstance(defaultInstance);
51
47
  }
52
48
  finally
53
49
  {
54
- if (globalJsonExists)
55
- {
56
- Console.WriteLine("Restoring `global.json` after MSBuild detection.");
57
- File.Move(tempGlobalJsonPath, globalJsonPath);
58
- }
59
- }
60
- }
61
- }
62
-
63
- public static string[] GetTargetFrameworkMonikers(ImmutableArray<ProjectBuildFile> buildFiles)
64
- {
65
- HashSet<string> targetFrameworkValues = new(StringComparer.OrdinalIgnoreCase);
66
- Dictionary<string, Property> propertyInfo = new(StringComparer.OrdinalIgnoreCase);
67
-
68
- foreach (var buildFile in buildFiles)
69
- {
70
- var projectRoot = CreateProjectRootElement(buildFile);
71
-
72
- foreach (var property in projectRoot.Properties)
73
- {
74
- if (property.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase) ||
75
- property.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase))
76
- {
77
- if (buildFile.IsOutsideBasePath)
78
- {
79
- continue;
80
- }
81
-
82
- foreach (var tfm in property.Value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
83
- {
84
- targetFrameworkValues.Add(tfm);
85
- }
86
- }
87
- else if (property.Name.Equals("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase))
50
+ foreach (var (globalJsonpath, tempGlobalJsonPath) in globalJsonPaths)
88
51
  {
89
- if (buildFile.IsOutsideBasePath)
90
- {
91
- continue;
92
- }
93
-
94
- // For packages.config projects that use TargetFrameworkVersion, we need to convert it to TargetFramework
95
- targetFrameworkValues.Add($"net{property.Value.TrimStart('v').Replace(".", "")}");
52
+ Console.WriteLine($"Restoring `global.json` to `{Path.GetDirectoryName(globalJsonpath)}` after MSBuild discovery.");
53
+ File.Move(tempGlobalJsonPath, globalJsonpath);
96
54
  }
97
- else
98
- {
99
- propertyInfo[property.Name] = new(property.Name, property.Value, buildFile.RelativePath);
100
- }
101
- }
102
- }
103
-
104
- HashSet<string> targetFrameworks = new(StringComparer.OrdinalIgnoreCase);
105
-
106
- foreach (var targetFrameworkValue in targetFrameworkValues)
107
- {
108
- var (resultType, _, tfms, _, errorMessage) =
109
- GetEvaluatedValue(targetFrameworkValue, propertyInfo, propertiesToIgnore: ["TargetFramework", "TargetFrameworks"]);
110
- if (resultType != EvaluationResultType.Success)
111
- {
112
- continue;
113
- }
114
-
115
- if (string.IsNullOrEmpty(tfms))
116
- {
117
- continue;
118
- }
119
-
120
- foreach (var tfm in tfms.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
121
- {
122
- targetFrameworks.Add(tfm);
123
55
  }
124
56
  }
125
-
126
- return targetFrameworks.ToArray();
127
57
  }
128
58
 
129
59
  public static IEnumerable<string> GetProjectPathsFromSolution(string solutionPath)
@@ -379,6 +309,124 @@ internal static partial class MSBuildHelper
379
309
  }
380
310
  }
381
311
 
312
+ internal static async Task<Dependency[]?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger logger)
313
+ {
314
+ var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
315
+ try
316
+ {
317
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
318
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
319
+
320
+ // simple cases first
321
+ // if restore failed, nothing we can do
322
+ if (exitCode != 0)
323
+ {
324
+ return null;
325
+ }
326
+
327
+ // if no problems found, just return the current set
328
+ if (!stdOut.Contains("NU1608"))
329
+ {
330
+ return packages;
331
+ }
332
+
333
+ // now it gets complicated; look for the packages with issues
334
+ MatchCollection matches = PackageIncompatibilityWarningPattern().Matches(stdOut);
335
+ (string, NuGetVersion)[] badPackagesAndVersions = matches.Select(m => (m.Groups["PackageName"].Value, NuGetVersion.Parse(m.Groups["PackageVersion"].Value))).ToArray();
336
+ Dictionary<string, HashSet<NuGetVersion>> badPackagesAndCandidateVersionsDictionary = new(StringComparer.OrdinalIgnoreCase);
337
+
338
+ // and for each of those packages, find all versions greater than the one that's currently installed
339
+ foreach ((string packageName, NuGetVersion packageVersion) in badPackagesAndVersions)
340
+ {
341
+ // this command dumps a JSON object with all versions of the specified package from all package sources
342
+ (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"package search {packageName} --exact-match --format json", workingDirectory: tempDirectory.FullName);
343
+ if (exitCode != 0)
344
+ {
345
+ continue;
346
+ }
347
+
348
+ // ensure collection exists
349
+ if (!badPackagesAndCandidateVersionsDictionary.ContainsKey(packageName))
350
+ {
351
+ badPackagesAndCandidateVersionsDictionary.Add(packageName, new HashSet<NuGetVersion>());
352
+ }
353
+
354
+ HashSet<NuGetVersion> foundVersions = badPackagesAndCandidateVersionsDictionary[packageName];
355
+
356
+ var json = JsonHelper.ParseNode(stdOut);
357
+ if (json?["searchResult"] is JsonArray searchResults)
358
+ {
359
+ foreach (var searchResult in searchResults)
360
+ {
361
+ if (searchResult?["packages"] is JsonArray packagesArray)
362
+ {
363
+ foreach (var package in packagesArray)
364
+ {
365
+ // in 8.0.xxx SDKs, the package version is in the `latestVersion` property, but in 9.0.xxx, it's `version`
366
+ var packageVersionProperty = package?["version"] ?? package?["latestVersion"];
367
+ if (packageVersionProperty is JsonValue latestVersion &&
368
+ latestVersion.GetValueKind() == JsonValueKind.String &&
369
+ NuGetVersion.TryParse(latestVersion.ToString(), out var nugetVersion) &&
370
+ nugetVersion > packageVersion)
371
+ {
372
+ foundVersions.Add(nugetVersion);
373
+ }
374
+ }
375
+ }
376
+ }
377
+ }
378
+ }
379
+
380
+ // generate all possible combinations
381
+ (string Key, NuGetVersion v)[][] expandedLists = badPackagesAndCandidateVersionsDictionary.Select(kvp => kvp.Value.Order().Select(v => (kvp.Key, v)).ToArray()).ToArray();
382
+ IEnumerable<(string PackageName, NuGetVersion PackageVersion)>[] product = expandedLists.CartesianProduct().ToArray();
383
+
384
+ // FUTURE WORK: pre-filter individual known package incompatibilities to reduce the number of combinations, e.g., if Package.A v1.0.0
385
+ // is incompatible with Package.B v2.0.0, then remove _all_ combinations with that pair
386
+
387
+ // this is the slow part
388
+ foreach (IEnumerable<(string PackageName, NuGetVersion PackageVersion)> candidateSet in product)
389
+ {
390
+ // rebuild candidate dependency list with the relevant versions
391
+ Dictionary<string, NuGetVersion> packageVersions = candidateSet.ToDictionary(candidateSet => candidateSet.PackageName, candidateSet => candidateSet.PackageVersion);
392
+ Dependency[] candidatePackages = packages.Select(p =>
393
+ {
394
+ if (packageVersions.TryGetValue(p.Name, out var version))
395
+ {
396
+ // create a new dependency with the updated version
397
+ return new Dependency(p.Name, version.ToString(), p.Type, IsDevDependency: p.IsDevDependency, IsOverride: p.IsOverride, IsUpdate: p.IsUpdate);
398
+ }
399
+
400
+ // not the dependency we're looking for, use whatever it already was in this set
401
+ return p;
402
+ }).ToArray();
403
+
404
+ if (await DependenciesAreCoherentAsync(repoRoot, projectPath, targetFramework, candidatePackages, logger))
405
+ {
406
+ // return as soon as we find a coherent set
407
+ return candidatePackages;
408
+ }
409
+ }
410
+
411
+ // no package resolution set found
412
+ return null;
413
+ }
414
+ finally
415
+ {
416
+ tempDirectory.Delete(recursive: true);
417
+ }
418
+ }
419
+
420
+ // fully expand all possible combinations using the algorithm from here:
421
+ // https://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
422
+ private static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
423
+ {
424
+ IEnumerable<IEnumerable<T>> emptyProduct = [[]];
425
+ return sequences.Aggregate(emptyProduct, (accumulator, sequence) => from accseq in accumulator
426
+ from item in sequence
427
+ select accseq.Concat([item]));
428
+ }
429
+
382
430
  private static ProjectRootElement CreateProjectRootElement(ProjectBuildFile buildFile)
383
431
  {
384
432
  var xmlString = buildFile.Contents.ToFullString();
@@ -569,7 +617,7 @@ internal static partial class MSBuildHelper
569
617
  return directoryPackagesPropsPath is not null;
570
618
  }
571
619
 
572
- internal static async Task<ImmutableArray<ProjectBuildFile>> LoadBuildFilesAsync(string repoRootPath, string projectPath, bool includeSdkPropsAndTargets = false)
620
+ internal static async Task<(ImmutableArray<ProjectBuildFile> ProjectBuildFiles, string[] TargetFrameworks)> LoadBuildFilesAndTargetFrameworksAsync(string repoRootPath, string projectPath)
573
621
  {
574
622
  var buildFileList = new List<string>
575
623
  {
@@ -579,6 +627,7 @@ internal static partial class MSBuildHelper
579
627
  // a global.json file might cause problems with the dotnet msbuild command; create a safe version temporarily
580
628
  TryGetGlobalJsonPath(repoRootPath, projectPath, out var globalJsonPath);
581
629
  var safeGlobalJsonName = $"{globalJsonPath}{Guid.NewGuid()}";
630
+ HashSet<string> targetFrameworks = new(StringComparer.OrdinalIgnoreCase);
582
631
 
583
632
  try
584
633
  {
@@ -607,16 +656,51 @@ internal static partial class MSBuildHelper
607
656
  // load the project even if it imports a file that doesn't exist (e.g. a file that's generated at restore
608
657
  // or build time).
609
658
  using var projectCollection = new ProjectCollection(); // do this in a one-off instance and don't pollute the global collection
610
- var project = Project.FromFile(projectPath, new ProjectOptions
659
+ Project project = Project.FromFile(projectPath, new ProjectOptions
611
660
  {
612
661
  LoadSettings = ProjectLoadSettings.IgnoreMissingImports,
613
662
  ProjectCollection = projectCollection,
614
663
  });
615
664
  buildFileList.AddRange(project.Imports.Select(i => i.ImportedProject.FullPath.NormalizePathToUnix()));
665
+
666
+ // use the MSBuild-evaluated value so we don't have to try to manually parse XML
667
+ IEnumerable<ProjectProperty> targetFrameworkProperties = project.Properties.Where(p => p.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase)).ToList();
668
+ IEnumerable<ProjectProperty> targetFrameworksProperties = project.Properties.Where(p => p.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase)).ToList();
669
+ IEnumerable<ProjectProperty> targetFrameworkVersionProperties = project.Properties.Where(p => p.Name.Equals("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase)).ToList();
670
+ foreach (ProjectProperty tfm in targetFrameworkProperties)
671
+ {
672
+ if (!string.IsNullOrWhiteSpace(tfm.EvaluatedValue))
673
+ {
674
+ targetFrameworks.Add(tfm.EvaluatedValue);
675
+ }
676
+ }
677
+
678
+ foreach (ProjectProperty tfms in targetFrameworksProperties)
679
+ {
680
+ foreach (string tfmValue in tfms.EvaluatedValue.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
681
+ {
682
+ targetFrameworks.Add(tfmValue);
683
+ }
684
+ }
685
+
686
+ if (targetFrameworks.Count == 0)
687
+ {
688
+ // Only try this if we haven't been able to resolve anything yet. This is because deep in the SDK, a
689
+ // `TargetFramework` of `netstandard2.0` (eventually) gets turned into `v2.0` and we don't want to
690
+ // interpret that as a .NET Framework 2.0 project.
691
+ foreach (ProjectProperty tfvm in targetFrameworkVersionProperties)
692
+ {
693
+ // `v0.0` is an error case where no TFM could be evaluated
694
+ if (tfvm.EvaluatedValue != "v0.0")
695
+ {
696
+ targetFrameworks.Add($"net{tfvm.EvaluatedValue.TrimStart('v').Replace(".", "")}");
697
+ }
698
+ }
699
+ }
616
700
  }
617
701
  catch (InvalidProjectFileException)
618
702
  {
619
- return [];
703
+ return ([], []);
620
704
  }
621
705
  finally
622
706
  {
@@ -627,18 +711,22 @@ internal static partial class MSBuildHelper
627
711
  }
628
712
 
629
713
  var repoRootPathPrefix = repoRootPath.NormalizePathToUnix() + "/";
630
- var buildFiles = includeSdkPropsAndTargets
631
- ? buildFileList.Distinct()
632
- : buildFileList
633
- .Where(f => f.StartsWith(repoRootPathPrefix, StringComparison.OrdinalIgnoreCase))
634
- .Distinct();
714
+ var buildFiles = buildFileList
715
+ .Where(f => f.StartsWith(repoRootPathPrefix, StringComparison.OrdinalIgnoreCase))
716
+ .Distinct();
635
717
  var result = buildFiles
636
718
  .Where(File.Exists)
637
719
  .Select(path => ProjectBuildFile.Open(repoRootPath, path))
638
720
  .ToImmutableArray();
639
- return result;
721
+ return (result, targetFrameworks.ToArray());
640
722
  }
641
723
 
642
724
  [GeneratedRegex("^\\s*NuGetData::Package=(?<PackageName>[^,]+), Version=(?<PackageVersion>.+)$")]
643
725
  private static partial Regex PackagePattern();
726
+
727
+ // Example output:
728
+ // NU1608: Detected package version outside of dependency constraint: SpecFlow.Tools.MsBuild.Generation 3.3.30 requires SpecFlow(= 3.3.30) but version SpecFlow 3.9.74 was resolved.
729
+ // PackageName-|+++++++++++++++++++++++++++++++| |++++|-PackageVersion
730
+ [GeneratedRegex("NU1608: [^:]+: (?<PackageName>[^ ]+) (?<PackageVersion>[^ ]+)")]
731
+ private static partial Regex PackageIncompatibilityWarningPattern();
644
732
  }
@@ -34,20 +34,11 @@ internal static class PathHelper
34
34
  public static string GetFullPathFromRelative(string rootPath, string relativePath)
35
35
  => Path.GetFullPath(JoinPath(rootPath, relativePath.NormalizePathToUnix()));
36
36
 
37
- /// <summary>
38
- /// Check in every directory from <paramref name="initialPath"/> up to <paramref name="rootPath"/> for the file specified in <paramref name="fileName"/>.
39
- /// </summary>
40
- /// <returns>The path of the found file or null.</returns>
41
- public static string? GetFileInDirectoryOrParent(string initialPath, string rootPath, string fileName, bool caseSensitive = true)
37
+ public static string[] GetAllDirectoriesToRoot(string initialDirectoryPath, string rootDirectoryPath)
42
38
  {
43
- if (File.Exists(initialPath))
44
- {
45
- initialPath = Path.GetDirectoryName(initialPath)!;
46
- }
47
-
48
39
  var candidatePaths = new List<string>();
49
- var rootDirectory = new DirectoryInfo(rootPath);
50
- var candidateDirectory = new DirectoryInfo(initialPath);
40
+ var rootDirectory = new DirectoryInfo(rootDirectoryPath);
41
+ var candidateDirectory = new DirectoryInfo(initialDirectoryPath);
51
42
  while (candidateDirectory.FullName != rootDirectory.FullName)
52
43
  {
53
44
  candidatePaths.Add(candidateDirectory.FullName);
@@ -58,8 +49,22 @@ internal static class PathHelper
58
49
  }
59
50
  }
60
51
 
61
- candidatePaths.Add(rootPath);
52
+ candidatePaths.Add(rootDirectoryPath);
53
+ return candidatePaths.ToArray();
54
+ }
55
+
56
+ /// <summary>
57
+ /// Check in every directory from <paramref name="initialPath"/> up to <paramref name="rootPath"/> for the file specified in <paramref name="fileName"/>.
58
+ /// </summary>
59
+ /// <returns>The path of the found file or null.</returns>
60
+ public static string? GetFileInDirectoryOrParent(string initialPath, string rootPath, string fileName, bool caseSensitive = true)
61
+ {
62
+ if (File.Exists(initialPath))
63
+ {
64
+ initialPath = Path.GetDirectoryName(initialPath)!;
65
+ }
62
66
 
67
+ var candidatePaths = GetAllDirectoriesToRoot(initialPath, rootPath);
63
68
  foreach (var candidatePath in candidatePaths)
64
69
  {
65
70
  try
@@ -1,4 +1,5 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Diagnostics.CodeAnalysis;
2
3
  using System.Text.Json;
3
4
 
4
5
  using NuGetUpdater.Core.Discover;
@@ -29,7 +30,7 @@ public class DiscoveryWorkerTestBase
29
30
  protected static void ValidateWorkspaceResult(ExpectedWorkspaceDiscoveryResult expectedResult, WorkspaceDiscoveryResult actualResult)
30
31
  {
31
32
  Assert.NotNull(actualResult);
32
- Assert.Equal(expectedResult.FilePath, actualResult.FilePath);
33
+ Assert.Equal(expectedResult.FilePath.NormalizePathToUnix(), actualResult.FilePath.NormalizePathToUnix());
33
34
  ValidateDirectoryPackagesProps(expectedResult.DirectoryPackagesProps, actualResult.DirectoryPackagesProps);
34
35
  ValidateResultWithDependencies(expectedResult.GlobalJson, actualResult.GlobalJson);
35
36
  ValidateResultWithDependencies(expectedResult.DotNetToolsJson, actualResult.DotNetToolsJson);
@@ -50,7 +51,7 @@ public class DiscoveryWorkerTestBase
50
51
  Assert.NotNull(actualResult);
51
52
  }
52
53
 
53
- Assert.Equal(expectedResult.FilePath, actualResult.FilePath);
54
+ Assert.Equal(expectedResult.FilePath.NormalizePathToUnix(), actualResult.FilePath.NormalizePathToUnix());
54
55
  ValidateDependencies(expectedResult.Dependencies, actualResult.Dependencies);
55
56
  Assert.Equal(expectedResult.ExpectedDependencyCount ?? expectedResult.Dependencies.Length, actualResult.Dependencies.Length);
56
57
  }
@@ -64,12 +65,12 @@ public class DiscoveryWorkerTestBase
64
65
 
65
66
  foreach (var expectedProject in expectedProjects)
66
67
  {
67
- var actualProject = actualProjects.Single(p => p.FilePath == expectedProject.FilePath);
68
+ var actualProject = actualProjects.Single(p => p.FilePath.NormalizePathToUnix() == expectedProject.FilePath.NormalizePathToUnix());
68
69
 
69
- Assert.Equal(expectedProject.FilePath, actualProject.FilePath);
70
- AssertEx.Equal(expectedProject.Properties, actualProject.Properties);
70
+ Assert.Equal(expectedProject.FilePath.NormalizePathToUnix(), actualProject.FilePath.NormalizePathToUnix());
71
+ AssertEx.Equal(expectedProject.Properties, actualProject.Properties, PropertyComparer.Instance);
71
72
  AssertEx.Equal(expectedProject.TargetFrameworks, actualProject.TargetFrameworks);
72
- AssertEx.Equal(expectedProject.ReferencedProjectPaths, actualProject.ReferencedProjectPaths);
73
+ AssertEx.Equal(expectedProject.ReferencedProjectPaths.Select(PathHelper.NormalizePathToUnix), actualProject.ReferencedProjectPaths.Select(PathHelper.NormalizePathToUnix));
73
74
  ValidateDependencies(expectedProject.Dependencies, actualProject.Dependencies);
74
75
  Assert.Equal(expectedProject.ExpectedDependencyCount ?? expectedProject.Dependencies.Length, actualProject.Dependencies.Length);
75
76
  }
@@ -114,4 +115,21 @@ public class DiscoveryWorkerTestBase
114
115
  var resultJson = await File.ReadAllTextAsync(resultPath);
115
116
  return JsonSerializer.Deserialize<WorkspaceDiscoveryResult>(resultJson, DiscoveryWorker.SerializerOptions)!;
116
117
  }
118
+
119
+ internal class PropertyComparer : IEqualityComparer<Property>
120
+ {
121
+ public static PropertyComparer Instance { get; } = new();
122
+
123
+ public bool Equals(Property? x, Property? y)
124
+ {
125
+ return x?.Name == y?.Name &&
126
+ x?.Value == y?.Value &&
127
+ x?.SourceFilePath.NormalizePathToUnix() == y?.SourceFilePath.NormalizePathToUnix();
128
+ }
129
+
130
+ public int GetHashCode([DisallowNull] Property obj)
131
+ {
132
+ throw new NotImplementedException();
133
+ }
134
+ }
117
135
  }
@@ -25,11 +25,14 @@ public partial class DiscoveryWorkerTests
25
25
  <package id="NuGet.Core" version="2.11.1" targetFramework="net46" />
26
26
  <package id="NuGet.Server" version="2.11.2" targetFramework="net46" />
27
27
  <package id="RouteMagic" version="1.3" targetFramework="net46" />
28
- <package id="WebActivatorEx" version="2.1.0" targetFramework="net46"></package>
28
+ <package id="WebActivatorEx" version="2.1.0" targetFramework="net46" />
29
29
  </packages>
30
30
  """),
31
31
  ("myproj.csproj", """
32
32
  <Project>
33
+ <PropertyGroup>
34
+ <TargetFramework>net46</TargetFramework>
35
+ </PropertyGroup>
33
36
  </Project>
34
37
  """)
35
38
  ],
@@ -40,16 +43,21 @@ public partial class DiscoveryWorkerTests
40
43
  new()
41
44
  {
42
45
  FilePath = "myproj.csproj",
46
+ Properties = [
47
+ new("TargetFramework", "net46", "myproj.csproj"),
48
+ ],
49
+ TargetFrameworks = ["net46"],
43
50
  Dependencies = [
44
- new("Microsoft.CodeDom.Providers.DotNetCompilerPlatform", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: []),
45
- new("Microsoft.Net.Compilers", "1.0.1", DependencyType.PackagesConfig, TargetFrameworks: []),
46
- new("Microsoft.Web.Infrastructure", "1.0.0.0", DependencyType.PackagesConfig, TargetFrameworks: []),
47
- new("Microsoft.Web.Xdt", "2.1.1", DependencyType.PackagesConfig, TargetFrameworks: []),
48
- new("Newtonsoft.Json", "8.0.3", DependencyType.PackagesConfig, TargetFrameworks: []),
49
- new("NuGet.Core", "2.11.1", DependencyType.PackagesConfig, TargetFrameworks: []),
50
- new("NuGet.Server", "2.11.2", DependencyType.PackagesConfig, TargetFrameworks: []),
51
- new("RouteMagic", "1.3", DependencyType.PackagesConfig, TargetFrameworks: []),
52
- new("WebActivatorEx", "2.1.0", DependencyType.PackagesConfig, TargetFrameworks: []),
51
+ new("Microsoft.NETFramework.ReferenceAssemblies", "1.0.3", DependencyType.Unknown, TargetFrameworks: ["net46"], IsTransitive: true),
52
+ new("Microsoft.CodeDom.Providers.DotNetCompilerPlatform", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
53
+ new("Microsoft.Net.Compilers", "1.0.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
54
+ new("Microsoft.Web.Infrastructure", "1.0.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
55
+ new("Microsoft.Web.Xdt", "2.1.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
56
+ new("Newtonsoft.Json", "8.0.3", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
57
+ new("NuGet.Core", "2.11.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
58
+ new("NuGet.Server", "2.11.2", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
59
+ new("RouteMagic", "1.3", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
60
+ new("WebActivatorEx", "2.1.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
53
61
  ],
54
62
  }
55
63
  ],
@@ -376,5 +376,30 @@ public partial class DiscoveryWorkerTests
376
376
  ],
377
377
  });
378
378
  }
379
+
380
+ [Fact]
381
+
382
+ public async Task NoDependenciesReturnedIfNoTargetFrameworkCanBeResolved()
383
+ {
384
+ await TestDiscoveryAsync(
385
+ workspacePath: "",
386
+ files: [
387
+ ("myproj.csproj", """
388
+ <Project Sdk="Microsoft.NET.Sdk">
389
+ <PropertyGroup>
390
+ <TargetFramework>$(SomeCommonTfmThatCannotBeResolved)</TargetFramework>
391
+ </PropertyGroup>
392
+ <ItemGroup>
393
+ <PackageReference Include="Some.Package" Version="1.2.3" />
394
+ </ItemGroup>
395
+ </Project>
396
+ """)
397
+ ],
398
+ expectedResult: new()
399
+ {
400
+ FilePath = "",
401
+ Projects = []
402
+ });
403
+ }
379
404
  }
380
405
  }
@@ -0,0 +1,10 @@
1
+ namespace NuGetUpdater.Core.Test
2
+ {
3
+ public abstract class TestBase
4
+ {
5
+ protected TestBase()
6
+ {
7
+ MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, Environment.CurrentDirectory);
8
+ }
9
+ }
10
+ }
@@ -1,16 +1,9 @@
1
- using System.Collections.Generic;
2
-
3
1
  using Xunit;
4
2
 
5
3
  namespace NuGetUpdater.Core.Test.Update;
6
4
 
7
- public class PackagesConfigUpdaterTests
5
+ public class PackagesConfigUpdaterTests : TestBase
8
6
  {
9
- public PackagesConfigUpdaterTests()
10
- {
11
- MSBuildHelper.RegisterMSBuild();
12
- }
13
-
14
7
  [Theory]
15
8
  [MemberData(nameof(PackagesDirectoryPathTestData))]
16
9
  public void PathToPackagesDirectoryCanBeDetermined(string projectContents, string dependencyName, string dependencyVersion, string expectedPackagesDirectoryPath)
@@ -10,7 +10,7 @@ namespace NuGetUpdater.Core.Test.Update;
10
10
  using TestFile = (string Path, string Content);
11
11
  using TestProject = (string Path, string Content, Guid ProjectId);
12
12
 
13
- public abstract class UpdateWorkerTestBase
13
+ public abstract class UpdateWorkerTestBase : TestBase
14
14
  {
15
15
  protected static Task TestNoChange(
16
16
  string dependencyName,
@@ -10,11 +10,6 @@ public partial class UpdateWorkerTests
10
10
  {
11
11
  public class DirsProj : UpdateWorkerTestBase
12
12
  {
13
- public DirsProj()
14
- {
15
- MSBuildHelper.RegisterMSBuild();
16
- }
17
-
18
13
  [Fact]
19
14
  public async Task UpdateSingleDependencyInDirsProj()
20
15
  {
@@ -8,11 +8,6 @@ public partial class UpdateWorkerTests
8
8
  {
9
9
  public class DotNetTools : UpdateWorkerTestBase
10
10
  {
11
- public DotNetTools()
12
- {
13
- MSBuildHelper.RegisterMSBuild();
14
- }
15
-
16
11
  [Fact]
17
12
  public async Task NoChangeWhenDotNetToolsJsonNotFound()
18
13
  {
@@ -8,11 +8,6 @@ public partial class UpdateWorkerTests
8
8
  {
9
9
  public class GlobalJson : UpdateWorkerTestBase
10
10
  {
11
- public GlobalJson()
12
- {
13
- MSBuildHelper.RegisterMSBuild();
14
- }
15
-
16
11
  [Fact]
17
12
  public async Task NoChangeWhenGlobalJsonNotFound()
18
13
  {
@@ -8,11 +8,6 @@ public partial class UpdateWorkerTests
8
8
  {
9
9
  public class Mixed : UpdateWorkerTestBase
10
10
  {
11
- public Mixed()
12
- {
13
- MSBuildHelper.RegisterMSBuild();
14
- }
15
-
16
11
  [Fact]
17
12
  public async Task ForPackagesProject_UpdatePackageReference_InBuildProps()
18
13
  {