Skip to content

Commit d464920

Browse files
authored
tests: introduce API for creating complex test projects (#267)
1 parent 5f1e37d commit d464920

File tree

13 files changed

+408
-244
lines changed

13 files changed

+408
-244
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using Microsoft.CodeAnalysis;
3+
4+
public class CsProjectArguments
5+
{
6+
public TargetFramework TargetFramework { get; set; } = TargetFramework.Net6_0;
7+
public string[] Sources { get; set; }
8+
public PackageReference[] PackageReferences { get; set; } = Array.Empty<PackageReference>();
9+
public string Language { get; set; } = LanguageNames.CSharp;
10+
}
11+
12+
public static class CsProjectArgumentsExtensions
13+
{
14+
public static TCsProjectArguments WithTargetFramework<TCsProjectArguments>(this TCsProjectArguments arguments, TargetFramework targetFramework) where TCsProjectArguments : CsProjectArguments
15+
{
16+
arguments.TargetFramework = targetFramework;
17+
return arguments;
18+
}
19+
20+
public static TCsProjectArguments WithSources<TCsProjectArguments>(this TCsProjectArguments arguments, params string[] sources) where TCsProjectArguments : CsProjectArguments
21+
{
22+
arguments.Sources = sources;
23+
return arguments;
24+
}
25+
26+
public static TCsProjectArguments WithPackageReferences<TCsProjectArguments>(this TCsProjectArguments arguments, params PackageReference[] packageReferences) where TCsProjectArguments : CsProjectArguments
27+
{
28+
arguments.PackageReferences = packageReferences;
29+
return arguments;
30+
}
31+
}

src/FluentAssertions.Analyzers.TestUtils/CsProjectGenerator.cs

Lines changed: 23 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,6 @@ string GetSystemAssemblyPathByName(string assemblyName)
5959
return System.IO.Path.Combine(root, assemblyName);
6060
}
6161
}
62-
// based on http://code.fitness/post/2017/02/using-csharpscript-with-netstandard.html
63-
public static string GetSystemAssemblyPathByName(string assemblyName)
64-
{
65-
var root = System.IO.Path.GetDirectoryName(typeof(object).Assembly.Location);
66-
return System.IO.Path.Combine(root, assemblyName);
67-
}
6862

6963
private static readonly ImmutableArray<MetadataReference> References;
7064

@@ -73,40 +67,7 @@ public static string GetSystemAssemblyPathByName(string assemblyName)
7367
private static readonly string VisualBasicDefaultExt = "vb";
7468
private static readonly string TestProjectName = "TestProject";
7569

76-
/// <summary>
77-
/// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it.
78-
/// </summary>
79-
/// <param name="sources">Classes in the form of strings</param>
80-
/// <param name="language">The language the source code is in</param>
81-
/// <returns>A Tuple containing the Documents produced from the sources and their TextSpans if relevant</returns>
82-
public static Document[] GetDocuments(string[] sources, string language)
83-
{
84-
if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic)
85-
{
86-
throw new ArgumentException("Unsupported Language");
87-
}
88-
89-
var project = CreateProject(sources, language);
90-
var documents = project.Documents.ToArray();
91-
92-
if (sources.Length != documents.Length)
93-
{
94-
throw new SystemException("Amount of sources did not match amount of Documents created");
95-
}
96-
97-
return documents;
98-
}
99-
100-
/// <summary>
101-
/// Create a Document from a string through creating a project that contains it.
102-
/// </summary>
103-
/// <param name="source">Classes in the form of a string</param>
104-
/// <param name="language">The language the source code is in</param>
105-
/// <returns>A Document created from the source string</returns>
106-
public static Document CreateDocument(string source, string language = LanguageNames.CSharp)
107-
{
108-
return CreateProject(new[] { source }, language).Documents.First();
109-
}
70+
public static Document CreateDocument(CsProjectArguments arguments) => CreateProject(arguments).Documents.First();
11071

