From 89f2b2d4c2c0d1ed2e0696c86698a69a48b9060d Mon Sep 17 00:00:00 2001 From: Caio Saldanha Date: Wed, 22 Mar 2023 15:08:58 -0700 Subject: [PATCH] [.NET] Updating ApiView to handle input/output files (#5310) --- src/dotnet/APIView/APIView/Program.cs | 27 +++-- .../APIViewUnitTests/CodeFileBuilderTests.cs | 16 +-- .../APIViewUnitTests/CodeFileRendererTests.cs | 1 - .../APIView/APIViewUnitTests/CodeFileTests.cs | 7 -- src/dotnet/APIView/APIViewUnitTests/Common.cs | 23 ++++ .../APIView/APIViewUnitTests/ProgramTests.cs | 101 ++++++++++++++++++ 6 files changed, 149 insertions(+), 26 deletions(-) create mode 100644 src/dotnet/APIView/APIViewUnitTests/Common.cs create mode 100644 src/dotnet/APIView/APIViewUnitTests/ProgramTests.cs diff --git a/src/dotnet/APIView/APIView/Program.cs b/src/dotnet/APIView/APIView/Program.cs index d535bfea302..c0a34365426 100644 --- a/src/dotnet/APIView/APIView/Program.cs +++ b/src/dotnet/APIView/APIView/Program.cs @@ -1,22 +1,35 @@ -using System; +using System; +using System.IO; +using System.Threading.Tasks; namespace ApiView { - class Program + public class Program { - static void Main(string[] args) + async static Task Main(string[] args) { try { - var assemblySymbol = CompilationFactory.GetCompilation(args[0]); - var renderer = new CodeFileRenderer(); - var codeNode = new CodeFileBuilder().Build(assemblySymbol, false, null); - Console.WriteLine(renderer.Render(codeNode)); + await RunAsync(args); } catch (Exception e) { Console.WriteLine(e.ToString()); } } + + public async static Task RunAsync(string[] args) + { + if (args.Length != 2) + { + throw new ArgumentException("usage: ApiView [input-path] [output-path]", nameof(args)); + } + + var assemblySymbol = CompilationFactory.GetCompilation(args[0]); + var codeNode = new CodeFileBuilder().Build(assemblySymbol, false, null); + + using var fileStream = new FileStream(args[1], FileMode.Create, FileAccess.Write); + await codeNode.SerializeAsync(fileStream); + } } } diff --git a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs index cbbdd80a555..aba9d33e0f3 100644 --- a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs +++ b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs @@ -1,12 +1,11 @@ -using System; -using ApiView; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using ApiView; +using Microsoft.CodeAnalysis; using Xunit; using Xunit.Abstractions; @@ -51,14 +50,9 @@ public async Task VerifyFormatted(string name) private async Task AssertFormattingAsync(string code, string formatted) { - var project = DiagnosticProject.Create(typeof(CodeFileBuilderTests).Assembly, LanguageVersion.Latest, new[] { code }); - project = project.WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - - var compilation = await project.GetCompilationAsync(); - Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning)); + using var memoryStream = new MemoryStream(); - var memoryStream = new MemoryStream(); - compilation.Emit(memoryStream); + await Common.BuildDllAsync(memoryStream, code); memoryStream.Position = 0; var compilationFromDll = CompilationFactory.GetCompilation(memoryStream, null); diff --git a/src/dotnet/APIView/APIViewUnitTests/CodeFileRendererTests.cs b/src/dotnet/APIView/APIViewUnitTests/CodeFileRendererTests.cs index 3b27547974e..9baff587289 100644 --- a/src/dotnet/APIView/APIViewUnitTests/CodeFileRendererTests.cs +++ b/src/dotnet/APIView/APIViewUnitTests/CodeFileRendererTests.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Linq; -using System.Threading.Tasks; using ApiView; using APIViewWeb.Models; using Xunit; diff --git a/src/dotnet/APIView/APIViewUnitTests/CodeFileTests.cs b/src/dotnet/APIView/APIViewUnitTests/CodeFileTests.cs index 39c63137be6..8826264b7d0 100644 --- a/src/dotnet/APIView/APIViewUnitTests/CodeFileTests.cs +++ b/src/dotnet/APIView/APIViewUnitTests/CodeFileTests.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; -using System.Security.AccessControl; -using System.Text; using System.Threading.Tasks; using ApiView; -using APIView; -using APIView.Model; using Xunit; namespace APIViewUnitTests diff --git a/src/dotnet/APIView/APIViewUnitTests/Common.cs b/src/dotnet/APIView/APIViewUnitTests/Common.cs new file mode 100644 index 00000000000..a24b8a8592e --- /dev/null +++ b/src/dotnet/APIView/APIViewUnitTests/Common.cs @@ -0,0 +1,23 @@ +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace APIViewUnitTests +{ + internal static class Common + { + public static async Task BuildDllAsync(Stream stream, string code) + { + var project = DiagnosticProject.Create(typeof(CodeFileBuilderTests).Assembly, LanguageVersion.Latest, new[] { code }) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + var compilation = await project.GetCompilationAsync(); + Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning)); + + compilation.Emit(stream); + } + } +} diff --git a/src/dotnet/APIView/APIViewUnitTests/ProgramTests.cs b/src/dotnet/APIView/APIViewUnitTests/ProgramTests.cs new file mode 100644 index 00000000000..312905f6c27 --- /dev/null +++ b/src/dotnet/APIView/APIViewUnitTests/ProgramTests.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ApiView; +using Microsoft.TeamFoundation.Build.WebApi; +using Xunit; + +namespace APIViewUnitTests +{ + public class ProgramTests + { + public static IEnumerable ExactFormattingFiles + { + get + { + var assembly = typeof(CodeFileBuilderTests).Assembly; + return assembly.GetManifestResourceNames() + .Where(r => r.Contains("ExactFormatting")) + .Select(r => new object[] { r }) + .ToArray(); + } + } + + private string InputPath => Path.Combine(Path.GetTempPath(), "input.dll"); + + private string OutputPath => Path.Combine(Path.GetTempPath(), "output.json"); + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(3)] + public void RunAsyncThrowsWithIncorrectNumberOfArgs(int length) + { + var args = new string[length]; + + for (int i = 0; i < length; i++) + { + args[i] = InputPath; + } + + Assert.ThrowsAsync(async () => await Program.RunAsync(args)); + } + + [Fact] + public void RunAsyncThrowsWithMissingInputFile() + { + var args = new string[] + { + "missing_file.dll", + OutputPath + }; + + Assert.ThrowsAsync(async () => await Program.RunAsync(args)); + } + + [Fact] + public void RunAsyncThrowsWithMissingOutputFolder() + { + var args = new string[] + { + InputPath, + Path.Combine("missing_folder", "output.json") + }; + + Assert.ThrowsAsync(async () => await Program.RunAsync(args)); + } + + [Theory] + [MemberData(nameof(ExactFormattingFiles))] + public async Task RunAsyncGeneratesOutputFile(string name) + { + var manifestResourceStream = typeof(CodeFileBuilderTests).Assembly.GetManifestResourceStream(name); + using var streamReader = new StreamReader(manifestResourceStream); + var code = streamReader.ReadToEnd(); + + using (var fileStream = new FileStream(InputPath, FileMode.Create, FileAccess.Write)) + { + await Common.BuildDllAsync(fileStream, code); + } + + var args = new string[] { InputPath, OutputPath }; + + await Program.RunAsync(args); + + var output = await File.ReadAllTextAsync(OutputPath); + + var assemblySymbol = CompilationFactory.GetCompilation(InputPath); + var codeNode = new CodeFileBuilder().Build(assemblySymbol, false, null); + using var stream = new MemoryStream(); + + await codeNode.SerializeAsync(stream); + + var expectedOutput = Encoding.UTF8.GetString(stream.ToArray()); + + Assert.Equal(expectedOutput, output); + } + } +}