Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NuGet] Add Analyze command to NuGetUpdater #9493

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
@@ -0,0 +1,44 @@
using System.Text;

using NuGetUpdater.Core.Test.Analyze;

namespace NuGetUpdater.Cli.Test;

using TestFile = (string Path, string Content);

public partial class EntryPointTests
{
public class Analyze : AnalyzeWorkerTestBase
{
private static async Task Run(Func<string, string[]> getArgs, string dependencyName, TestFile[] initialFiles, ExpectedAnalysisResult expectedResult)
{
var actualResult = await RunAnalyzerAsync(dependencyName, initialFiles, async path =>
{
var sb = new StringBuilder();
var writer = new StringWriter(sb);

var originalOut = Console.Out;
var originalErr = Console.Error;
Console.SetOut(writer);
Console.SetError(writer);

try
{
var args = getArgs(path);
var result = await Program.Main(args);
if (result != 0)
{
throw new Exception($"Program exited with code {result}.\nOutput:\n\n{sb}");
}
}
finally
{
Console.SetOut(originalOut);
Console.SetError(originalErr);
}
});

ValidateAnalysisResult(expectedResult, actualResult);
}
}
}
@@ -1,7 +1,7 @@
using System.Collections.Immutable;
using System.Text;

using NuGetUpdater.Core;
using NuGetUpdater.Core.Discover;
using NuGetUpdater.Core.Test.Discover;