11172
/// <summary>
11273
/// Create a project using the inputted strings as sources.
@@ -115,27 +76,40 @@ public static Document CreateDocument(string source, string language = LanguageN
11576
/// <param name="language">The language the source code is in</param>
11677
/// <returns>A Project created out of the Documents created from the source strings</returns>
11778
public static Project CreateProject(string[] sources, string language = LanguageNames.CSharp)
79+
{
80+
var arguments = new CsProjectArguments
81+
{
82+
Language = language,
83+
Sources = sources,
84+
TargetFramework = TargetFramework.Net6_0,
85+
};
86+
return CreateProject(arguments).AddMetadataReferences(References);
87+
}
88+
89+
public static Project CreateProject(CsProjectArguments arguments)
11890
{
11991
string fileNamePrefix = DefaultFilePathPrefix;
120-
string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;
92+
string fileExt = arguments.Language is LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;
12193

12294
var projectId = ProjectId.CreateNewId(debugName: TestProjectName);
12395

12496
var solution = new AdhocWorkspace()
12597
.CurrentSolution
126-
.AddProject(projectId, TestProjectName, TestProjectName, language)
127-
.AddMetadataReferences(projectId, References);
98+
.AddProject(projectId, TestProjectName, TestProjectName, arguments.Language);
99+
foreach (var package in arguments.PackageReferences)
100+
{
101+
solution = solution.AddPackageReference(projectId, package);
102+
}
103+
104+
solution = solution.AddTargetFrameworkReference(projectId, arguments.TargetFramework);
128105

129-
int count = 0;
130-
foreach (var source in sources)
106+
for (int i = 0; i < arguments.Sources.Length; i++)
131107
{
132-
var newFileName = fileNamePrefix + count + "." + fileExt;
108+
var newFileName = fileNamePrefix + i + "." + fileExt;
133109
var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
134-
solution = solution.AddDocument(documentId, newFileName, SourceText.From(source));
135-
count++;
110+
solution = solution.AddDocument(documentId, newFileName, SourceText.From(arguments.Sources[i]));
136111
}
137112
return solution.GetProject(projectId);
138113
}
139114
}
140115
}
141-
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
public class PackageReference
2+
{
3+
public string Name { get; }
4+
public string Version { get; }
5+
public string Path { get; }
6+
7+
public PackageReference(string name, string version, string path) => (Name, Version, Path) = (name, version, path);
8+
9+
public static PackageReference FluentAssertions_6_12_0 { get; } = FluentAssertions("6.12.0");
10+
11+
public static PackageReference MSTestTestFramework_3_1_1 { get; } = new("MSTest.TestFramework", "3.1.1", "lib/netstandard2.0/");
12+
public static PackageReference XunitAssert_2_5_1 { get; } = new("xunit.assert", "2.5.1", "lib/netstandard1.1/");
13+
14+
public static PackageReference FluentAssertions(string version) => new("FluentAssertions", version, "lib/netstandard2.0/");
15+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.IO;
3+
using System.IO.Compression;
4+
using System.Net.Http;
5+
using System.Threading.Tasks;
6+
using Microsoft.CodeAnalysis;
7+
8+
public static class SolutionExtensions
9+
{
10+
private static readonly string NugetPackagesPath = Environment.GetEnvironmentVariable("NUGET_PACKAGES")
11+
?? (OperatingSystem.IsWindows() ? Environment.ExpandEnvironmentVariables("%userprofile%\\.nuget\\packages") : "~/.nuget/packages");
12+
13+
private static readonly HttpClient HttpClient = new HttpClient();
14+
15+
public static Solution AddPackageReference(this Solution solution, ProjectId projectId, PackageReference package)
16+
{
17+
DownloadPackageAsync(package.Name, package.Version).GetAwaiter().GetResult();
18+
19+
var packagePath = Path.Combine(NugetPackagesPath, package.Name, package.Version, package.Path);
20+
foreach (var dll in Directory.GetFiles(packagePath, "*.dll"))
21+
{
22+
solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(dll));
23+
}
24+
25+
return solution;
26+
}
27+
28+
public static Solution AddTargetFrameworkReference(this Solution solution, ProjectId projectId, TargetFramework targetFramework)
29+
{
30+
return targetFramework switch
31+
{
32+
TargetFramework.NetStandard2_0 => solution.AddPackageReference(projectId, new("NETStandard.Library", "2.0.3", "build/netstandard2.0/ref/")),
33+
TargetFramework.NetStandard2_1 => solution.AddPackageReference(projectId, new("NETStandard.Library.Ref", "2.1.0", "ref/netstandard2.1/ref/")),
34+
TargetFramework.Net6_0 => solution.AddPackageReference(projectId, new("Microsoft.NETCore.App.Ref", "6.0.25", "ref/net6.0/")),
35+
TargetFramework.Net7_0 => solution.AddPackageReference(projectId, new("Microsoft.NETCore.App.Ref", "7.0.14", "ref/net7.0/")),
36+
TargetFramework.Net8_0 => solution.AddPackageReference(projectId, new("Microsoft.NETCore.App.Ref", "8.0.0", "ref/net8.0/")),
37+
_ => throw new ArgumentOutOfRangeException(nameof(targetFramework), targetFramework, "Unknown target framework"),
38+
};
39+
}
40+
41+
private static async Task DownloadPackageAsync(string packageId, string version)
42+
{
43+
var packagePath = Path.Combine(NugetPackagesPath, packageId, version);
44+
if (Directory.Exists(packagePath))
45+
{
46+
return;
47+
}
48+
49+
await using var stream = await HttpClient.GetStreamAsync(new Uri($"https://www.nuget.org/api/v2/package/{packageId}/{version}")).ConfigureAwait(false);
50+
using var zip = new ZipArchive(stream, ZipArchiveMode.Read);
51+
52+
Directory.CreateDirectory(packagePath);
53+
zip.ExtractToDirectory(packagePath, overwriteFiles: true);
54+
}
55+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public enum TargetFramework
2+
{
3+
NetStandard2_0,
4+
NetStandard2_1,
5+
Net6_0,
6+
Net7_0,
7+
Net8_0,
8+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.Collections.Generic;
2+
using Microsoft.CodeAnalysis.CodeFixes;
3+
using Microsoft.CodeAnalysis.Diagnostics;
4+
5+
namespace FluentAssertions.Analyzers.Tests;
6+
7+
public class CodeFixVerifierArguments : CsProjectArguments
8+
{
9+
public List<string> FixedSources { get; } = new();
10+
11+
public List<DiagnosticAnalyzer> DiagnosticAnalyzers { get; } = new();
12+
13+
public List<CodeFixProvider> CodeFixProviders { get; } = new();
14+
15+
public CodeFixVerifierArguments() { }
16+
17+
public CodeFixVerifierArguments WithDiagnosticAnalyzer<TDiagnosticAnalyzer>() where TDiagnosticAnalyzer : DiagnosticAnalyzer, new()
18+
{
19+
DiagnosticAnalyzers.Add(new TDiagnosticAnalyzer());
20+
return this;
21+
}
22+
23+
public CodeFixVerifierArguments WithCodeFixProvider<TCodeFixProvider>() where TCodeFixProvider : CodeFixProvider, new()
24+
{
25+
CodeFixProviders.Add(new TCodeFixProvider());
26+
return this;
27+
}
28+
29+
public CodeFixVerifierArguments WithFixedSources(params string[] fixedSources)
30+
{
31+
FixedSources.AddRange(fixedSources);
32+
return this;
33+
}
34+
}

0 commit comments

Comments
 (0)