diff --git a/.build/.build.csproj b/.build/.build.csproj index 67d554b7ad..7dff94fa91 100644 --- a/.build/.build.csproj +++ b/.build/.build.csproj @@ -2,7 +2,7 @@ Exe net6.0 - + False CS0649;CS0169 @@ -12,6 +12,7 @@ + @@ -19,4 +20,4 @@ - \ No newline at end of file + diff --git a/.build/Build.CI.cs b/.build/Build.CI.cs index bfd360b36b..7de0234da0 100644 --- a/.build/Build.CI.cs +++ b/.build/Build.CI.cs @@ -31,6 +31,7 @@ internal class LocalConstants "ci-ignore", GitHubActionsImage.WindowsLatest, GitHubActionsImage.UbuntuLatest, + AutoGenerate = false, On = new[] { GitHubActionsTrigger.Push }, OnPushTags = new[] { "v*" }, OnPushBranches = new[] { "master", "main", "next" }, @@ -42,6 +43,7 @@ internal class LocalConstants GitHubActionsImage.MacOsLatest, GitHubActionsImage.WindowsLatest, GitHubActionsImage.UbuntuLatest, + AutoGenerate = false, On = new[] { GitHubActionsTrigger.Push }, OnPushTags = new[] { "v*" }, OnPushBranches = new[] { "master", "main", "next" }, @@ -64,7 +66,7 @@ internal class LocalConstants [PrintCIEnvironment] [UploadLogs] [TitleEvents] -public partial class Solution +public partial class NukeSolution { public static RocketSurgeonGitHubActionsConfiguration CiIgnoreMiddleware( RocketSurgeonGitHubActionsConfiguration configuration diff --git a/.build/Build.Metadata.cs b/.build/Build.Metadata.cs new file mode 100644 index 0000000000..c999d6cbff --- /dev/null +++ b/.build/Build.Metadata.cs @@ -0,0 +1,220 @@ +using System.Collections.Immutable; +using System.Text; +using System.Xml.Linq; +using NuGet.LibraryModel; +using NuGet.ProjectModel; +using NuGet.Versioning; +using Nuke.Common; +using Nuke.Common.ProjectModel; +using Nuke.Common.Utilities; + +public partial class NukeSolution : IParseGeneratorMetadata +{ +} + +/* + + + + <_rsgPackageReferenceList>@(PackageReference) + <_rsgPackageVersionList>@(PackageVersion) + + + + + + + + + <_rsgPackageReferenceList>@(PackageReference) + <_rsgPackageVersionList>@(PackageVersion) + + + + + + + + + + */ + + +public interface IParseGeneratorMetadata : IHaveSolution, IHaveOutputLogs, IHaveBuildTarget, IHaveRestoreTarget, IComprehendSources, IHaveGitVersion, + IComprehendTests, IHaveTestTarget, IHavePackTarget +{ + public Target LoadProjectData => _ => + _ + .After(Restore) + .DependentFor(Pack) + .Executes( + () => + { + var projects = new List(); + + projects.Add(new GeneratorItem("Rocket.Surgery.Extensions.Testing", ImmutableArray.Empty)); + var lockFileFormat = new LockFileFormat(); + foreach (var project in Solution + .AllProjects + .Where(z => z.GetProperty("IsMagicProject") == true)) + { + var lockFile = lockFileFormat.Read(project.Directory / "obj" / "project.assets.json")!; + var results = lockFile.PackageSpec.TargetFrameworks + .SelectMany( + z => z.Dependencies + .Where( + z => !z.AutoReferenced + && ( z.IncludeType & LibraryIncludeFlags.Compile ) != 0 + && z.ReferenceType == LibraryDependencyReferenceType.Direct + ), + (information, dependency) => ( target: information.TargetAlias, dependency ) + ) + .GroupBy(z => z.dependency.Name) + .Where(z => z.Count() == lockFile.PackageSpec.TargetFrameworks.Count) + .Select(z => z.First().dependency) + .Select( + z => new PackageReferenceItem( + z.Name, + lockFile.Libraries.First(x => x.Name == z.Name).Version + ) + ) + .ToList(); + + + projects.Add(new GeneratorItem(lockFile.PackageSpec.Name, results.ToImmutableArray())); + if (lockFile.PackageSpec.Name == "Rocket.Surgery.Extensions.Testing.XUnit") + { + projects.Add( + new GeneratorItem( + lockFile.PackageSpec.Name, + results + .Select(z => z with { Name = z.Name.Replace("xunit.abstractions", "xunit") }) + .ToImmutableArray() + ) + ); + } + } + + var targetsDoc = new XDocument(); + var implicitPackageReferencesTarget = new XElement("Target"); + var implicitCentralPackageVersionsTarget = new XElement("Target"); + var xProperties = new XElement("PropertyGroup"); + { + var xProject = new XElement("Project"); + var propertyGroup = XElement.Parse( + @"<_rsgPackageReferenceList>@(PackageReference)<_rsgPackageVersionList>@(PackageVersion)" + ); + + implicitPackageReferencesTarget.SetAttributeValue("Name", "AddRsgImplicitPackageReferences"); + implicitPackageReferencesTarget.SetAttributeValue("BeforeTargets", "CollectPackageReferences"); + implicitPackageReferencesTarget.SetAttributeValue( + "Condition", "'$(ManagePackageVersionsCentrally)' == 'true' and '$(ImplicitPackageReferences)' == 'true'" + ); + implicitPackageReferencesTarget.Add(propertyGroup.Clone()); + + implicitCentralPackageVersionsTarget.SetAttributeValue("Name", "AddRsgImplicitCentralPackageVersions"); + implicitCentralPackageVersionsTarget.SetAttributeValue("BeforeTargets", "CollectCentralPackageVersions"); + implicitCentralPackageVersionsTarget.SetAttributeValue("AfterTargets", "CollectPackageReferences"); + implicitCentralPackageVersionsTarget.SetAttributeValue( + "Condition", "'$(ManagePackageVersionsCentrally)' == 'true' and '$(ImplicitPackageReferences)' == 'true'" + ); + implicitCentralPackageVersionsTarget.Add(propertyGroup.Clone()); + + + var implicitTestingReferenceItemGroup = new XElement("ItemGroup"); + implicitTestingReferenceItemGroup.SetAttributeValue( + "Condition", "'$(ManagePackageVersionsCentrally)' == 'true' and '$(ImplicitPackageReferences)' == 'true'" + ); + + var defaultPackageReference = new XElement("PackageReference"); + defaultPackageReference.SetAttributeValue("Include", "Rocket.Surgery.Extensions.Testing"); + implicitTestingReferenceItemGroup.Add(defaultPackageReference); + xProject.Add(implicitTestingReferenceItemGroup); + + xProject.Add(implicitPackageReferencesTarget); + xProject.Add(implicitCentralPackageVersionsTarget); + targetsDoc.Add(xProject); + } + var propsDoc = new XDocument(); + { + var xProject = new XElement("Project"); + propsDoc.Add(xProject); + xProject.Add(xProperties); + { + var prop = new XElement("ImplicitPackageReferences"); + xProperties.Add(prop); + prop.SetValue("true"); + prop.SetAttributeValue("Condition", "'$(ImplicitPackageReferences)' == ''"); + } + { + var prop = new XElement("ImplicitPackageReferenceWarning"); + xProperties.Add(prop); + prop.SetValue("true"); + prop.SetAttributeValue("Condition", "'$(ImplicitPackageReferenceWarning)' == ''"); + } + } + + var addedItems = new HashSet(); + foreach (var project in projects) + { + var version = GitVersion.FullSemVer; + + var conditionPropertyName = $"ImplicitPackageReference{project.AssemblyName.Replace(".", "")}"; + if (!addedItems.Contains(project.AssemblyName)) + { + var enabledProperty = new XElement(conditionPropertyName); + enabledProperty.SetValue("true"); + enabledProperty.SetAttributeValue("Condition", $"'$({conditionPropertyName})' == ''"); + xProperties.Add(enabledProperty); + } + + var packageReferenceItemGroup = new XElement("ItemGroup"); + var conditionBuilder = new StringBuilder(); + conditionBuilder + .Append("'$(").Append(conditionPropertyName).Append(")' == 'true' "); + if (project.PackageReferences.Length > 0) + { + conditionBuilder + .Append("and ") + .AppendJoin(" and ", project.PackageReferences.Select(z => $"$(_rsgPackageReferenceList.Contains('{z.Name}'))")); + } + + packageReferenceItemGroup.SetAttributeValue( + "Condition", conditionBuilder + $" and !$(_rsgPackageReferenceList.Contains('{project.AssemblyName}'))" + ); + + var packageReference = new XElement("PackageReference"); + packageReferenceItemGroup.Add(packageReference); + packageReference.SetAttributeValue("Include", project.AssemblyName); + implicitPackageReferencesTarget.Add(packageReferenceItemGroup); + + conditionBuilder.Append(" and !$(_rsgPackageVersionList.Contains('").Append(project.AssemblyName).Append("'))"); + + var packageVersionWarning = new XElement("Warning"); + packageVersionWarning.SetAttributeValue("Condition", $"'$(ImplicitPackageReferenceWarning)' == 'true' and {conditionBuilder}"); + packageVersionWarning.SetAttributeValue( + "Text", + $"PackageReference to {project.AssemblyName} has been added implicitly using default version {version}. Add or disable this warning with false. Use false to disable all implicit package references or <{conditionPropertyName}>false to disable only this implicit reference." + ); + var packageVersionItemGroup = new XElement("ItemGroup"); + packageVersionItemGroup.SetAttributeValue("Condition", conditionBuilder); + var packageVersion = new XElement("PackageVersion"); + packageVersion.SetAttributeValue("Include", project.AssemblyName); + packageVersion.SetAttributeValue("Version", version); + packageVersionItemGroup.Add(packageVersion); + implicitCentralPackageVersionsTarget.Add(packageVersionWarning); + implicitCentralPackageVersionsTarget.Add(packageVersionItemGroup); + addedItems.Add(project.AssemblyName); + } + + // src/Testing/build/Rocket.Surgery.Extensions.Testing.props + + propsDoc.Save(SourceDirectory / "Testing" / "Sdk" / "ImplicitPackageReferences.props"); + targetsDoc.Save(SourceDirectory / "Testing" / "Sdk" / "ImplicitPackageReferences.targets"); + } + ); +} + +public record GeneratorItem(string AssemblyName, ImmutableArray PackageReferences); + +public record PackageReferenceItem(string Name, NuGetVersion Version); diff --git a/.build/Build.cs b/.build/Build.cs index 110e14460d..b00a19bcda 100644 --- a/.build/Build.cs +++ b/.build/Build.cs @@ -6,68 +6,67 @@ using Nuke.Common.Tools.DotNet; using Nuke.Common.Tools.GitVersion; using Nuke.Common.Tools.MSBuild; -using Rocket.Surgery.Nuke; using Rocket.Surgery.Nuke.DotNetCore; [PublicAPI] [CheckBuildProjectConfigurations] [UnsetVisualStudioEnvironmentVariables] [PackageIcon("https://raw.githubusercontent.com/RocketSurgeonsGuild/graphics/master/png/social-square-thrust-rounded.png")] -[EnsureGitHooks(GitHook.PreCommit)] +//[EnsureGitHooks(GitHook.PreCommit)] [EnsureReadmeIsUpdated("Readme.md")] [DotNetVerbosityMapping] [MSBuildVerbosityMapping] [NuGetVerbosityMapping] [ShutdownDotNetAfterServerBuild] -public partial class Solution : NukeBuild, - ICanRestoreWithDotNetCore, - ICanBuildWithDotNetCore, - ICanTestWithDotNetCore, - ICanPackWithDotNetCore, - IHaveDataCollector, - ICanClean, - ICanUpdateReadme, - IGenerateCodeCoverageReport, - IGenerateCodeCoverageSummary, - IGenerateCodeCoverageBadges, - IHaveConfiguration, - ICanLint +public partial class NukeSolution : NukeBuild, + ICanRestoreWithDotNetCore, + ICanBuildWithDotNetCore, + ICanTestWithDotNetCore, + ICanPackWithDotNetCore, + IHaveDataCollector, + ICanClean, + ICanUpdateReadme, + IGenerateCodeCoverageReport, + IGenerateCodeCoverageSummary, + IGenerateCodeCoverageBadges, + IHaveConfiguration + { /// - /// Support plugins are available for: - /// - JetBrains ReSharper https://nuke.build/resharper - /// - JetBrains Rider https://nuke.build/rider - /// - Microsoft VisualStudio https://nuke.build/visualstudio - /// - Microsoft VSCode https://nuke.build/vscode + /// Support plugins are available for: + /// - JetBrains ReSharper https://nuke.build/resharper + /// - JetBrains Rider https://nuke.build/rider + /// - Microsoft VisualStudio https://nuke.build/visualstudio + /// - Microsoft VSCode https://nuke.build/vscode /// - public static int Main() => Execute(x => x.Default); + public static int Main() + { + return Execute(x => x.Default); + } - [OptionalGitRepository] - public GitRepository? GitRepository { get; } + [OptionalGitRepository] public GitRepository? GitRepository { get; } private Target Default => _ => _ - .DependsOn(Restore) - .DependsOn(Build) - .DependsOn(Test) - .DependsOn(Pack); + .DependsOn(Restore) + .DependsOn(Build) + .DependsOn(Test) + .DependsOn(Pack); public Target Build => _ => _.Inherit(x => x.CoreBuild); public Target Pack => _ => _.Inherit(x => x.CorePack) - .DependsOn(Clean); + .DependsOn(Clean) + .After(Test); - [ComputedGitVersion] - public GitVersion GitVersion { get; } = null!; + [ComputedGitVersion] public GitVersion GitVersion { get; } = null!; public Target Clean => _ => _.Inherit(x => x.Clean); - public Target Lint => _ => _.Inherit(x => x.Lint); public Target Restore => _ => _.Inherit(x => x.CoreRestore); public Target Test => _ => _.Inherit(x => x.CoreTest); public Target BuildVersion => _ => _.Inherit(x => x.BuildVersion) - .Before(Default) - .Before(Clean); + .Before(Default) + .Before(Clean); - [Parameter("Configuration to build")] - public Configuration Configuration { get; } = IsLocalBuild ? Configuration.Debug : Configuration.Release; -} \ No newline at end of file + [Parameter("Configuration to build")] public Configuration Configuration { get; } = IsLocalBuild ? Configuration.Debug : Configuration.Release; +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7948bb4113..0112380b7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,10 +84,6 @@ jobs: uses: actions/setup-dotnet@v1.9.0 with: dotnet-version: '3.1.x' - - name: 🔨 Use .NET Core 5.0 SDK - uses: actions/setup-dotnet@v1.9.0 - with: - dotnet-version: '5.0.x' - name: 🔨 Use .NET Core 6.0 SDK uses: actions/setup-dotnet@v1.9.0 with: @@ -98,6 +94,9 @@ jobs: - name: 🎁 Restore run: | dotnet nuke Restore --skip + - name: Load Project Data + run: | + dotnet nuke LoadProjectData --skip - name: ⚙ Build run: | dotnet nuke Build --skip diff --git a/.gitignore b/.gitignore index 8cbd569933..ec198b31be 100644 --- a/.gitignore +++ b/.gitignore @@ -210,4 +210,5 @@ codealike.json .idea/ # mac OS -.DS_STORE \ No newline at end of file +.DS_STORE +/test/Testing.Tests/Fixtures/Directory.Packages.props diff --git a/.husky/pre-commit b/.husky/pre-commit index c32e97ffee..104008d081 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,3 +2,7 @@ . "$(dirname "$0")/_/husky.sh" npx lint-staged -d -r +dotnet nuke --generate-configuration GitHubActions_ci --host GitHubActions +git add .github/workflows/ci.yml +dotnet nuke --generate-configuration GitHubActions_ci-ignore --host GitHubActions +git add .github/workflows/ci-ignore.yml diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 6932e74056..63845730ae 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -94,7 +94,7 @@ "GenerateCodeCoverageReportCobertura", "GenerateCodeCoverageSummary", "GenerateReadme", - "Lint", + "LoadProjectData", "Pack", "Restore", "Test", @@ -131,7 +131,7 @@ "GenerateCodeCoverageReportCobertura", "GenerateCodeCoverageSummary", "GenerateReadme", - "Lint", + "LoadProjectData", "Pack", "Restore", "Test", diff --git a/Directory.Packages.props b/Directory.Packages.props index a5d699a14a..bd30d9b81b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,4 +1,4 @@ - + @@ -15,9 +15,13 @@ - + + + + + @@ -51,5 +55,8 @@ - - \ No newline at end of file + + diff --git a/Readme.md b/Readme.md index 305fface1c..c628ce0eba 100644 --- a/Readme.md +++ b/Readme.md @@ -19,6 +19,7 @@ | Package | NuGet | | ------- | ----- | | Rocket.Surgery.Extensions.Testing | [![nuget-version-ingbk+ngdt+w-badge]![nuget-downloads-ingbk+ngdt+w-badge]][nuget-ingbk+ngdt+w] | +| Rocket.Surgery.Extensions.Testing.Analyzers | [![nuget-version-is2d/pp3l2nq-badge]![nuget-downloads-is2d/pp3l2nq-badge]][nuget-is2d/pp3l2nq] | | Rocket.Surgery.Extensions.Testing.Coverlet | [![nuget-version-2jdbmqdcrhfg-badge]![nuget-downloads-2jdbmqdcrhfg-badge]][nuget-2jdbmqdcrhfg] | | Rocket.Surgery.Extensions.Testing.FakeItEasy | [![nuget-version-6rnnzg4ixtvq-badge]![nuget-downloads-6rnnzg4ixtvq-badge]][nuget-6rnnzg4ixtvq] | | Rocket.Surgery.Extensions.Testing.Fixtures | [![nuget-version-xegxxxxh/pzg-badge]![nuget-downloads-xegxxxxh/pzg-badge]][nuget-xegxxxxh/pzg] | @@ -44,6 +45,9 @@ TBD [nuget-ingbk+ngdt+w]: https://www.nuget.org/packages/Rocket.Surgery.Extensions.Testing/ [nuget-version-ingbk+ngdt+w-badge]: https://img.shields.io/nuget/v/Rocket.Surgery.Extensions.Testing.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" [nuget-downloads-ingbk+ngdt+w-badge]: https://img.shields.io/nuget/dt/Rocket.Surgery.Extensions.Testing.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" +[nuget-is2d/pp3l2nq]: https://www.nuget.org/packages/Rocket.Surgery.Extensions.Testing.Analyzers/ +[nuget-version-is2d/pp3l2nq-badge]: https://img.shields.io/nuget/v/Rocket.Surgery.Extensions.Testing.Analyzers.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" +[nuget-downloads-is2d/pp3l2nq-badge]: https://img.shields.io/nuget/dt/Rocket.Surgery.Extensions.Testing.Analyzers.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" [nuget-2jdbmqdcrhfg]: https://www.nuget.org/packages/Rocket.Surgery.Extensions.Testing.Coverlet/ [nuget-version-2jdbmqdcrhfg-badge]: https://img.shields.io/nuget/v/Rocket.Surgery.Extensions.Testing.Coverlet.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" [nuget-downloads-2jdbmqdcrhfg-badge]: https://img.shields.io/nuget/dt/Rocket.Surgery.Extensions.Testing.Coverlet.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" diff --git a/Testing.sln b/Testing.sln index f82bd1b71e..79f852b7c8 100644 --- a/Testing.sln +++ b/Testing.sln @@ -39,6 +39,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rocket.Surgery.Extensions.T EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rocket.Surgery.Extensions.Testing.XUnit", "src\Testing.XUnit\Rocket.Surgery.Extensions.Testing.XUnit.csproj", "{88A55AFE-EDA6-410C-B576-8B960688D113}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rocket.Surgery.Extensions.Testing.Analyzers", "src\Analyzers\Rocket.Surgery.Extensions.Testing.Analyzers.csproj", "{CAB5BF83-775D-4457-ABC6-F9A74E230E96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Analyzers.Tests", "test\Analyzers.Tests\Analyzers.Tests.csproj", "{DFB78690-0AC5-4B48-9A07-5D321CA4A865}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -167,6 +171,30 @@ Global {88A55AFE-EDA6-410C-B576-8B960688D113}.Release|x64.Build.0 = Release|Any CPU {88A55AFE-EDA6-410C-B576-8B960688D113}.Release|x86.ActiveCfg = Release|Any CPU {88A55AFE-EDA6-410C-B576-8B960688D113}.Release|x86.Build.0 = Release|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Debug|x64.ActiveCfg = Debug|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Debug|x64.Build.0 = Debug|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Debug|x86.ActiveCfg = Debug|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Debug|x86.Build.0 = Debug|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Release|Any CPU.Build.0 = Release|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Release|x64.ActiveCfg = Release|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Release|x64.Build.0 = Release|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Release|x86.ActiveCfg = Release|Any CPU + {CAB5BF83-775D-4457-ABC6-F9A74E230E96}.Release|x86.Build.0 = Release|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Debug|x64.ActiveCfg = Debug|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Debug|x64.Build.0 = Debug|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Debug|x86.ActiveCfg = Debug|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Debug|x86.Build.0 = Debug|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Release|Any CPU.Build.0 = Release|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Release|x64.ActiveCfg = Release|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Release|x64.Build.0 = Release|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Release|x86.ActiveCfg = Release|Any CPU + {DFB78690-0AC5-4B48-9A07-5D321CA4A865}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -182,6 +210,8 @@ Global {961EE399-F31D-40A9-9559-F9568BCF5088} = {8FFDF555-DB50-45F9-9A2D-6410F39151C3} {AFEE7BD7-6EAE-41F7-B2F6-0B3EB04F5D4D} = {8FFDF555-DB50-45F9-9A2D-6410F39151C3} {88A55AFE-EDA6-410C-B576-8B960688D113} = {8FFDF555-DB50-45F9-9A2D-6410F39151C3} + {CAB5BF83-775D-4457-ABC6-F9A74E230E96} = {8FFDF555-DB50-45F9-9A2D-6410F39151C3} + {DFB78690-0AC5-4B48-9A07-5D321CA4A865} = {DF33E0FB-9790-4654-B60F-8AB22E0CC3D1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {439897C2-CCBD-44FE-B2DC-A3E4670ADA59} diff --git a/src/Analyzers/MutableGenerator.cs b/src/Analyzers/MutableGenerator.cs new file mode 100644 index 0000000000..f856bf0c10 --- /dev/null +++ b/src/Analyzers/MutableGenerator.cs @@ -0,0 +1,36 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Rocket.Surgery.Extensions.Testing.Analyzers +{ + /// + /// Generator to convert an immutable type into a mutable one + /// + [Generator] + public class MutableGenerator : ISourceGenerator + { + /// + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + /// + public void Execute(GeneratorExecutionContext context) + { + if (!( context.SyntaxReceiver is SyntaxReceiver syntaxReceiver )) + { + return; + } + + var compliation = ( context.Compilation as CSharpCompilation )!; + } + + private class SyntaxReceiver : ISyntaxReceiver + { + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + } + } + } +} diff --git a/src/Analyzers/Rocket.Surgery.Extensions.Testing.Analyzers.csproj b/src/Analyzers/Rocket.Surgery.Extensions.Testing.Analyzers.csproj new file mode 100644 index 0000000000..61f57eb1aa --- /dev/null +++ b/src/Analyzers/Rocket.Surgery.Extensions.Testing.Analyzers.csproj @@ -0,0 +1,13 @@ + + + netstandard2.0 + true + + + + + + + + + diff --git a/src/Testing.FakeItEasy/Rocket.Surgery.Extensions.Testing.FakeItEasy.csproj b/src/Testing.FakeItEasy/Rocket.Surgery.Extensions.Testing.FakeItEasy.csproj index 1303f8d35a..8494e4a9da 100644 --- a/src/Testing.FakeItEasy/Rocket.Surgery.Extensions.Testing.FakeItEasy.csproj +++ b/src/Testing.FakeItEasy/Rocket.Surgery.Extensions.Testing.FakeItEasy.csproj @@ -3,11 +3,12 @@ netstandard2.1;netstandard2.0;net6.0 false Rocket.Surgery.Extensions.Testing + true - + diff --git a/src/Testing.Moq/Rocket.Surgery.Extensions.Testing.Moq.csproj b/src/Testing.Moq/Rocket.Surgery.Extensions.Testing.Moq.csproj index 7111131d28..fe5b00a6a7 100644 --- a/src/Testing.Moq/Rocket.Surgery.Extensions.Testing.Moq.csproj +++ b/src/Testing.Moq/Rocket.Surgery.Extensions.Testing.Moq.csproj @@ -3,6 +3,7 @@ netstandard2.1;netstandard2.0;net6.0 false Rocket.Surgery.Extensions.Testing + true diff --git a/src/Testing.NSubstitute/Rocket.Surgery.Extensions.Testing.NSubstitute.csproj b/src/Testing.NSubstitute/Rocket.Surgery.Extensions.Testing.NSubstitute.csproj index e48d22ae47..d93c7f353f 100644 --- a/src/Testing.NSubstitute/Rocket.Surgery.Extensions.Testing.NSubstitute.csproj +++ b/src/Testing.NSubstitute/Rocket.Surgery.Extensions.Testing.NSubstitute.csproj @@ -4,11 +4,12 @@ netstandard2.1;netstandard2.0;net6.0 false Rocket.Surgery.Extensions.Testing + true - + diff --git a/src/Testing.XUnit/Rocket.Surgery.Extensions.Testing.XUnit.csproj b/src/Testing.XUnit/Rocket.Surgery.Extensions.Testing.XUnit.csproj index 05e3710f25..9a455b7cea 100644 --- a/src/Testing.XUnit/Rocket.Surgery.Extensions.Testing.XUnit.csproj +++ b/src/Testing.XUnit/Rocket.Surgery.Extensions.Testing.XUnit.csproj @@ -3,6 +3,7 @@ netstandard2.1;netstandard2.0;net6.0 false Rocket.Surgery.Extensions.Testing + true diff --git a/src/Testing.XUnit/XUnitExtensions.cs b/src/Testing.XUnit/XUnitExtensions.cs index 29e7ff2f17..9b5ffd07c4 100644 --- a/src/Testing.XUnit/XUnitExtensions.cs +++ b/src/Testing.XUnit/XUnitExtensions.cs @@ -2,7 +2,7 @@ using Xunit.Abstractions; // ReSharper disable once CheckNamespace -namespace xunit; +namespace Xunit; /// /// Helpful XUnit Extensions @@ -20,4 +20,4 @@ public static ITest GetTest(this ITestOutputHelper output) var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic)!; return (ITest)testMember.GetValue(output)!; } -} \ No newline at end of file +} diff --git a/src/Testing/LoggerTest.cs b/src/Testing/LoggerTest.cs index 3a1d07e873..d049e3255a 100644 --- a/src/Testing/LoggerTest.cs +++ b/src/Testing/LoggerTest.cs @@ -48,6 +48,8 @@ public abstract class LoggerTest : IDisposable /// protected DiagnosticSource DiagnosticSource => _values.Value.diagnosticSource; + protected ITestOutputHelper TestOutputHelper { get; } + /// /// The /// @@ -108,6 +110,7 @@ protected LoggerTest( ) { Disposables = new CompositeDisposable(); + TestOutputHelper = outputHelper; _values = new Lazy<(IMsftLogger logger, ILoggerFactory loggerFactory, ISeriLogger serilogLogger, DiagnosticSource diagnosticSource, IObservable diff --git a/src/Testing/Rocket.Surgery.Extensions.Testing.csproj b/src/Testing/Rocket.Surgery.Extensions.Testing.csproj index 5adb781ecc..df00ccb17d 100644 --- a/src/Testing/Rocket.Surgery.Extensions.Testing.csproj +++ b/src/Testing/Rocket.Surgery.Extensions.Testing.csproj @@ -21,11 +21,18 @@ - - + + + diff --git a/src/Testing/Sdk/ImplicitPackageReferences.props b/src/Testing/Sdk/ImplicitPackageReferences.props new file mode 100644 index 0000000000..d8eedf282a --- /dev/null +++ b/src/Testing/Sdk/ImplicitPackageReferences.props @@ -0,0 +1,22 @@ + + + + true + true + true + true + true + true + true + + diff --git a/src/Testing/Sdk/ImplicitPackageReferences.targets b/src/Testing/Sdk/ImplicitPackageReferences.targets new file mode 100644 index 0000000000..892be2d379 --- /dev/null +++ b/src/Testing/Sdk/ImplicitPackageReferences.targets @@ -0,0 +1,111 @@ + + + + + + + + <_rsgPackageReferenceList>@(PackageReference) + <_rsgPackageVersionList>@(PackageVersion) + + + + + + + + + + + + + + + + + + + + + + + <_rsgPackageReferenceList>@(PackageReference) + <_rsgPackageVersionList>@(PackageVersion) + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Testing/Sdk/Sdk.props b/src/Testing/Sdk/Sdk.props new file mode 100644 index 0000000000..8961ec6bb5 --- /dev/null +++ b/src/Testing/Sdk/Sdk.props @@ -0,0 +1,3 @@ + + + diff --git a/src/Testing/Sdk/Sdk.targets b/src/Testing/Sdk/Sdk.targets new file mode 100644 index 0000000000..34b28848d5 --- /dev/null +++ b/src/Testing/Sdk/Sdk.targets @@ -0,0 +1,3 @@ + + + diff --git a/src/Testing/build/Rocket.Surgery.Extensions.Testing.props b/src/Testing/build/Rocket.Surgery.Extensions.Testing.props index 542ab9da6b..5d204031f1 100644 --- a/src/Testing/build/Rocket.Surgery.Extensions.Testing.props +++ b/src/Testing/build/Rocket.Surgery.Extensions.Testing.props @@ -1,7 +1,11 @@  - [Bogus*]*,[Autofac*]*,[FakeItEasy*]*,[Moq*]*,[FluentValidation*]*,[Ben.Demystifier*]*,[Humanizer*]*,[xunit*]*,[Microsoft.*]*,[XunitXml*]*,[coverlet.*]*,[System.*]*,[*]JetBrains.Annotations* - [Bogus*]*,[Autofac*]*,[FakeItEasy*]*,[Moq*]*,[FluentValidation*]*,[Ben.Demystifier*]*,[Humanizer*]*,[xunit*]*,[Microsoft.*]*,[XunitXml*]*,[coverlet.*]*,[System.*]*,[*]JetBrains.Annotations*,$(Exclude) + [Bogus*]*,[Autofac*]*,[FakeItEasy*]*,[Moq*]*,[FluentValidation*]*,[Ben.Demystifier*]*,[Humanizer*]*,[xunit*]*,[Microsoft.*]*,[XunitXml*]*,[coverlet.*]*,[System.*]*,[*]JetBrains.Annotations* + [Bogus*]*,[Autofac*]*,[FakeItEasy*]*,[Moq*]*,[FluentValidation*]*,[Ben.Demystifier*]*,[Humanizer*]*,[xunit*]*,[Microsoft.*]*,[XunitXml*]*,[coverlet.*]*,[System.*]*,[*]JetBrains.Annotations*,$(Exclude) diff --git a/src/Testing/build/Rocket.Surgery.Extensions.Testing.targets b/src/Testing/build/Rocket.Surgery.Extensions.Testing.targets index a139622d8f..f75adf7e4d 100644 --- a/src/Testing/build/Rocket.Surgery.Extensions.Testing.targets +++ b/src/Testing/build/Rocket.Surgery.Extensions.Testing.targets @@ -1,7 +1,2 @@  - - - PreserveNewest - - diff --git a/test/Analyzers.Tests/Analyzers.Tests.csproj b/test/Analyzers.Tests/Analyzers.Tests.csproj new file mode 100644 index 0000000000..27abccf247 --- /dev/null +++ b/test/Analyzers.Tests/Analyzers.Tests.csproj @@ -0,0 +1,16 @@ + + + net6.0 + + + + + + + + + + + + + diff --git a/test/Analyzers.Tests/Helpers/CollectibleTestAssemblyLoadContext.cs b/test/Analyzers.Tests/Helpers/CollectibleTestAssemblyLoadContext.cs new file mode 100644 index 0000000000..4af85bca76 --- /dev/null +++ b/test/Analyzers.Tests/Helpers/CollectibleTestAssemblyLoadContext.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Analyzers.Tests.Helpers; + +internal class CollectibleTestAssemblyLoadContext : AssemblyLoadContext, IDisposable +{ + protected override Assembly? Load(AssemblyName assemblyName) + { + return null; + } + + public void Dispose() + { +#if NETCOREAPP3_1 + Unload(); +#endif + } +} diff --git a/test/Analyzers.Tests/Helpers/GenerationHelpers.cs b/test/Analyzers.Tests/Helpers/GenerationHelpers.cs new file mode 100644 index 0000000000..b1e807bc4a --- /dev/null +++ b/test/Analyzers.Tests/Helpers/GenerationHelpers.cs @@ -0,0 +1,57 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; + +namespace Analyzers.Tests.Helpers; + +public static class GenerationHelpers +{ + internal const string CrLf = "\r\n"; + internal const string Lf = "\n"; + internal const string DefaultFilePathPrefix = "Test"; + internal const string CSharpDefaultFileExt = "cs"; + internal const string TestProjectName = "TestProject"; + + public static Project CreateProject( + string projectName, + IEnumerable metadataReferences, + params SourceText[] sources + ) + { + var projectId = ProjectId.CreateNewId(projectName); + var solution = new AdhocWorkspace() + .CurrentSolution + .AddProject(projectId, projectName, projectName, LanguageNames.CSharp) + .WithProjectCompilationOptions( + projectId, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ) + .WithProjectParseOptions( + projectId, + new CSharpParseOptions(preprocessorSymbols: new[] { "SOMETHING_ACTIVE" }) + ) + .AddMetadataReferences(projectId, metadataReferences); + + var count = 0; + foreach (var source in sources) + { + var newFileName = DefaultFilePathPrefix + count + "." + CSharpDefaultFileExt; + var documentId = DocumentId.CreateNewId(projectId, newFileName); + solution = solution.AddDocument(documentId, newFileName, source); + count++; + } + + var project = solution.GetProject(projectId); + if (project is null) + { + throw new InvalidOperationException($"The ad hoc workspace does not contain a project with the id {projectId.Id}"); + } + + return project; + } + + public static Project CreateProject(IEnumerable metadataReferences, params SourceText[] sources) + { + return CreateProject(TestProjectName, metadataReferences, sources); + } +} diff --git a/test/Analyzers.Tests/Helpers/GenerationTestResult.cs b/test/Analyzers.Tests/Helpers/GenerationTestResult.cs new file mode 100644 index 0000000000..76ebf559d6 --- /dev/null +++ b/test/Analyzers.Tests/Helpers/GenerationTestResult.cs @@ -0,0 +1,61 @@ +using System.Collections.Immutable; +using FluentAssertions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.Logging; +using Xunit; +using Xunit.Sdk; + +namespace Analyzers.Tests.Helpers; + +public record GenerationTestResult( + CSharpCompilation Compilation, + ImmutableArray Diagnostics, + ImmutableArray SyntaxTrees, + ILogger Logger +) +{ + public void EnsureDiagnosticSeverity(DiagnosticSeverity severity = DiagnosticSeverity.Warning) + { + Diagnostics.Where(x => x.Severity >= severity).Should().HaveCount(0); + } + + public void AssertGeneratedAsExpected(string expectedValue, params string[] expectedValues) + { + // normalize line endings to just LF + var generatedText = SyntaxTrees.Select(z => NormalizeToLf(z.GetText().ToString()).Trim()).ToArray(); + // and append preamble to the expected + var expectedText = new[] { expectedValue }.Concat(expectedValues).Select(z => NormalizeToLf(z).Trim()).ToArray(); + + generatedText.Should().HaveCount(expectedText.Length); + foreach (var (generated, expectedTxt) in generatedText.Zip(expectedText, (generated, expected) => ( generated, expected ))) + { + try + { + generated.Should().Be(expectedTxt); + } + catch + { + try + { + Assert.Equal(generated, expectedTxt); + } + catch (EqualException e2) + { + Logger.LogCritical(e2.Message); + } + catch + { + // ignore + } + + throw; + } + } + } + + public static string NormalizeToLf(string input) + { + return input.Replace(GenerationHelpers.CrLf, GenerationHelpers.Lf); + } +} diff --git a/test/Analyzers.Tests/Helpers/GenerationTestResults.cs b/test/Analyzers.Tests/Helpers/GenerationTestResults.cs new file mode 100644 index 0000000000..7c68a7a18a --- /dev/null +++ b/test/Analyzers.Tests/Helpers/GenerationTestResults.cs @@ -0,0 +1,84 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace Analyzers.Tests.Helpers; + +public record GenerationTestResults( + CSharpCompilation InputCompilation, + ImmutableArray InputDiagnostics, + ImmutableArray InputSyntaxTrees, + ImmutableDictionary Results, + CSharpCompilation FinalCompilation, + ImmutableArray FinalDiagnostics, + Assembly? Assembly +) +{ + public bool TryGetResult(Type type, [NotNullWhen(true)] out GenerationTestResult? result) + { + return Results.TryGetValue(type, out result); + } + + public bool TryGetResult([NotNullWhen(true)] out GenerationTestResult? result) + where T : new() + { + return Results.TryGetValue(typeof(T), out result); + } + + public void EnsureDiagnosticSeverity(DiagnosticSeverity severity = DiagnosticSeverity.Warning) + { + Assert.Empty(InputDiagnostics.Where(x => x.Severity >= severity)); + foreach (var result in Results.Values) + { + result.EnsureDiagnosticSeverity(severity); + } + } + + public void AssertGeneratedAsExpected(string expectedValue, params string[] expectedValues) + where T : new() + { + if (!TryGetResult(out var result)) + { + Assert.NotNull(result); + return; + } + + result.AssertGeneratedAsExpected(expectedValue); + } + + public void AssertCompilationWasSuccessful() + { + Assert.Empty( + InputDiagnostics + .Where(z => !z.GetMessage().Contains("does not contain a definition for")) + .Where(z => !z.GetMessage().Contains("Assuming assembly reference")) + .Where(x => x.Severity >= DiagnosticSeverity.Warning) + ); + foreach (var result in Results.Values) + { + result.EnsureDiagnosticSeverity(); + } + } + + public void AssertGenerationWasSuccessful() + { + foreach (var item in Results.Values) + { + Assert.NotNull(item.Compilation); + item.EnsureDiagnosticSeverity(); + } + } + + public static implicit operator CSharpCompilation(GenerationTestResults results) + { + return results.FinalCompilation; + } + + public static implicit operator Assembly?(GenerationTestResults results) + { + return results.Assembly; + } +} diff --git a/test/Analyzers.Tests/Helpers/GeneratorTest.cs b/test/Analyzers.Tests/Helpers/GeneratorTest.cs new file mode 100644 index 0000000000..52e8050fc1 --- /dev/null +++ b/test/Analyzers.Tests/Helpers/GeneratorTest.cs @@ -0,0 +1,370 @@ +using System.Collections.Immutable; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using Castle.Core; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Rocket.Surgery.Extensions.Testing; +using Xunit; +using Xunit.Abstractions; + +namespace Analyzers.Tests.Helpers; + +public static class Helpers +{ + public static Assembly EmitInto(this CSharpCompilation compilation, AssemblyLoadContext context, string? outputName = null) + { + using var stream = new MemoryStream(); + var emitResult = compilation.Emit(stream, options: new EmitOptions(outputNameOverride: outputName)); + if (!emitResult.Success) + { + Assert.Empty(emitResult.Diagnostics); + } + + var data = stream.ToArray(); + + using var assemblyStream = new MemoryStream(data); + return context.LoadFromStream(assemblyStream); + } + + public static MetadataReference CreateMetadataReference(this CSharpCompilation compilation) + { + using var stream = new MemoryStream(); + var emitResult = compilation.Emit(stream, options: new EmitOptions(outputNameOverride: compilation.AssemblyName)); + if (!emitResult.Success) + { + Assert.Empty(emitResult.Diagnostics); + } + + var data = stream.ToArray(); + + using var assemblyStream = new MemoryStream(data); + return MetadataReference.CreateFromStream(assemblyStream, MetadataReferenceProperties.Assembly); + } +} + +public abstract class GeneratorTest : GeneratorTester +{ + protected GeneratorTest(ITestOutputHelper testOutputHelper, LogLevel minLevel) : base( + GenerationHelpers.TestProjectName, + new CollectibleTestAssemblyLoadContext(), + testOutputHelper, + minLevel + ) + { + Disposables.Add(( AssemblyLoadContext as IDisposable )!); + } +} + +public class GeneratorTester : LoggerTest +{ + private readonly string _projectName; + protected AssemblyLoadContext AssemblyLoadContext { get; } + private readonly HashSet _metadataReferences = new(ReferenceEqualityComparer.Instance); + private readonly HashSet _generators = new(); + private readonly List _sources = new(); + private GenerationTestResults? _lastResult; + private readonly HashSet _ignoredFilePaths = new(); + private readonly OptionsProvider _optionsProvider = new OptionsProvider(); + + public GeneratorTester( + string projectName, AssemblyLoadContext assemblyLoadContext, ITestOutputHelper testOutputHelper, LogLevel minLevel = LogLevel.Trace + ) : base( + testOutputHelper, + minLevel + ) + { + _projectName = projectName; + AssemblyLoadContext = assemblyLoadContext; + AddReferences( + "mscorlib.dll", + "netstandard.dll", + "System.dll", + "System.Core.dll", +#if NETCOREAPP + "System.Private.CoreLib.dll", +#endif + "System.Runtime.dll" + ); + + AddReferences( + typeof(ActivatorUtilities).Assembly, + typeof(IServiceProvider).Assembly, + typeof(IServiceCollection).Assembly, + typeof(ServiceCollection).Assembly + ); + } + + public GeneratorTester AddCompilationReference(params CSharpCompilation[] additionalCompilations) + { + AddReferences(additionalCompilations.Select(z => z.CreateMetadataReference()).ToArray()); + return this; + } + + public GeneratorTester IgnoreOutputFile(string path) + { + _ignoredFilePaths.Add(path); + return this; + } + + public GeneratorTester WithGenerator(Type type) + { + _generators.Add(type); + return this; + } + + public GeneratorTester WithGenerator() + where T : new() + { + _generators.Add(typeof(T)); + return this; + } + + public GeneratorTester AddReferences(params string[] coreAssemblyNames) + { + // this "core assemblies hack" is from https://stackoverflow.com/a/47196516/4418060 + var coreAssemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!; + + foreach (var name in coreAssemblyNames) + { + _metadataReferences.Add(MetadataReference.CreateFromFile(Path.Combine(coreAssemblyPath, name))); + } + + return this; + } + + public GeneratorTester AddReferences(params MetadataReference[] references) + { + foreach (var metadataReference in references) + { + _metadataReferences.Add(metadataReference); + } + + return this; + } + + public GeneratorTester AddReferences(params Type[] references) + { + foreach (var type in references) + { + _metadataReferences.Add(MetadataReference.CreateFromFile(type.Assembly.Location)); + } + + return this; + } + + public GeneratorTester AddReferences(params Assembly[] references) + { + foreach (var type in references) + { + _metadataReferences.Add(MetadataReference.CreateFromFile(type.Location)); + } + + return this; + } + + public GeneratorTester AddSources(params SourceText[] additionalSources) + { + _sources.AddRange(additionalSources); + return this; + } + + public GeneratorTester AddSources(params string[] additionalSources) + { + _sources.AddRange(additionalSources.Select(s => SourceText.From(s, Encoding.UTF8))); + return this; + } + + public GeneratorTester AddGlobalOption(string key, string value) + { + _optionsProvider.AddGlobalOption(key, value); + return this; + } + + public GeneratorTester AddOption(SyntaxTree tree, string value) + { + _optionsProvider.AddOption(tree, value); + return this; + } + + public GeneratorTester AddOption(AdditionalText key, string value) + { + _optionsProvider.AddOption(key, value); + return this; + } + + public CSharpCompilation Compile() + { + return GenerateAsync().ConfigureAwait(false).GetAwaiter().GetResult().FinalCompilation; + } + + private Assembly? Emit(CSharpCompilation compilation, string? outputName = null) + { + using var stream = new MemoryStream(); + var emitResult = compilation.Emit( + stream, + options: new EmitOptions(outputNameOverride: outputName) + ); + if (!emitResult.Success) + { + return null; + } + + var data = stream.ToArray(); + + using var assemblyStream = new MemoryStream(data); + return AssemblyLoadContext.LoadFromStream(assemblyStream); + } + + public async Task GenerateAsync() + { + Logger.LogInformation("Starting Generation for {SourceCount}", _sources.Count); + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace("--- References --- {Count}", _sources.Count); + foreach (var reference in _metadataReferences) + Logger.LogTrace(" Reference: {Name}", reference.Display); + } + + var project = GenerationHelpers.CreateProject(_projectName, _metadataReferences, _sources.ToArray()); + + var compilation = (CSharpCompilation?)await project.GetCompilationAsync().ConfigureAwait(false); + if (compilation is null) + { + throw new InvalidOperationException("Could not compile the sources"); + } + + var diagnostics = compilation.GetDiagnostics(); + if (Logger.IsEnabled(LogLevel.Trace) && diagnostics is { Length: > 0 }) + { + Logger.LogTrace("--- Input Diagnostics --- {Count}", _sources.Count); + foreach (var d in diagnostics) + Logger.LogTrace(" Reference: {Name}", d.ToString()); + } + + var results = new GenerationTestResults( + compilation, + diagnostics, + compilation.SyntaxTrees, + ImmutableDictionary.Empty, + null!, + ImmutableArray.Empty, + null! + ); + + var builder = ImmutableDictionary.Empty.ToBuilder(); + + var inputCompilation = compilation; + + foreach (var generatorType in _generators) + { + Logger.LogInformation("--- {Generator} ---", generatorType.FullName); + var generator = ( Activator.CreateInstance(generatorType) as IIncrementalGenerator )!; + var driver = CSharpGeneratorDriver.Create(generator).WithUpdatedParseOptions(new CSharpParseOptions()); + + driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out diagnostics); + + if (Logger.IsEnabled(LogLevel.Trace) && diagnostics is { Length: > 0 }) + { + Logger.LogTrace("--- Diagnostics --- {Count}", _sources.Count); + foreach (var d in diagnostics) + Logger.LogTrace(" Reference: {Name}", d.ToString()); + } + + var trees = outputCompilation.SyntaxTrees + .Except(compilation.SyntaxTrees) + .ToImmutableArray(); + if (Logger.IsEnabled(LogLevel.Trace) && trees is { Length: > 0 }) + { + Logger.LogTrace("--- Syntax Trees --- {Count}", _sources.Count); + foreach (var t in trees) + { + Logger.LogTrace(" FilePath: {Name}", t.FilePath); + Logger.LogTrace(" Source:\n{Name}", ( await t.GetTextAsync().ConfigureAwait(false) ).ToString()); + } + } + + inputCompilation = inputCompilation.AddSyntaxTrees(trees); + + builder.Add( + generatorType, + new GenerationTestResult( + ( outputCompilation as CSharpCompilation )!, + diagnostics, + trees.Where(z => !_ignoredFilePaths.Any(x => z.FilePath.Contains(x))).ToImmutableArray(), + Logger + ) + ); + } + + results = results with + { + FinalCompilation = inputCompilation, + FinalDiagnostics = inputCompilation.GetDiagnostics(), + Assembly = Emit(inputCompilation) + }; + + return _lastResult = results with { Results = builder.ToImmutable() }; + } + + private class OptionsProvider : AnalyzerConfigOptionsProvider + { + // ReSharper disable once CollectionNeverQueried.Local + private readonly Dictionary _options = new(); + private readonly Dictionary _globalOptions; + + public OptionsProvider() + { + _globalOptions = new(); + GlobalOptions = new Options(_globalOptions); + } + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) + { + throw new NotImplementedException(); + } + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + { + throw new NotImplementedException(); + } + + public override AnalyzerConfigOptions GlobalOptions { get; } + + public void AddOption(SyntaxTree tree, string value) + { + _options.Add(tree.FilePath, value); + } + + public void AddOption(AdditionalText tree, string value) + { + _options.Add(tree.Path, value); + } + + public void AddGlobalOption(string key, string value) + { + _globalOptions.Add(key, value); + } + } + + private class Options : AnalyzerConfigOptions + { + private readonly IReadOnlyDictionary _options; + + public Options(IReadOnlyDictionary options) + { + _options = options; + } + + public override bool TryGetValue(string key, out string value) + { + return _options.TryGetValue(key, out value!); + } + } +} diff --git a/test/Analyzers.Tests/ViewModelGeneratorTests.cs b/test/Analyzers.Tests/ViewModelGeneratorTests.cs new file mode 100644 index 0000000000..a5c52abde7 --- /dev/null +++ b/test/Analyzers.Tests/ViewModelGeneratorTests.cs @@ -0,0 +1,46 @@ +//using FluentAssertions; +//using JetBrains.Annotations; +//using Microsoft.Extensions.Logging; +//using Analyzers.Tests.Helpers; +//using Rocket.Surgery.Extensions.Testing.Analyzers; +//using Xunit; +//using Xunit.Abstractions; +// +//namespace Analyzers.Tests +//{ +// public class InheritFromGeneratorTests : GeneratorTest +// { +// public InheritFromGeneratorTests([NotNull] ITestOutputHelper testOutputHelper) : base(testOutputHelper, LogLevel.Trace) +// { +// WithGenerator(); +// } +// +// [Fact] +// public async Task Should_Add_Sources_For_XUnit() +// { +// AddReferences(typeof(FactAttribute), typeof(Xunit.Abstractions.ITest)); +// +// var result = await GenerateAsync(); +// result.TryGetResult(out var output).Should().BeTrue(); +// result.EnsureDiagnosticSeverity(); +// output.SyntaxTrees.Should().HaveCount(2); +// output.SyntaxTrees.Should().Contain(z => z.FilePath.EndsWith("Marker_Rocket.Surgery.Extensions.Testing.XUnit.cs")) +// .And.Contain(z => z.FilePath.EndsWith("Rocket.Surgery.Extensions.Testing.XUnit_XUnitExtensions.cs")); +// } +// +// [Fact] +// public async Task Should_Add_Sources_For_XUnit_Once() +// { +// AddReferences(typeof(FactAttribute), typeof(Xunit.Abstractions.ITest)); +// AddCompilationReference(new GeneratorTester("TestA", AssemblyLoadContext, TestOutputHelper).AddReferences(typeof(FactAttribute), typeof(Xunit.Abstractions.ITest)).WithGenerator().Compile()); +// +// var result = await GenerateAsync(); +// result.TryGetResult(out var output).Should().BeTrue(); +// result.EnsureDiagnosticSeverity(); +// output.SyntaxTrees.Should().HaveCount(0); +// } +// +// } +//} + + diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 0a5dc14ebc..18604500ef 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,7 +1,19 @@ - + + [Bogus*]*,[Autofac*]*,[FakeItEasy*]*,[Moq*]*,[FluentValidation*]*,[Ben.Demystifier*]*,[Humanizer*]*,[xunit*]*,[Microsoft.*]*,[XunitXml*]*,[coverlet.*]*,[System.*]*,[*]JetBrains.Annotations* + [Bogus*]*,[Autofac*]*,[FakeItEasy*]*,[Moq*]*,[FluentValidation*]*,[Ben.Demystifier*]*,[Humanizer*]*,[xunit*]*,[Microsoft.*]*,[XunitXml*]*,[coverlet.*]*,[System.*]*,[*]JetBrains.Annotations*,$(Exclude) + + + + false + true + true true diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index 7e160b5442..e2529eda15 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -12,7 +12,6 @@ - diff --git a/test/Testing.Tests/Fixtures/Includes_XUnit_Implictly/FakeXUnit.csproj b/test/Testing.Tests/Fixtures/Includes_XUnit_Implictly/FakeXUnit.csproj new file mode 100644 index 0000000000..e2acc5d106 --- /dev/null +++ b/test/Testing.Tests/Fixtures/Includes_XUnit_Implictly/FakeXUnit.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + false + + + + + + + + diff --git a/test/Testing.Tests/Fixtures/NuGet.config b/test/Testing.Tests/Fixtures/NuGet.config new file mode 100644 index 0000000000..ba215df6f8 --- /dev/null +++ b/test/Testing.Tests/Fixtures/NuGet.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/Testing.Tests/Rocket.Surgery.Extensions.Testing.Tests.csproj b/test/Testing.Tests/Rocket.Surgery.Extensions.Testing.Tests.csproj index 814109224f..94bacab2b1 100644 --- a/test/Testing.Tests/Rocket.Surgery.Extensions.Testing.Tests.csproj +++ b/test/Testing.Tests/Rocket.Surgery.Extensions.Testing.Tests.csproj @@ -4,10 +4,15 @@ + + + + + diff --git a/test/Testing.Tests/XUnitExtensionsTests.cs b/test/Testing.Tests/XUnitExtensionsTests.cs index 7196c4f44c..f456f4c80f 100644 --- a/test/Testing.Tests/XUnitExtensionsTests.cs +++ b/test/Testing.Tests/XUnitExtensionsTests.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using xunit; using Xunit; using Xunit.Abstractions; @@ -21,4 +20,4 @@ public void GetTestTest() test.Should().NotBeNull(); test.DisplayName.Should().EndWith("GetTestTest"); } -} \ No newline at end of file +}