dependabot-nuget 0.252.0 → 0.254.0

Sign up to get free protection for your applications and to get access to all the features.
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
  {