dependabot-nuget 0.258.0 → 0.259.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +2 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +2 -2
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +255 -191
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +63 -35
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +107 -14
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +9 -5
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +18 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +6 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +6 -2
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +6 -2
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +11 -21
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Proj.cs +95 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +204 -62
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +64 -45
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +419 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +1 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +7 -2
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +77 -19
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +120 -91
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +132 -97
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +93 -75
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +45 -42
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +1089 -956
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +1624 -1291
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +296 -293
  27. data/helpers/lib/NuGetUpdater/global.json +6 -0
  28. data/lib/dependabot/nuget/file_parser.rb +4 -5
  29. data/lib/dependabot/nuget/file_updater.rb +1 -1
  30. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +7 -2
  31. data/lib/dependabot/nuget/update_checker/property_updater.rb +1 -0
  32. data/lib/dependabot/nuget/update_checker/version_finder.rb +2 -3
  33. data/lib/dependabot/nuget/update_checker.rb +1 -0
  34. metadata +8 -5
@@ -0,0 +1,419 @@
1
+ using System.IO.Compression;
2
+ using System.Security.Cryptography;
3
+ using System.Text;
4
+ using System.Text.Json.Nodes;
5
+ using System.Text.RegularExpressions;
6
+ using System.Xml;
7
+ using System.Xml.Linq;
8
+ using System.Xml.XPath;
9
+
10
+ using Microsoft.CodeAnalysis;
11
+ using Microsoft.CodeAnalysis.CSharp;
12
+ using Microsoft.CodeAnalysis.Emit;
13
+
14
+ namespace NuGetUpdater.Core.Test
15
+ {
16
+ public record MockNuGetPackage(
17
+ string Id,
18
+ string Version,
19
+ XElement[]? AdditionalMetadata = null,
20
+ (string? TargetFramework, (string Id, string Version)[] Packages)[]? DependencyGroups = null,
21
+ (string Path, byte[] Content)[]? Files = null)
22
+ {
23
+ private static readonly XNamespace Namespace = "http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd";
24
+ private static readonly XmlWriterSettings WriterSettings = new()
25
+ {
26
+ Encoding = Encoding.UTF8,
27
+ Indent = true,
28
+ };
29
+
30
+ private XDocument? _nuspec;
31
+ private Stream? _stream;
32
+
33
+ public void WriteToDirectory(string localPackageSourcePath)
34
+ {
35
+ string cachePath = Path.Join(localPackageSourcePath, "_nupkg_cache");
36
+ string nupkgPath = Path.Join(cachePath, $"{Id}.{Version}.nupkg");
37
+ Directory.CreateDirectory(cachePath);
38
+ Stream stream = GetZipStream();
39
+ using (FileStream fileStream = new(nupkgPath, FileMode.Create))
40
+ {
41
+ stream.CopyTo(fileStream);
42
+ }
43
+
44
+ // add the package to the local feed; this is equivalent to running
45
+ // nuget add <nupkgPath> -source <localPackageSourcePath>
46
+ // but running that in-process locks the files, so we have to do it manually
47
+ // the end result is 4 files:
48
+ // .nupkg.metadata // a JSON object with the package's content hash and some other fields
49
+ // <id>.<version>.nupkg // the package itself
50
+ // <id>.<version>.nupkg.sha512 // the SHA512 hash of the package
51
+ // <id>.nuspec // the package's nuspec file
52
+ string expandedPath = Path.Join(localPackageSourcePath, Id.ToLowerInvariant(), Version);
53
+ Directory.CreateDirectory(expandedPath);
54
+ File.Copy(nupkgPath, Path.Join(expandedPath, $"{Id}.{Version}.nupkg".ToLowerInvariant()));
55
+ using XmlWriter writer = XmlWriter.Create(Path.Join(expandedPath, $"{Id}.nuspec".ToLowerInvariant()), WriterSettings);
56
+ GetNuspec().WriteTo(writer);
57
+ using SHA512 sha512 = SHA512.Create();
58
+ byte[] hash = sha512.ComputeHash(File.ReadAllBytes(nupkgPath));
59
+ string hashString = Convert.ToBase64String(hash);
60
+ File.WriteAllText(Path.Join(expandedPath, $"{Id}.{Version}.nupkg.sha512".ToLowerInvariant()), hashString);
61
+ JsonObject metadata = new()
62
+ {
63
+ ["version"] = 2,
64
+ ["contentHash"] = hashString,
65
+ ["source"] = null,
66
+ };
67
+ File.WriteAllText(Path.Join(expandedPath, ".nupkg.metadata"), metadata.ToString());
68
+ }
69
+
70
+ /// <summary>
71
+ /// Creates a mock NuGet package with a single assembly in the appropriate `lib/` directory. The assembly will
72
+ /// be empty.
73
+ /// </summary>
74
+ public static MockNuGetPackage CreateSimplePackage(string id, string version, string targetFramework, (string? TargetFramework, (string Id, string Version)[] Packages)[]? dependencyGroups = null)
75
+ {
76
+ return new(
77
+ id,
78
+ version,
79
+ AdditionalMetadata: null,
80
+ DependencyGroups: dependencyGroups,
81
+ Files:
82
+ [
83
+ ($"lib/{targetFramework}/{id}.dll", Array.Empty<byte>())
84
+ ]
85
+ );
86
+ }
87
+
88
+ /// <summary>
89
+ /// Creates a mock NuGet package with a single assembly in the appropriate `lib/` directory. The assembly will
90
+ /// contain the appropriate `AssemblyVersion` attribute and nothing else.
91
+ /// </summary>
92
+ public static MockNuGetPackage CreatePackageWithAssembly(string id, string version, string targetFramework, string assemblyVersion, (string? TargetFramework, (string Id, string Version)[] Packages)[]? dependencyGroups = null)
93
+ {
94
+ return new(
95
+ id,
96
+ version,
97
+ AdditionalMetadata: null,
98
+ DependencyGroups: dependencyGroups,
99
+ Files:
100
+ [
101
+ ($"lib/{targetFramework}/{id}.dll", CreateAssembly(id, assemblyVersion))
102
+ ]
103
+ );
104
+ }
105
+
106
+ /// <summary>
107
+ /// Creates a mock NuGet package with empty analyzer assemblies for both C# and VB.
108
+ /// </summary>
109
+ public static MockNuGetPackage CreateAnalyzerPackage(string id, string version, (string? TargetFramework, (string Id, string Version)[] Packages)[]? dependencyGroups = null)
110
+ {
111
+ return new(
112
+ id,
113
+ version,
114
+ AdditionalMetadata:
115
+ [
116
+ new XElement("developmentDependency", "true"),
117
+ ],
118
+ DependencyGroups: dependencyGroups,
119
+ Files:
120
+ [
121
+ ($"analyzers/dotnet/cs/{id}.dll", Array.Empty<byte>()),
122
+ ($"analyzers/dotnet/vb/{id}.dll", Array.Empty<byte>()),
123
+ ]
124
+ );
125
+ }
126
+
127
+ public static MockNuGetPackage CreateDotNetToolPackage(string id, string version, string targetFramework)
128
+ {
129
+ return new(
130
+ id,
131
+ version,
132
+ AdditionalMetadata:
133
+ [
134
+ new XElement("packageTypes",
135
+ new XElement("packageType",
136
+ new XAttribute("name", "DotnetTool")
137
+ )
138
+ )
139
+ ],
140
+ Files:
141
+ [
142
+ ($"tools/{targetFramework}/any/DotnetToolSettings.xml", Encoding.UTF8.GetBytes($"""
143
+ <DotNetCliTool Version="1">
144
+ <Commands>
145
+ <Command Name="{id}" EntryPoint="{id}.dll" Runner="dotnet" />
146
+ </Commands>
147
+ </DotNetCliTool>
148
+ """)),
149
+ ($"tools/{targetFramework}/any/{id}.dll", Array.Empty<byte>()),
150
+ ]
151
+ );
152
+ }
153
+
154
+ public static MockNuGetPackage CreateMSBuildSdkPackage(string id, string version, string? sdkPropsContent = null, string? sdkTargetsContent = null)
155
+ {
156
+ sdkPropsContent ??= """
157
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
158
+ </Project>
159
+ """;
160
+ sdkTargetsContent ??= """
161
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
162
+ </Project>
163
+ """;
164
+ return new(
165
+ id,
166
+ version,
167
+ AdditionalMetadata:
168
+ [
169
+ new XElement("packageTypes",
170
+ new XElement("packageType",
171
+ new XAttribute("name", "MSBuildSdk")
172
+ )
173
+ )
174
+ ],
175
+ Files:
176
+ [
177
+ ("Sdk/Sdk.props", Encoding.UTF8.GetBytes(sdkPropsContent)),
178
+ ("Sdk/Sdk.targets", Encoding.UTF8.GetBytes(sdkTargetsContent)),
179
+ ]
180
+ );
181
+ }
182
+
183
+ private XDocument GetNuspec()
184
+ {
185
+ if (_nuspec is null)
186
+ {
187
+ _nuspec = new XDocument(
188
+ new XElement(Namespace + "package",
189
+ new XElement(Namespace + "metadata",
190
+ new XElement(Namespace + "id", Id),
191
+ new XElement(Namespace + "version", Version),
192
+ new XElement(Namespace + "authors", "MockNuGetPackage"),
193
+ new XElement(Namespace + "description", "Mock NuGet package"),
194
+ AdditionalMetadata?.Select(a => WithNamespace(a, Namespace)),
195
+ new XElement(Namespace + "dependencies",
196
+ // dependencies with no target framework
197
+ DependencyGroups?.Where(g => g.TargetFramework is null).SelectMany(g =>
198
+ g.Packages.Select(p =>
199
+ new XElement(Namespace + "dependency",
200
+ new XAttribute("id", p.Id),
201
+ new XAttribute("version", p.Version)
202
+ )
203
+ )
204
+ ),
205
+ // dependencies with a target framework
206
+ DependencyGroups?.Where(g => g.TargetFramework is not null).Select(g =>
207
+ new XElement(Namespace + "group",
208
+ new XAttribute("targetFramework", g.TargetFramework!),
209
+ g.Packages.Select(p =>
210
+ new XElement(Namespace + "dependency",
211
+ new XAttribute("id", p.Id),
212
+ new XAttribute("version", p.Version)
213
+ )
214
+ )
215
+ )
216
+ )
217
+ )
218
+ )
219
+ )
220
+ );
221
+ }
222
+
223
+ return _nuspec;
224
+ }
225
+
226
+ private static XElement WithNamespace(XElement element, XNamespace ns)
227
+ {
228
+ return new XElement(ns + element.Name.LocalName,
229
+ element.Attributes(),
230
+ element.Nodes().Select(n =>
231
+ {
232
+ if (n is XElement e)
233
+ {
234
+ return WithNamespace(e, ns);
235
+ }
236
+
237
+ return n;
238
+ })
239
+ );
240
+ }
241
+
242
+ private Stream GetZipStream()
243
+ {
244
+ if (_stream is null)
245
+ {
246
+ XDocument nuspec = GetNuspec();
247
+ _stream = new MemoryStream();
248
+ using ZipArchive zip = new(_stream, ZipArchiveMode.Create, leaveOpen: true);
249
+ ZipArchiveEntry nuspecEntry = zip.CreateEntry($"{Id}.nuspec");
250
+ using (Stream contentStream = nuspecEntry.Open())
251
+ using (XmlWriter writer = XmlWriter.Create(contentStream, WriterSettings))
252
+ {
253
+ nuspec.WriteTo(writer);
254
+ }
255
+
256
+ foreach (var file in Files ?? [])
257
+ {
258
+ ZipArchiveEntry fileEntry = zip.CreateEntry(file.Path);
259
+ using Stream contentStream = fileEntry.Open();
260
+ contentStream.Write(file.Content, 0, file.Content.Length);
261
+ }
262
+ }
263
+
264
+ _stream.Seek(0, SeekOrigin.Begin);
265
+ return _stream;
266
+ }
267
+
268
+ private static byte[] CreateAssembly(string assemblyName, string assemblyVersion)
269
+ {
270
+ CSharpCompilationOptions compilationOptions = new(OutputKind.DynamicallyLinkedLibrary);
271
+ CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, options: compilationOptions)
272
+ .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
273
+ .AddSyntaxTrees(CSharpSyntaxTree.ParseText($"[assembly: System.Reflection.AssemblyVersionAttribute(\"{assemblyVersion}\")]"));
274
+ MemoryStream assemblyStream = new();
275
+ EmitResult emitResult = compilation.Emit(assemblyStream);
276
+ if (!emitResult.Success)
277
+ {
278
+ throw new Exception($"Unable to create test assembly:\n\t{string.Join("\n\t", emitResult.Diagnostics.ToString())}");
279
+ }
280
+
281
+ return assemblyStream.ToArray();
282
+ }
283
+
284
+ // some well-known packages
285
+ public static MockNuGetPackage CentralPackageVersionsPackage =>
286
+ CreateMSBuildSdkPackage(
287
+ "Microsoft.Build.CentralPackageVersions",
288
+ "2.1.3",
289
+ sdkTargetsContent: """
290
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
291
+ <!-- this is a simplified version of this package used for testing -->
292
+ <PropertyGroup>
293
+ <CentralPackagesFile Condition=" '$(CentralPackagesFile)' == '' ">$([MSBuild]::GetPathOfFileAbove('Packages.props', $(MSBuildProjectDirectory)))</CentralPackagesFile>
294
+ </PropertyGroup>
295
+ <Import Project="$(CentralPackagesFile)" Condition="Exists('$(CentralPackagesFile)')" />
296
+ </Project>
297
+ """
298
+ );
299
+
300
+ private static readonly Lazy<string> BundledVersionsPropsPath = new(() =>
301
+ {
302
+ // we need to find the file `Microsoft.NETCoreSdk.BundledVersions.props` in the SDK directory
303
+
304
+ DirectoryInfo projectDir = Directory.CreateTempSubdirectory("bundled_versions_props_path_discovery_");
305
+ try
306
+ {
307
+ // get the sdk version
308
+ string projectPath = Path.Combine(projectDir.FullName, "project.csproj");
309
+ File.WriteAllText(projectPath, """
310
+ <Project Sdk="Microsoft.NET.Sdk">
311
+ <Target Name="_ReportCurrentSdkVersion">
312
+ <Message Text="_CurrentSdkVersion=$(NETCoreSdkVersion)" Importance="High" />
313
+ </Target>
314
+ </Project>
315
+ """
316
+ );
317
+ var (exitCode, stdout, stderr) = ProcessEx.RunAsync("dotnet", $"msbuild {projectPath} /t:_ReportCurrentSdkVersion").Result;
318
+ if (exitCode != 0)
319
+ {
320
+ throw new Exception($"Failed to report the current SDK version:\n{stdout}\n{stderr}");
321
+ }
322
+
323
+ MatchCollection matches = Regex.Matches(stdout, "_CurrentSdkVersion=(?<SdkVersion>.*)$", RegexOptions.Multiline);
324
+ if (matches.Count == 0)
325
+ {
326
+ throw new Exception($"Failed to find the current SDK version in the output:\n{stdout}");
327
+ }
328
+
329
+ string sdkVersionString = matches.First().Groups["SdkVersion"].Value.Trim();
330
+
331
+ // find the actual SDK directory
332
+ string privateCoreLibPath = typeof(object).Assembly.Location; // e.g., C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll
333
+ string sdkDirectory = Path.Combine(Path.GetDirectoryName(privateCoreLibPath)!, "..", "..", "..", "sdk", sdkVersionString); // e.g., C:\Program Files\dotnet\sdk\8.0.204
334
+ string bundledVersionsPropsPath = Path.Combine(sdkDirectory, "Microsoft.NETCoreSdk.BundledVersions.props");
335
+ FileInfo normalizedPath = new(bundledVersionsPropsPath);
336
+ return normalizedPath.FullName;
337
+ }
338
+ finally
339
+ {
340
+ projectDir.Delete(recursive: true);
341
+ }
342
+ });
343
+
344
+ private static readonly Dictionary<string, MockNuGetPackage> WellKnownPackages = new();
345
+ public static MockNuGetPackage WellKnownReferencePackage(string packageName, string targetFramework, (string Path, byte[] Content)[]? files = null)
346
+ {
347
+ string key = $"{packageName}/{targetFramework}";
348
+ if (!WellKnownPackages.ContainsKey(key))
349
+ {
350
+ // for the current SDK, the file `Microsoft.NETCoreSdk.BundledVersions.props` contains the version of the
351
+ // `Microsoft.WindowsDesktop.App.Ref` package that will be needed to build, so we find it by TFM
352
+ XDocument propsDocument = XDocument.Load(BundledVersionsPropsPath.Value);
353
+ XElement? matchingFrameworkElement = propsDocument.XPathSelectElement(
354
+ $"""
355
+ /Project/ItemGroup/KnownFrameworkReference
356
+ [
357
+ @Include='{packageName}' and
358
+ @TargetingPackName='{packageName}.Ref' and
359
+ @TargetFramework='{targetFramework}'
360
+ ]
361
+ """);
362
+ if (matchingFrameworkElement is null)
363
+ {
364
+ throw new Exception($"Unable to find {packageName}.Ref version for target framework '{targetFramework}'");
365
+ }
366
+
367
+ string expectedVersion = matchingFrameworkElement.Attribute("TargetingPackVersion")!.Value;
368
+ return new(
369
+ $"{packageName}.Ref",
370
+ expectedVersion,
371
+ AdditionalMetadata:
372
+ [
373
+ new XElement("packageTypes",
374
+ new XElement("packageType",
375
+ new XAttribute("name", "DotnetPlatform")
376
+ )
377
+ )
378
+ ],
379
+ Files: files
380
+ );
381
+ }
382
+
383
+ return WellKnownPackages[key];
384
+ }
385
+
386
+ public static MockNuGetPackage[] CommonPackages { get; } =
387
+ [
388
+ CreateSimplePackage("NETStandard.Library", "2.0.3", "netstandard2.0"),
389
+ new MockNuGetPackage("Microsoft.NETFramework.ReferenceAssemblies", "1.0.3"),
390
+ WellKnownReferencePackage("Microsoft.AspNetCore.App", "net6.0"),
391
+ WellKnownReferencePackage("Microsoft.AspNetCore.App", "net7.0"),
392
+ WellKnownReferencePackage("Microsoft.AspNetCore.App", "net8.0"),
393
+ WellKnownReferencePackage("Microsoft.NETCore.App", "net6.0",
394
+ [
395
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
396
+ <FileList TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="6.0" FrameworkName="Microsoft.NETCore.App" Name=".NET Runtime">
397
+ </FileList>
398
+ """))
399
+ ]),
400
+ WellKnownReferencePackage("Microsoft.NETCore.App", "net7.0",
401
+ [
402
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
403
+ <FileList TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="7.0" FrameworkName="Microsoft.NETCore.App" Name=".NET Runtime">
404
+ </FileList>
405
+ """))
406
+ ]),
407
+ WellKnownReferencePackage("Microsoft.NETCore.App", "net8.0",
408
+ [
409
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
410
+ <FileList TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="8.0" FrameworkName="Microsoft.NETCore.App" Name=".NET Runtime">
411
+ </FileList>
412
+ """))
413
+ ]),
414
+ WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net6.0"),
415
+ WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net7.0"),
416
+ WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net8.0"),
417
+ ];
418
+ }
419
+ }
@@ -12,6 +12,7 @@
12
12
 
13
13
  <ItemGroup>
14
14
  <PackageReference Include="DiffPlex" />
15
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
15
16
  <PackageReference Include="Microsoft.NET.Test.Sdk" />
16
17
  <PackageReference Include="xunit" />
17
18
  <PackageReference Include="xunit.runner.visualstudio" />
@@ -50,9 +50,14 @@ public sealed class TemporaryDirectory : IDisposable
50
50
  var parentDirectory = Path.GetDirectoryName(temporaryDirectory.DirectoryPath)!;
51
51
 
52
52
  // prevent directory crawling
53
- await File.WriteAllTextAsync(Path.Combine(parentDirectory, "Directory.Build.props"), "<Project />");
53
+ await File.WriteAllTextAsync(Path.Combine(parentDirectory, "Directory.Build.props"), """
54
+ <Project>
55
+ <PropertyGroup>
56
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
57
+ </PropertyGroup>
58
+ </Project>
59
+ """);
54
60
  await File.WriteAllTextAsync(Path.Combine(parentDirectory, "Directory.Build.targets"), "<Project />");
55
- await File.WriteAllTextAsync(Path.Combine(parentDirectory, "Directory.Packages.props"), "<Project />");
56
61
 
57
62
  foreach (var (path, contents) in fileContents)
58
63
  {
@@ -1,8 +1,3 @@
1
- using System;
2
- using System.IO;
3
- using System.Linq;
4
- using System.Threading.Tasks;
5
-
6
1
  using Xunit;
7
2
 
8
3
  namespace NuGetUpdater.Core.Test.Update;
@@ -20,11 +15,12 @@ public abstract class UpdateWorkerTestBase : TestBase
20
15
  string projectContents,
21
16
  bool isTransitive = false,
22
17
  TestFile[]? additionalFiles = null,
18
+ MockNuGetPackage[]? packages = null,
23
19
  string projectFilePath = "test-project.csproj")
24
20
  {
25
21
  return useSolution
26
- ? TestNoChangeforSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], isTransitive, additionalFiles)
27
- : TestNoChangeforProject(dependencyName, oldVersion, newVersion, projectContents, isTransitive, additionalFiles, projectFilePath);
22
+ ? TestNoChangeforSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], isTransitive, additionalFiles, packages)
23
+ : TestNoChangeforProject(dependencyName, oldVersion, newVersion, projectContents, isTransitive, additionalFiles, packages, projectFilePath);
28
24
  }
29
25
 
30
26
  protected static Task TestUpdate(
@@ -37,11 +33,12 @@ public abstract class UpdateWorkerTestBase : TestBase
37
33
  bool isTransitive = false,
38
34
  TestFile[]? additionalFiles = null,
39
35
  TestFile[]? additionalFilesExpected = null,
36
+ MockNuGetPackage[]? packages = null,
40
37
  string projectFilePath = "test-project.csproj")
41
38
  {
42
39
  return useSolution
43
- ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], projectFilesExpected: [(projectFilePath, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected)
44
- : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile: (projectFilePath, projectContents), expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected);
40
+ ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], projectFilesExpected: [(projectFilePath, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected, packages)
41
+ : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile: (projectFilePath, projectContents), expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected, packages);
45
42
  }
46
43
 
47
44
  protected static Task TestUpdate(
@@ -53,11 +50,12 @@ public abstract class UpdateWorkerTestBase : TestBase
53
50
  string expectedProjectContents,
54
51
  bool isTransitive = false,
55
52
  TestFile[]? additionalFiles = null,
56
- TestFile[]? additionalFilesExpected = null)
53
+ TestFile[]? additionalFilesExpected = null,
54
+ MockNuGetPackage[]? packages = null)
57
55
  {
58
56
  return useSolution
59
- ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [projectFile], projectFilesExpected: [(projectFile.Path, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected)
60
- : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile, expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected);
57
+ ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [projectFile], projectFilesExpected: [(projectFile.Path, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected, packages)
58
+ : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile, expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected, packages);
61
59
  }
62
60
 
63
61
  protected static Task TestNoChangeforProject(
@@ -67,6 +65,7 @@ public abstract class UpdateWorkerTestBase : TestBase
67
65
  string projectContents,
68
66
  bool isTransitive = false,
69
67
  TestFile[]? additionalFiles = null,
68
+ MockNuGetPackage[]? packages = null,
70
69
  string projectFilePath = "test-project.csproj")
71
70
  => TestUpdateForProject(
72
71
  dependencyName,
@@ -76,7 +75,8 @@ public abstract class UpdateWorkerTestBase : TestBase
76
75
  expectedProjectContents: projectContents,
77
76
  isTransitive,
78
77
  additionalFiles,
79
- additionalFilesExpected: additionalFiles);
78
+ additionalFilesExpected: additionalFiles,
79
+ packages: packages);
80
80
 
81
81
  protected static Task TestUpdateForProject(
82
82
  string dependencyName,
@@ -87,6 +87,7 @@ public abstract class UpdateWorkerTestBase : TestBase
87
87
  bool isTransitive = false,
88
88
  TestFile[]? additionalFiles = null,
89
89
  TestFile[]? additionalFilesExpected = null,
90
+ MockNuGetPackage[]? packages = null,
90
91
  string projectFilePath = "test-project.csproj")
91
92
  => TestUpdateForProject(
92
93
  dependencyName,
@@ -96,7 +97,8 @@ public abstract class UpdateWorkerTestBase : TestBase
96
97
  expectedProjectContents,
97
98
  isTransitive,
98
99
  additionalFiles,
99
- additionalFilesExpected);
100
+ additionalFilesExpected,
101
+ packages);
100
102
 
101
103
  protected static async Task TestUpdateForProject(
102
104
  string dependencyName,
@@ -106,21 +108,36 @@ public abstract class UpdateWorkerTestBase : TestBase
106
108
  string expectedProjectContents,
107
109
  bool isTransitive = false,
108
110
  TestFile[]? additionalFiles = null,
109
- TestFile[]? additionalFilesExpected = null)
111
+ TestFile[]? additionalFilesExpected = null,
112
+ MockNuGetPackage[]? packages = null)
110
113
  {
111
114
  additionalFiles ??= [];
112
115
  additionalFilesExpected ??= [];
113
116
 
117
+ var placeFilesInSrc = packages is not null;
118
+
114
119
  var projectFilePath = projectFile.Path;
115
120
  var testFiles = new[] { projectFile }.Concat(additionalFiles).ToArray();
121
+ if (placeFilesInSrc)
122
+ {
123
+ testFiles = testFiles.Select(f => ($"src/{f.Path}", f.Content)).ToArray();
124
+ }
116
125
 
117
126
  var actualResult = await RunUpdate(testFiles, async temporaryDirectory =>
118
127
  {
128
+ await MockNuGetPackagesInDirectory(packages, temporaryDirectory);
129
+
130
+ // run update
119
131
  var worker = new UpdaterWorker(new Logger(verbose: true));
120
- await worker.RunAsync(temporaryDirectory, projectFilePath, dependencyName, oldVersion, newVersion, isTransitive);
132
+ var projectPath = placeFilesInSrc ? $"src/{projectFilePath}" : projectFilePath;
133
+ await worker.RunAsync(temporaryDirectory, projectPath, dependencyName, oldVersion, newVersion, isTransitive);
121
134
  });
122
135
 
123
136
  var expectedResult = additionalFilesExpected.Prepend((projectFilePath, expectedProjectContents)).ToArray();
137
+ if (placeFilesInSrc)
138
+ {
139
+ expectedResult = expectedResult.Select(er => ($"src/{er.Item1}", er.Item2)).ToArray();
140
+ }
124
141
 
125
142
  AssertContainsFiles(expectedResult, actualResult);
126
143
  }
@@ -131,7 +148,8 @@ public abstract class UpdateWorkerTestBase : TestBase
131
148
  string newVersion,
132
149
  TestFile[] projectFiles,
133
150
  bool isTransitive = false,
134
- TestFile[]? additionalFiles = null)
151
+ TestFile[]? additionalFiles = null,
152
+ MockNuGetPackage[]? packages = null)
135
153
  => TestUpdateForSolution(
136
154
  dependencyName,
137
155
  oldVersion,
@@ -140,7 +158,8 @@ public abstract class UpdateWorkerTestBase : TestBase
140
158
  projectFilesExpected: projectFiles,
141
159
  isTransitive,
142
160
  additionalFiles,
143
- additionalFilesExpected: additionalFiles);
161
+ additionalFilesExpected: additionalFiles,
162
+ packages: packages);
144
163
 
145
164
  protected static async Task TestUpdateForSolution(
146
165
  string dependencyName,
@@ -150,7 +169,8 @@ public abstract class UpdateWorkerTestBase : TestBase
150
169
  TestFile[] projectFilesExpected,
151
170
  bool isTransitive = false,
152
171
  TestFile[]? additionalFiles = null,
153
- TestFile[]? additionalFilesExpected = null)
172
+ TestFile[]? additionalFilesExpected = null,
173
+ MockNuGetPackage[]? packages = null)
154
174
  {
155
175
  additionalFiles ??= [];
156
176
  additionalFilesExpected ??= [];
@@ -191,6 +211,8 @@ public abstract class UpdateWorkerTestBase : TestBase
191
211
 
192
212
  var actualResult = await RunUpdate(testFiles, async temporaryDirectory =>
193
213
  {
214
+ await MockNuGetPackagesInDirectory(packages, temporaryDirectory);
215
+
194
216
  var slnPath = Path.Combine(temporaryDirectory, slnName);
195
217
  var worker = new UpdaterWorker(new Logger(verbose: true));
196
218
  await worker.RunAsync(temporaryDirectory, slnPath, dependencyName, oldVersion, newVersion, isTransitive);
@@ -201,6 +223,42 @@ public abstract class UpdateWorkerTestBase : TestBase
201
223
  AssertContainsFiles(expectedResult, actualResult);
202
224
  }
203
225
 
226
+ public static async Task MockNuGetPackagesInDirectory(MockNuGetPackage[]? packages, string temporaryDirectory)
227
+ {
228
+ if (packages is not null)
229
+ {
230
+ string localFeedPath = Path.Join(temporaryDirectory, "local-feed");
231
+ Directory.CreateDirectory(localFeedPath);
232
+ MockNuGetPackage[] allPackages = packages.Concat(MockNuGetPackage.CommonPackages).ToArray();
233
+
234
+ // write all packages to disk
235
+ foreach (MockNuGetPackage package in allPackages)
236
+ {
237
+ package.WriteToDirectory(localFeedPath);
238
+ }
239
+
240
+ // override various nuget locations
241
+ foreach (var envName in new[] { "NUGET_PACKAGES", "NUGET_HTTP_CACHE_PATH", "NUGET_SCRATCH", "NUGET_PLUGINS_CACHE_PATH" })
242
+ {
243
+ string dir = Path.Join(temporaryDirectory, envName);
244
+ Directory.CreateDirectory(dir);
245
+ Environment.SetEnvironmentVariable(envName, dir);
246
+ }
247
+
248
+ // ensure only the test feed is used
249
+ await File.WriteAllTextAsync(Path.Join(temporaryDirectory, "NuGet.Config"), $"""
250
+ <?xml version="1.0" encoding="utf-8"?>
251
+ <configuration>
252
+ <packageSources>
253
+ <clear />
254
+ <add key="local-feed" value="{localFeedPath}" />
255
+ </packageSources>
256
+ </configuration>
257
+ """
258
+ );
259
+ }
260
+ }
261
+
204
262
  protected static async Task<TestFile[]> RunUpdate(TestFile[] files, Func<string, Task> action)
205
263
  {
206
264
  // write initial files