using Xunit;
Expand All @@ -25,6 +25,8 @@ public async Task WithSolution()
path,
"--workspace",
path,
"--output",
Path.Combine(path, DiscoveryWorker.DiscoveryResultFileName)
],
new[]
{
Expand Down Expand Up @@ -77,7 +79,7 @@ public async Task WithSolution()
},
expectedResult: new()
{
FilePath = "",
Path = "",
Projects = [
new()
{
Expand Down Expand Up @@ -107,6 +109,8 @@ public async Task WithProject()
path,
"--workspace",
path,
"--output",
Path.Combine(path, DiscoveryWorker.DiscoveryResultFileName)
],
new[]
{
Expand Down Expand Up @@ -136,7 +140,7 @@ public async Task WithProject()
},
expectedResult: new()
{
FilePath = "",
Path = "",
Projects = [
new()
{
Expand Down Expand Up @@ -166,6 +170,8 @@ public async Task WithDirectory()
path,
"--workspace",
Path.Combine(path, workspacePath),
"--output",
Path.Combine(path, DiscoveryWorker.DiscoveryResultFileName)
],
new[]
{
Expand Down Expand Up @@ -195,7 +201,7 @@ public async Task WithDirectory()
},
expectedResult: new()
{
FilePath = workspacePath,
Path = workspacePath,
Projects = [
new()
{
Expand All @@ -221,6 +227,8 @@ public async Task WithDirectory()
{
var actualResult = await RunDiscoveryAsync(initialFiles, async path =>
{
expectedResult = expectedResult with { Path = Path.Combine(path, expectedResult.Path) };

var sb = new StringBuilder();
var writer = new StringWriter(sb);

Expand Down
@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using Xunit;

namespace NuGetUpdater.Cli.Test;
Expand Down
@@ -1,7 +1,4 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

using NuGetUpdater.Core;
using NuGetUpdater.Core.Test;
Expand Down Expand Up @@ -235,7 +232,7 @@ public async Task WithDirsProjAndDirectoryBuildPropsThatIsOutOfDirectoryButStill
"""),
("other-dir/Directory.Build.props", """
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<ItemGroup>
<PackageReference Include="NuGet.Versioning" Version="6.1.0" />
</ItemGroup>
Expand All @@ -254,7 +251,7 @@ public async Task WithDirsProjAndDirectoryBuildPropsThatIsOutOfDirectoryButStill
</Project>
"""),
("some-dir/project1/project.csproj",
"""
"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down Expand Up @@ -282,7 +279,7 @@ public async Task WithDirsProjAndDirectoryBuildPropsThatIsOutOfDirectoryButStill
"""),
("other-dir/Directory.Build.props", """
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<ItemGroup>
<PackageReference Include="NuGet.Versioning" Version="6.1.0" />
</ItemGroup>
Expand Down
@@ -0,0 +1,35 @@
using System.CommandLine;

using NuGetUpdater.Core;
using NuGetUpdater.Core.Analyze;

namespace NuGetUpdater.Cli.Commands;

internal static class AnalyzeCommand
{
internal static readonly Option<FileInfo> DependencyFilePathOption = new("--dependency-file-path") { IsRequired = true };
internal static readonly Option<FileInfo> DiscoveryFilePathOption = new("--discovery-file-path") { IsRequired = true };
internal static readonly Option<DirectoryInfo> AnalysisFolderOption = new("--analysis-folder-path") { IsRequired = true };
internal static readonly Option<bool> VerboseOption = new("--verbose", getDefaultValue: () => false);

internal static Command GetCommand(Action<int> setExitCode)
{
Command command = new("analyze", "Determines how to update a dependency based on the workspace discovery information.")
{
DependencyFilePathOption,
DiscoveryFilePathOption,
AnalysisFolderOption,
VerboseOption
};

command.TreatUnmatchedTokensAsErrors = true;

command.SetHandler(async (discoveryPath, dependencyPath, analysisDirectory, verbose) =>
{
var worker = new AnalyzeWorker(new Logger(verbose));
await worker.RunAsync(discoveryPath.FullName, dependencyPath.FullName, analysisDirectory.FullName);
}, DiscoveryFilePathOption, DependencyFilePathOption, AnalysisFolderOption, VerboseOption);

return command;
}
}
Expand Up @@ -7,9 +7,9 @@ namespace NuGetUpdater.Cli.Commands;

internal static class DiscoverCommand
{
internal static readonly Option<DirectoryInfo> RepoRootOption = new("--repo-root", () => new DirectoryInfo(Environment.CurrentDirectory)) { IsRequired = false };
internal static readonly Option<DirectoryInfo> RepoRootOption = new("--repo-root") { IsRequired = true };
internal static readonly Option<FileSystemInfo> WorkspaceOption = new("--workspace") { IsRequired = true };
internal static readonly Option<string> OutputOption = new("--output", () => DiscoveryWorker.DiscoveryResultFileName) { IsRequired = false };
internal static readonly Option<FileInfo> OutputOption = new("--output") { IsRequired = true };
internal static readonly Option<bool> VerboseOption = new("--verbose", getDefaultValue: () => false);

internal static Command GetCommand(Action<int> setExitCode)
Expand All @@ -27,7 +27,7 @@ internal static Command GetCommand(Action<int> setExitCode)
command.SetHandler(async (repoRoot, workspace, outputPath, verbose) =>
{
var worker = new DiscoveryWorker(new Logger(verbose));
await worker.RunAsync(repoRoot.FullName, workspace.FullName, outputPath);
await worker.RunAsync(repoRoot.FullName, workspace.FullName, outputPath.FullName);
}, RepoRootOption, WorkspaceOption, OutputOption, VerboseOption);

return command;
Expand Down
Expand Up @@ -15,6 +15,7 @@ internal static async Task<int> Main(string[] args)
{
FrameworkCheckCommand.GetCommand(setExitCode),
DiscoverCommand.GetCommand(setExitCode),
AnalyzeCommand.GetCommand(setExitCode),
UpdateCommand.GetCommand(setExitCode),
};
command.TreatUnmatchedTokensAsErrors = true;
Expand Down
@@ -0,0 +1,85 @@
using System.Collections.Immutable;
using System.Text.Json;

using NuGetUpdater.Core.Analyze;
using NuGetUpdater.Core.Discover;
using NuGetUpdater.Core.Test.Utilities;

using Xunit;

namespace NuGetUpdater.Core.Test.Analyze;

using TestFile = (string Path, string Content);

public class AnalyzeWorkerTestBase
{
protected static async Task TestAnalyzeAsync(
WorkspaceDiscoveryResult discovery,
DependencyInfo dependencyInfo,
ExpectedAnalysisResult expectedResult)
{
var relativeDependencyPath = $"./dependabot/dependency/{dependencyInfo.Name}.json";

TestFile[] files = [
(DiscoveryWorker.DiscoveryResultFileName, JsonSerializer.Serialize(discovery, AnalyzeWorker.SerializerOptions)),
(relativeDependencyPath, JsonSerializer.Serialize(dependencyInfo, AnalyzeWorker.SerializerOptions)),
];

var actualResult = await RunAnalyzerAsync(dependencyInfo.Name, files, async directoryPath =>
{
var discoveryPath = Path.GetFullPath(DiscoveryWorker.DiscoveryResultFileName, directoryPath);
var dependencyPath = Path.GetFullPath(relativeDependencyPath, directoryPath);
var analysisPath = Path.GetFullPath(AnalyzeWorker.AnalysisDirectoryName, directoryPath);

var worker = new AnalyzeWorker(new Logger(verbose: true));
await worker.RunAsync(discoveryPath, dependencyPath, analysisPath);
});

ValidateAnalysisResult(expectedResult, actualResult);
}

protected static void ValidateAnalysisResult(ExpectedAnalysisResult expectedResult, AnalysisResult actualResult)
{
Assert.NotNull(actualResult);
Assert.Equal(expectedResult.UpdatedVersion, actualResult.UpdatedVersion);
Assert.Equal(expectedResult.CanUpdate, actualResult.CanUpdate);
Assert.Equal(expectedResult.VersionComesFromMultiDependencyProperty, actualResult.VersionComesFromMultiDependencyProperty);
ValidateDependencies(expectedResult.UpdatedDependencies, actualResult.UpdatedDependencies);
Assert.Equal(expectedResult.ExpectedUpdatedDependenciesCount ?? expectedResult.UpdatedDependencies.Length, actualResult.UpdatedDependencies.Length);

return;

void ValidateDependencies(ImmutableArray<Dependency> expectedDependencies, ImmutableArray<Dependency> actualDependencies)
{
if (expectedDependencies.IsDefault)
{
return;
}

foreach (var expectedDependency in expectedDependencies)
{
var actualDependency = actualDependencies.Single(d => d.Name == expectedDependency.Name);
Assert.Equal(expectedDependency.Name, actualDependency.Name);
Assert.Equal(expectedDependency.Version, actualDependency.Version);
Assert.Equal(expectedDependency.Type, actualDependency.Type);
AssertEx.Equal(expectedDependency.TargetFrameworks, actualDependency.TargetFrameworks);
Assert.Equal(expectedDependency.IsDirect, actualDependency.IsDirect);
Assert.Equal(expectedDependency.IsTransitive, actualDependency.IsTransitive);
}
}
}

protected static async Task<AnalysisResult> RunAnalyzerAsync(string dependencyName, TestFile[] files, Func<string, Task> action)
{
// write initial files
using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync(files);

// run discovery
await action(temporaryDirectory.DirectoryPath);

// gather results
var resultPath = Path.Join(temporaryDirectory.DirectoryPath, AnalyzeWorker.AnalysisDirectoryName, $"{dependencyName}.json");
var resultJson = await File.ReadAllTextAsync(resultPath);
return JsonSerializer.Deserialize<AnalysisResult>(resultJson, DiscoveryWorker.SerializerOptions)!;
}
}