diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fee44c5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +[*] +indent_style = space +charset = utf-8 + +[*.{csproj,yml,yaml,conf}] +indent_size = 2 + +[Directory.Build.props] +indent_size = 2 + +[nuget.config] +indent_size = 2 + +[*.{css,scss,js,ps1}] +indent_size = 4 + +[*.cs] +indent_size = 4 +tab_width = 4 +max_line_length = off \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7a2a039 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Set the default behavior, in case users don't have core.autocrlf set. +* text=auto + +# Files that should always be normalized and converted to native line endings on checkout. +*.js text +*.json text +*.ts text +*.tsx text +*.md text +*.sh text eol=lf +*.conf text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.Dockerfile text eol=lf +LICENSE text eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..febdc5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,391 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +.idea/ +*.sln.iml + +# OS junk +.DS_Store +Desktop.ini +ehthumbs.db +Thumbs.db +$RECYCLE.BIN/ + +# Cake +build/tools/** +!build/tools/packages.config \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..01cfb7b --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# GSoft.Xunit.Extensions + +An opinionated library that provides base unit test and fixture classes based on the `Microsoft.Extensions.*` packages used by modern .NET applications. + + +## Getting started + +There are base classes for **unit** and **integration tests**. Each test method has its own [service collection](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-6.0) configured through a class fixture. + + +### Unit tests + +Create a test class that extends `BaseUnitTest<>` and accepts a class fixture that extends `BaseUnitFixture`. + +In the fixture class, you can: +* Override `ConfigureServices(services)` to add, remove or update dependencies for each test method. +* Override `ConfigureConfiguration(builder)` to makes changes to the generated shared `IConfiguration`. +* Access the generated `IConfiguration` through `this.Configuration`. + +In the test class, you can: +* Access the fixture through `this.Fixture`. +* Access the .NET logger through `this.Logger` - it is connected to the Xunit's `ITestOutputHelper`. +* Access the `IServiceProvider` which has been configured by the fixture through `this.Services`. + +By default, unit tests come with an xunit-connected `ILogger` and an empty `IConfiguration`. + +### Integration tests + +Create a test class that extends `BaseIntegrationTest<>` and accepts a class fixture that extends `BaseIntegrationFixture`. +They both inherit from their respective `BaseUnit*` class. + +* `BaseIntegrationFixture` adds a default `IHostEnvironment` where its environment name is: + * `Local` by default, + * `Test` on CI environments, + * overrideable by defining a `DOTNET_ENVIRONMENT` environment variable, such as in .NET modern applications. + +* `BaseIntegrationFixture` adds `appsettings.json` and `appsettings.{environment}.json` optional configuration providers and also an environment variable configuration provider. + + +### Example + +```csharp +public class MyUnitTests : BaseUnitTest +{ + public MyUnitTests(MyUnitFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture, testOutputHelper) + { + } + + [Fact] + public void Some_Test_Works() + { + var myClass = this.Services.GetRequiredService(); + myClass.DoWork(); + } +} + +public class MyUnitFixture : BaseUnitFixture +{ + protected override IConfigurationBuilder ConfigureConfiguration(IConfigurationBuilder builder) + { + // Executed once per fixture instance + return base.ConfigureConfiguration(builder).AddInMemoryCollection(new Dictionary + { + ["My:Config:Variable"] = "foo", + }); + + // In an integration fixture, you could add concrete configuration providers, such as: + // builder.AddAzureKeyVault(...); + } + + public override IServiceCollection ConfigureServices(IServiceCollection services) + { + // Executed for each test method + return base.ConfigureServices(services) + .AddTransient() + .AddTransient(new MyFakeDependency()); + } +} +``` + + +## License + +Copyright © 2022, GSoft inc. This code is licensed under the Apache License, Version 2.0. You may obtain a copy of this license at https://github.com/gsoft-inc/gsoft-license/blob/master/LICENSE. diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..a707719 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,2 @@ +dotnet run --project build/Build.csproj -- $args +exit $LASTEXITCODE; \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..dfd6b85 --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +dotnet run --project ./build/Build.csproj -- "$@" diff --git a/build/Build.csproj b/build/Build.csproj new file mode 100644 index 0000000..c66d32e --- /dev/null +++ b/build/Build.csproj @@ -0,0 +1,14 @@ + + + Exe + net6.0 + $(MSBuildProjectDirectory) + True + enable + 10 + + + + + + \ No newline at end of file diff --git a/build/Program.cs b/build/Program.cs new file mode 100644 index 0000000..f95711b --- /dev/null +++ b/build/Program.cs @@ -0,0 +1,160 @@ +using System; +using System.IO; +using Cake.Common; +using Cake.Common.IO; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Build; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Common.Tools.DotNet.NuGet.Push; +using Cake.Common.Tools.DotNet.Pack; +using Cake.Common.Tools.DotNet.Restore; +using Cake.Common.Tools.GitVersion; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Frosting; + +return new CakeHost() + .InstallTool(new Uri("dotnet:?package=GitVersion.Tool&version=5.10.1")) + .UseContext() + .Run(args); + +public static class Constants +{ + public const string Release = "Release"; + public const string ProjectName = "GSoft.Xunit.Extensions"; + + public static readonly string SourceDirectoryPath = Path.Combine("..", "src"); + public static readonly string OutputDirectoryPath = Path.Combine("..", ".output"); + public static readonly string SolutionPath = Path.Combine(SourceDirectoryPath, ProjectName + ".sln"); + public static readonly string MainProjectPath = Path.Combine(SourceDirectoryPath, ProjectName, ProjectName + ".csproj"); +} + +public class BuildContext : FrostingContext +{ + public BuildContext(ICakeContext context) : base(context) + { + this.NugetApiKey = context.Argument("nuget-api-key", string.Empty); + this.NugetSource = context.Argument("nuget-source", string.Empty); + } + + public DotNetMSBuildSettings MSBuildSettings { get; } = new DotNetMSBuildSettings(); + + public string NugetApiKey { get; } + + public string NugetSource { get; } + + public void AddMSBuildSetting(string name, string value, bool log = false) + { + if (log) + { + this.Log.Information(name + ": " + value); + } + + if (!string.IsNullOrWhiteSpace(value)) + { + this.MSBuildSettings.Properties[name] = new[] { value }; + } + } +} + +[TaskName("Clean")] +public sealed class CleanTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var objGlobPath = Path.Combine(Constants.SourceDirectoryPath, "*", "obj"); + var binGlobPath = Path.Combine(Constants.SourceDirectoryPath, "*", "bin"); + + context.CleanDirectories(Constants.OutputDirectoryPath); + context.CleanDirectories(objGlobPath); + context.CleanDirectories(binGlobPath); + } +} + +[TaskName("GitVersion")] +public sealed class GitVersionTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var gitVersion = context.GitVersion(); + + context.AddMSBuildSetting("Version", gitVersion.NuGetVersion, log: true); + context.AddMSBuildSetting("VersionPrefix", gitVersion.MajorMinorPatch, log: true); + context.AddMSBuildSetting("VersionSuffix", gitVersion.PreReleaseTag, log: true); + context.AddMSBuildSetting("PackageVersion", gitVersion.FullSemVer, log: true); + context.AddMSBuildSetting("InformationalVersion", gitVersion.InformationalVersion, log: true); + context.AddMSBuildSetting("AssemblyVersion", gitVersion.AssemblySemVer, log: true); + context.AddMSBuildSetting("FileVersion", gitVersion.AssemblySemFileVer, log: true); + context.AddMSBuildSetting("RepositoryBranch", gitVersion.BranchName, log: true); + context.AddMSBuildSetting("RepositoryCommit", gitVersion.Sha, log: true); + } +} + +[TaskName("Restore")] +[IsDependentOn(typeof(CleanTask))] +[IsDependentOn(typeof(GitVersionTask))] +public sealed class RestoreTask : FrostingTask +{ + public override void Run(BuildContext context) => context.DotNetRestore(Constants.SolutionPath, new DotNetRestoreSettings + { + MSBuildSettings = context.MSBuildSettings, + }); +} + +[TaskName("Build")] +[IsDependentOn(typeof(RestoreTask))] +public sealed class BuildTask : FrostingTask +{ + public override void Run(BuildContext context) + { + context.AddMSBuildSetting("Deterministic", "true"); + context.AddMSBuildSetting("ContinuousIntegrationBuild", "true"); + + context.DotNetBuild(Constants.SolutionPath, new DotNetBuildSettings + { + Configuration = Constants.Release, + MSBuildSettings = context.MSBuildSettings, + NoRestore = true, + NoLogo = true, + }); + } +} + +[TaskName("Pack")] +[IsDependentOn(typeof(BuildTask))] +public sealed class PackTask : FrostingTask +{ + public override void Run(BuildContext context) => context.DotNetPack(Constants.MainProjectPath, new DotNetPackSettings + { + Configuration = Constants.Release, + MSBuildSettings = context.MSBuildSettings, + OutputDirectory = Constants.OutputDirectoryPath, + NoBuild = true, + NoRestore = true, + NoLogo = true, + }); +} + +[TaskName("Push")] +[IsDependentOn(typeof(PackTask))] +public sealed class PushTask : FrostingTask +{ + public override void Run(BuildContext context) + { + foreach (var packageFilePath in context.GetFiles(Path.Combine(Constants.OutputDirectoryPath, "*.nupkg"))) + { + context.DotNetNuGetPush(packageFilePath, new DotNetNuGetPushSettings + { + ApiKey = context.NugetApiKey, + Source = context.NugetSource, + IgnoreSymbols = false + }); + } + } +} + +[TaskName("Default")] +[IsDependentOn(typeof(PackTask))] +public sealed class DefaultTask : FrostingTask +{ +} \ No newline at end of file diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..a00321a --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,530 @@ +[*.json] +indent_size = 2 + +[*.{cs,vb}] +dotnet_diagnostic.CA1000.severity = warning +dotnet_diagnostic.CA1001.severity = warning +dotnet_diagnostic.CA1002.severity = warning +dotnet_diagnostic.CA1003.severity = warning +dotnet_diagnostic.CA1004.severity = warning +dotnet_diagnostic.CA1005.severity = none +dotnet_diagnostic.CA1006.severity = warning +dotnet_diagnostic.CA1007.severity = warning +dotnet_diagnostic.CA1008.severity = warning +dotnet_diagnostic.CA1009.severity = warning +dotnet_diagnostic.CA1010.severity = warning +dotnet_diagnostic.CA1011.severity = warning +dotnet_diagnostic.CA1012.severity = warning +dotnet_diagnostic.CA1013.severity = warning +dotnet_diagnostic.CA1014.severity = none +dotnet_diagnostic.CA1016.severity = warning +dotnet_diagnostic.CA1017.severity = warning +dotnet_diagnostic.CA1018.severity = warning +dotnet_diagnostic.CA1019.severity = warning +dotnet_diagnostic.CA1020.severity = none +dotnet_diagnostic.CA1021.severity = none +dotnet_diagnostic.CA1023.severity = warning +dotnet_diagnostic.CA1024.severity = warning +dotnet_diagnostic.CA1025.severity = warning +dotnet_diagnostic.CA1026.severity = warning +dotnet_diagnostic.CA1027.severity = none +dotnet_diagnostic.CA1028.severity = warning +dotnet_diagnostic.CA1030.severity = warning +dotnet_diagnostic.CA1031.severity = none +dotnet_diagnostic.CA1032.severity = none +dotnet_diagnostic.CA1033.severity = warning +dotnet_diagnostic.CA1034.severity = none +dotnet_diagnostic.CA1035.severity = warning +dotnet_diagnostic.CA1036.severity = warning +dotnet_diagnostic.CA1038.severity = warning +dotnet_diagnostic.CA1039.severity = warning +dotnet_diagnostic.CA1040.severity = none +dotnet_diagnostic.CA1041.severity = warning +dotnet_diagnostic.CA1043.severity = warning +dotnet_diagnostic.CA1044.severity = warning +dotnet_diagnostic.CA1045.severity = warning +dotnet_diagnostic.CA1046.severity = warning +dotnet_diagnostic.CA1047.severity = warning +dotnet_diagnostic.CA1048.severity = warning +dotnet_diagnostic.CA1049.severity = warning +dotnet_diagnostic.CA1050.severity = warning +dotnet_diagnostic.CA1051.severity = warning +dotnet_diagnostic.CA1052.severity = warning +dotnet_diagnostic.CA1053.severity = warning +dotnet_diagnostic.CA1054.severity = none +dotnet_diagnostic.CA1055.severity = none +dotnet_diagnostic.CA1056.severity = none +dotnet_diagnostic.CA1057.severity = warning +dotnet_diagnostic.CA1058.severity = warning +dotnet_diagnostic.CA1059.severity = warning +dotnet_diagnostic.CA1060.severity = none +dotnet_diagnostic.CA1061.severity = none +dotnet_diagnostic.CA1062.severity = none +dotnet_diagnostic.CA1063.severity = warning +dotnet_diagnostic.CA1064.severity = warning +dotnet_diagnostic.CA1065.severity = none +dotnet_diagnostic.CA1300.severity = warning +dotnet_diagnostic.CA1301.severity = warning +dotnet_diagnostic.CA1302.severity = warning +dotnet_diagnostic.CA1303.severity = none +dotnet_diagnostic.CA1304.severity = none +dotnet_diagnostic.CA1305.severity = none +dotnet_diagnostic.CA1306.severity = warning +dotnet_diagnostic.CA1307.severity = none +dotnet_diagnostic.CA1308.severity = none +dotnet_diagnostic.CA1309.severity = none +dotnet_diagnostic.CA1310.severity = none +dotnet_diagnostic.CA1400.severity = warning +dotnet_diagnostic.CA1401.severity = warning +dotnet_diagnostic.CA1402.severity = warning +dotnet_diagnostic.CA1403.severity = warning +dotnet_diagnostic.CA1404.severity = warning +dotnet_diagnostic.CA1405.severity = warning +dotnet_diagnostic.CA1406.severity = warning +dotnet_diagnostic.CA1407.severity = warning +dotnet_diagnostic.CA1408.severity = warning +dotnet_diagnostic.CA1409.severity = warning +dotnet_diagnostic.CA1410.severity = warning +dotnet_diagnostic.CA1411.severity = warning +dotnet_diagnostic.CA1412.severity = warning +dotnet_diagnostic.CA1413.severity = warning +dotnet_diagnostic.CA1414.severity = warning +dotnet_diagnostic.CA1415.severity = warning +dotnet_diagnostic.CA1416.severity = warning +dotnet_diagnostic.CA1417.severity = warning +dotnet_diagnostic.CA1500.severity = none +dotnet_diagnostic.CA1501.severity = none +dotnet_diagnostic.CA1502.severity = none +dotnet_diagnostic.CA1504.severity = none +dotnet_diagnostic.CA1505.severity = none +dotnet_diagnostic.CA1506.severity = none +dotnet_diagnostic.CA1600.severity = warning +dotnet_diagnostic.CA1601.severity = warning +dotnet_diagnostic.CA1700.severity = warning +dotnet_diagnostic.CA1701.severity = warning +dotnet_diagnostic.CA1702.severity = warning +dotnet_diagnostic.CA1703.severity = warning +dotnet_diagnostic.CA1704.severity = none +dotnet_diagnostic.CA1707.severity = none +dotnet_diagnostic.CA1708.severity = warning +dotnet_diagnostic.CA1709.severity = none +dotnet_diagnostic.CA1710.severity = none +dotnet_diagnostic.CA1711.severity = none +dotnet_diagnostic.CA1712.severity = warning +dotnet_diagnostic.CA1713.severity = warning +dotnet_diagnostic.CA1714.severity = warning +dotnet_diagnostic.CA1715.severity = warning +dotnet_diagnostic.CA1716.severity = none +dotnet_diagnostic.CA1717.severity = warning +dotnet_diagnostic.CA1719.severity = warning +dotnet_diagnostic.CA1720.severity = none +dotnet_diagnostic.CA1721.severity = none +dotnet_diagnostic.CA1722.severity = warning +dotnet_diagnostic.CA1724.severity = none +dotnet_diagnostic.CA1725.severity = warning +dotnet_diagnostic.CA1726.severity = warning +dotnet_diagnostic.CA1800.severity = warning +dotnet_diagnostic.CA1801.severity = warning +dotnet_diagnostic.CA1802.severity = warning +dotnet_diagnostic.CA1804.severity = warning +dotnet_diagnostic.CA1805.severity = warning +dotnet_diagnostic.CA1806.severity = warning +dotnet_diagnostic.CA1809.severity = warning +dotnet_diagnostic.CA1810.severity = warning +dotnet_diagnostic.CA1811.severity = warning +dotnet_diagnostic.CA1812.severity = none +dotnet_diagnostic.CA1813.severity = warning +dotnet_diagnostic.CA1814.severity = warning +dotnet_diagnostic.CA1815.severity = warning +dotnet_diagnostic.CA1816.severity = warning +dotnet_diagnostic.CA1819.severity = none +dotnet_diagnostic.CA1820.severity = warning +dotnet_diagnostic.CA1821.severity = warning +dotnet_diagnostic.CA1822.severity = warning +dotnet_diagnostic.CA1823.severity = warning +dotnet_diagnostic.CA1824.severity = none +dotnet_diagnostic.CA1825.severity = none +dotnet_diagnostic.CA1826.severity = warning +dotnet_diagnostic.CA1827.severity = warning +dotnet_diagnostic.CA1828.severity = warning +dotnet_diagnostic.CA1829.severity = warning +dotnet_diagnostic.CA1830.severity = warning +dotnet_diagnostic.CA1831.severity = warning +dotnet_diagnostic.CA1832.severity = warning +dotnet_diagnostic.CA1833.severity = warning +dotnet_diagnostic.CA1834.severity = warning +dotnet_diagnostic.CA1835.severity = warning +dotnet_diagnostic.CA1836.severity = warning +dotnet_diagnostic.CA1837.severity = warning +dotnet_diagnostic.CA1838.severity = none +dotnet_diagnostic.CA1900.severity = warning +dotnet_diagnostic.CA1901.severity = warning +dotnet_diagnostic.CA1903.severity = warning +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CA2001.severity = none +dotnet_diagnostic.CA2002.severity = warning +dotnet_diagnostic.CA2003.severity = none +dotnet_diagnostic.CA2004.severity = none +dotnet_diagnostic.CA2006.severity = none +dotnet_diagnostic.CA2007.severity = error +dotnet_diagnostic.CA2100.severity = warning +dotnet_diagnostic.CA2101.severity = none +dotnet_diagnostic.CA2102.severity = warning +dotnet_diagnostic.CA2103.severity = warning +dotnet_diagnostic.CA2104.severity = warning +dotnet_diagnostic.CA2105.severity = warning +dotnet_diagnostic.CA2106.severity = warning +dotnet_diagnostic.CA2107.severity = warning +dotnet_diagnostic.CA2108.severity = warning +dotnet_diagnostic.CA2109.severity = warning +dotnet_diagnostic.CA2111.severity = warning +dotnet_diagnostic.CA2112.severity = warning +dotnet_diagnostic.CA2114.severity = warning +dotnet_diagnostic.CA2115.severity = warning +dotnet_diagnostic.CA2116.severity = warning +dotnet_diagnostic.CA2117.severity = warning +dotnet_diagnostic.CA2118.severity = warning +dotnet_diagnostic.CA2119.severity = warning +dotnet_diagnostic.CA2120.severity = warning +dotnet_diagnostic.CA2121.severity = warning +dotnet_diagnostic.CA2122.severity = warning +dotnet_diagnostic.CA2123.severity = warning +dotnet_diagnostic.CA2124.severity = warning +dotnet_diagnostic.CA2126.severity = warning +dotnet_diagnostic.CA2130.severity = warning +dotnet_diagnostic.CA2131.severity = warning +dotnet_diagnostic.CA2132.severity = warning +dotnet_diagnostic.CA2133.severity = warning +dotnet_diagnostic.CA2134.severity = warning +dotnet_diagnostic.CA2135.severity = warning +dotnet_diagnostic.CA2136.severity = warning +dotnet_diagnostic.CA2137.severity = warning +dotnet_diagnostic.CA2138.severity = warning +dotnet_diagnostic.CA2139.severity = warning +dotnet_diagnostic.CA2140.severity = warning +dotnet_diagnostic.CA2141.severity = warning +dotnet_diagnostic.CA2142.severity = warning +dotnet_diagnostic.CA2143.severity = warning +dotnet_diagnostic.CA2144.severity = warning +dotnet_diagnostic.CA2145.severity = warning +dotnet_diagnostic.CA2146.severity = warning +dotnet_diagnostic.CA2147.severity = warning +dotnet_diagnostic.CA2149.severity = warning +dotnet_diagnostic.CA2151.severity = warning +dotnet_diagnostic.CA2153.severity = warning +dotnet_diagnostic.CA2200.severity = warning +dotnet_diagnostic.CA2201.severity = none +dotnet_diagnostic.CA2202.severity = none +dotnet_diagnostic.CA2204.severity = none +dotnet_diagnostic.CA2205.severity = warning +dotnet_diagnostic.CA2207.severity = warning +dotnet_diagnostic.CA2208.severity = warning +dotnet_diagnostic.CA2210.severity = warning +dotnet_diagnostic.CA2211.severity = warning +dotnet_diagnostic.CA2212.severity = warning +dotnet_diagnostic.CA2213.severity = none +dotnet_diagnostic.CA2214.severity = warning +dotnet_diagnostic.CA2215.severity = warning +dotnet_diagnostic.CA2216.severity = warning +dotnet_diagnostic.CA2217.severity = warning +dotnet_diagnostic.CA2218.severity = warning +dotnet_diagnostic.CA2219.severity = warning +dotnet_diagnostic.CA2220.severity = warning +dotnet_diagnostic.CA2221.severity = warning +dotnet_diagnostic.CA2222.severity = warning +dotnet_diagnostic.CA2223.severity = warning +dotnet_diagnostic.CA2224.severity = warning +dotnet_diagnostic.CA2225.severity = warning +dotnet_diagnostic.CA2226.severity = warning +dotnet_diagnostic.CA2227.severity = none +dotnet_diagnostic.CA2228.severity = warning +dotnet_diagnostic.CA2229.severity = none +dotnet_diagnostic.CA2230.severity = warning +dotnet_diagnostic.CA2231.severity = warning +dotnet_diagnostic.CA2232.severity = warning +dotnet_diagnostic.CA2233.severity = warning +dotnet_diagnostic.CA2234.severity = none +dotnet_diagnostic.CA2235.severity = none +dotnet_diagnostic.CA2236.severity = warning +dotnet_diagnostic.CA2237.severity = warning +dotnet_diagnostic.CA2238.severity = warning +dotnet_diagnostic.CA2239.severity = warning +dotnet_diagnostic.CA2240.severity = warning +dotnet_diagnostic.CA2241.severity = warning +dotnet_diagnostic.CA2242.severity = warning +dotnet_diagnostic.CA2243.severity = warning +dotnet_diagnostic.CA2244.severity = warning +dotnet_diagnostic.CA2245.severity = warning +dotnet_diagnostic.CA2246.severity = warning +dotnet_diagnostic.CA2247.severity = warning +dotnet_diagnostic.CA2248.severity = warning +dotnet_diagnostic.CA2249.severity = warning +dotnet_diagnostic.CA2300.severity = warning +dotnet_diagnostic.CA2301.severity = warning +dotnet_diagnostic.CA2302.severity = warning +dotnet_diagnostic.CA2305.severity = warning +dotnet_diagnostic.CA2310.severity = warning +dotnet_diagnostic.CA2311.severity = warning +dotnet_diagnostic.CA2312.severity = warning +dotnet_diagnostic.CA2315.severity = warning +dotnet_diagnostic.CA2321.severity = warning +dotnet_diagnostic.CA2322.severity = warning +dotnet_diagnostic.CA2326.severity = warning +dotnet_diagnostic.CA2327.severity = warning +dotnet_diagnostic.CA2328.severity = warning +dotnet_diagnostic.CA2329.severity = warning +dotnet_diagnostic.CA2330.severity = warning +dotnet_diagnostic.CA2350.severity = warning +dotnet_diagnostic.CA2351.severity = warning +dotnet_diagnostic.CA2352.severity = warning +dotnet_diagnostic.CA2353.severity = warning +dotnet_diagnostic.CA2354.severity = warning +dotnet_diagnostic.CA2355.severity = warning +dotnet_diagnostic.CA2356.severity = warning +dotnet_diagnostic.CA2361.severity = warning +dotnet_diagnostic.CA2362.severity = warning +dotnet_diagnostic.CA5122.severity = warning +dotnet_diagnostic.CA5350.severity = none +dotnet_diagnostic.CA5351.severity = warning +dotnet_diagnostic.CA5358.severity = warning +dotnet_diagnostic.CA5359.severity = warning +dotnet_diagnostic.CA5360.severity = warning +dotnet_diagnostic.CA5361.severity = warning +dotnet_diagnostic.CA5362.severity = none +dotnet_diagnostic.CA5363.severity = warning +dotnet_diagnostic.CA5364.severity = warning +dotnet_diagnostic.CA5365.severity = warning +dotnet_diagnostic.CA5366.severity = warning +dotnet_diagnostic.CA5367.severity = warning +dotnet_diagnostic.CA5368.severity = warning +dotnet_diagnostic.CA5369.severity = warning +dotnet_diagnostic.CA5370.severity = warning +dotnet_diagnostic.CA5371.severity = warning +dotnet_diagnostic.CA5372.severity = warning +dotnet_diagnostic.CA5373.severity = warning +dotnet_diagnostic.CA5374.severity = warning +dotnet_diagnostic.CA5375.severity = warning +dotnet_diagnostic.CA5376.severity = warning +dotnet_diagnostic.CA5377.severity = warning +dotnet_diagnostic.CA5378.severity = warning +dotnet_diagnostic.CA5379.severity = warning +dotnet_diagnostic.CA5380.severity = warning +dotnet_diagnostic.CA5381.severity = warning +dotnet_diagnostic.CA5382.severity = warning +dotnet_diagnostic.CA5383.severity = warning +dotnet_diagnostic.CA5384.severity = warning +dotnet_diagnostic.CA5385.severity = warning +dotnet_diagnostic.CA5386.severity = warning +dotnet_diagnostic.CA5387.severity = warning +dotnet_diagnostic.CA5388.severity = warning +dotnet_diagnostic.CA5389.severity = warning +dotnet_diagnostic.CA5390.severity = warning +dotnet_diagnostic.CA5391.severity = warning +dotnet_diagnostic.CA5392.severity = warning +dotnet_diagnostic.CA5393.severity = warning +dotnet_diagnostic.CA5394.severity = warning +dotnet_diagnostic.CA5395.severity = warning +dotnet_diagnostic.CA5396.severity = warning +dotnet_diagnostic.CA5397.severity = warning +dotnet_diagnostic.CA5398.severity = warning +dotnet_diagnostic.CA5399.severity = warning +dotnet_diagnostic.CA5400.severity = warning +dotnet_diagnostic.CA5401.severity = warning +dotnet_diagnostic.CA5402.severity = warning +dotnet_diagnostic.CA5403.severity = warning +dotnet_diagnostic.CS0078.severity = none +dotnet_diagnostic.CS0105.severity = none +dotnet_diagnostic.CS0108.severity = none +dotnet_diagnostic.CS0109.severity = none +dotnet_diagnostic.CS0114.severity = none +dotnet_diagnostic.CS0162.severity = none +dotnet_diagnostic.CS0164.severity = none +dotnet_diagnostic.CS0168.severity = none +dotnet_diagnostic.CS0183.severity = none +dotnet_diagnostic.CS0184.severity = none +dotnet_diagnostic.CS0197.severity = none +dotnet_diagnostic.CS0219.severity = none +dotnet_diagnostic.CS0251.severity = none +dotnet_diagnostic.CS0252.severity = none +dotnet_diagnostic.CS0253.severity = none +dotnet_diagnostic.CS0278.severity = none +dotnet_diagnostic.CS0279.severity = none +dotnet_diagnostic.CS0280.severity = none +dotnet_diagnostic.CS0282.severity = none +dotnet_diagnostic.CS0419.severity = none +dotnet_diagnostic.CS0420.severity = none +dotnet_diagnostic.CS0435.severity = none +dotnet_diagnostic.CS0436.severity = none +dotnet_diagnostic.CS0437.severity = none +dotnet_diagnostic.CS0440.severity = none +dotnet_diagnostic.CS0458.severity = none +dotnet_diagnostic.CS0464.severity = none +dotnet_diagnostic.CS0465.severity = none +dotnet_diagnostic.CS0469.severity = none +dotnet_diagnostic.CS0472.severity = none +dotnet_diagnostic.CS0473.severity = none +dotnet_diagnostic.CS0612.severity = none +dotnet_diagnostic.CS0618.severity = none +dotnet_diagnostic.CS0626.severity = none +dotnet_diagnostic.CS0628.severity = none +dotnet_diagnostic.CS0642.severity = none +dotnet_diagnostic.CS0652.severity = none +dotnet_diagnostic.CS0657.severity = none +dotnet_diagnostic.CS0658.severity = none +dotnet_diagnostic.CS0659.severity = none +dotnet_diagnostic.CS0660.severity = none +dotnet_diagnostic.CS0661.severity = none +dotnet_diagnostic.CS0665.severity = none +dotnet_diagnostic.CS0672.severity = none +dotnet_diagnostic.CS0675.severity = none +dotnet_diagnostic.CS0684.severity = none +dotnet_diagnostic.CS0693.severity = none +dotnet_diagnostic.CS0728.severity = none +dotnet_diagnostic.CS0809.severity = none +dotnet_diagnostic.CS0811.severity = none +dotnet_diagnostic.CS0824.severity = none +dotnet_diagnostic.CS1030.severity = none +dotnet_diagnostic.CS1058.severity = none +dotnet_diagnostic.CS1062.severity = none +dotnet_diagnostic.CS1064.severity = none +dotnet_diagnostic.CS1066.severity = none +dotnet_diagnostic.CS1072.severity = none +dotnet_diagnostic.CS1522.severity = none +dotnet_diagnostic.CS1570.severity = none +dotnet_diagnostic.CS1571.severity = none +dotnet_diagnostic.CS1572.severity = none +dotnet_diagnostic.CS1573.severity = none +dotnet_diagnostic.CS1574.severity = none +dotnet_diagnostic.CS1580.severity = none +dotnet_diagnostic.CS1581.severity = none +dotnet_diagnostic.CS1584.severity = none +dotnet_diagnostic.CS1587.severity = none +dotnet_diagnostic.CS1589.severity = none +dotnet_diagnostic.CS1590.severity = none +dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.CS1592.severity = none +dotnet_diagnostic.CS1616.severity = none +dotnet_diagnostic.CS1633.severity = none +dotnet_diagnostic.CS1634.severity = none +dotnet_diagnostic.CS1635.severity = none +dotnet_diagnostic.CS1645.severity = none +dotnet_diagnostic.CS1658.severity = none +dotnet_diagnostic.CS1668.severity = none +dotnet_diagnostic.CS1685.severity = none +dotnet_diagnostic.CS1687.severity = none +dotnet_diagnostic.CS1690.severity = none +dotnet_diagnostic.CS1692.severity = none +dotnet_diagnostic.CS1695.severity = none +dotnet_diagnostic.CS1696.severity = none +dotnet_diagnostic.CS1697.severity = none +dotnet_diagnostic.CS1700.severity = none +dotnet_diagnostic.CS1701.severity = none +dotnet_diagnostic.CS1702.severity = none +dotnet_diagnostic.CS1710.severity = none +dotnet_diagnostic.CS1711.severity = none +dotnet_diagnostic.CS1712.severity = none +dotnet_diagnostic.CS1717.severity = none +dotnet_diagnostic.CS1718.severity = none +dotnet_diagnostic.CS1720.severity = none +dotnet_diagnostic.CS1723.severity = none +dotnet_diagnostic.CS1734.severity = none +dotnet_diagnostic.CS1735.severity = none +dotnet_diagnostic.CS1762.severity = none +dotnet_diagnostic.CS1927.severity = none +dotnet_diagnostic.CS1956.severity = none +dotnet_diagnostic.CS1957.severity = none +dotnet_diagnostic.CS1974.severity = none +dotnet_diagnostic.CS1981.severity = none +dotnet_diagnostic.CS1998.severity = none +dotnet_diagnostic.CS2002.severity = none +dotnet_diagnostic.CS2008.severity = none +dotnet_diagnostic.CS2023.severity = none +dotnet_diagnostic.CS2029.severity = none +dotnet_diagnostic.CS2038.severity = none +dotnet_diagnostic.CS3000.severity = none +dotnet_diagnostic.CS3001.severity = warning +dotnet_diagnostic.CS3002.severity = warning +dotnet_diagnostic.CS3003.severity = warning +dotnet_diagnostic.CS3005.severity = warning +dotnet_diagnostic.CS3006.severity = warning +dotnet_diagnostic.CS3007.severity = warning +dotnet_diagnostic.CS3008.severity = warning +dotnet_diagnostic.CS3009.severity = warning +dotnet_diagnostic.CS3010.severity = warning +dotnet_diagnostic.CS3011.severity = warning +dotnet_diagnostic.CS3012.severity = warning +dotnet_diagnostic.CS3013.severity = none +dotnet_diagnostic.CS3014.severity = none +dotnet_diagnostic.CS3015.severity = none +dotnet_diagnostic.CS3016.severity = none +dotnet_diagnostic.CS3017.severity = none +dotnet_diagnostic.CS3018.severity = none +dotnet_diagnostic.CS3019.severity = none +dotnet_diagnostic.CS3021.severity = none +dotnet_diagnostic.CS3022.severity = none +dotnet_diagnostic.CS3023.severity = none +dotnet_diagnostic.CS3024.severity = none +dotnet_diagnostic.CS3026.severity = none +dotnet_diagnostic.CS3027.severity = none +dotnet_diagnostic.CS3061.severity = warning +dotnet_diagnostic.CS3075.severity = warning +dotnet_diagnostic.CS3076.severity = warning +dotnet_diagnostic.CS3077.severity = warning +dotnet_diagnostic.CS3147.severity = warning +dotnet_diagnostic.IDE0003.severity = none +dotnet_diagnostic.IDE0005.severity = suggestion +dotnet_diagnostic.SA0001.severity = none +dotnet_diagnostic.SA1028.severity = none +dotnet_diagnostic.SA1100.severity = none +dotnet_diagnostic.SA1118.severity = none +dotnet_diagnostic.SA1124.severity = none +dotnet_diagnostic.SA1127.severity = none +dotnet_diagnostic.SA1128.severity = none +dotnet_diagnostic.SA1129.severity = none +dotnet_diagnostic.SA1133.severity = none +dotnet_diagnostic.SA1200.severity = none +dotnet_diagnostic.SA1202.severity = none +dotnet_diagnostic.SA1204.severity = none +dotnet_diagnostic.SA1207.severity = none +dotnet_diagnostic.SA1210.severity = none +dotnet_diagnostic.SA1306.severity = none +dotnet_diagnostic.SA1309.severity = none +dotnet_diagnostic.SA1310.severity = none +dotnet_diagnostic.SA1401.severity = none +dotnet_diagnostic.SA1402.severity = none +dotnet_diagnostic.SA1407.severity = none +dotnet_diagnostic.SA1600.severity = none +dotnet_diagnostic.SA1601.severity = none +dotnet_diagnostic.SA1602.severity = none +dotnet_diagnostic.SA1604.severity = none +dotnet_diagnostic.SA1605.severity = none +dotnet_diagnostic.SA1606.severity = none +dotnet_diagnostic.SA1607.severity = none +dotnet_diagnostic.SA1608.severity = none +dotnet_diagnostic.SA1609.severity = silent +dotnet_diagnostic.SA1611.severity = none +dotnet_diagnostic.SA1613.severity = none +dotnet_diagnostic.SA1614.severity = none +dotnet_diagnostic.SA1615.severity = none +dotnet_diagnostic.SA1616.severity = none +dotnet_diagnostic.SA1618.severity = none +dotnet_diagnostic.SA1619.severity = none +dotnet_diagnostic.SA1620.severity = none +dotnet_diagnostic.SA1621.severity = none +dotnet_diagnostic.SA1622.severity = none +dotnet_diagnostic.SA1623.severity = none +dotnet_diagnostic.SA1626.severity = none +dotnet_diagnostic.SA1627.severity = none +dotnet_diagnostic.SA1629.severity = none +dotnet_diagnostic.SA1633.severity = none +dotnet_diagnostic.SA1634.severity = none +dotnet_diagnostic.SA1635.severity = none +dotnet_diagnostic.SA1636.severity = none +dotnet_diagnostic.SA1637.severity = none +dotnet_diagnostic.SA1638.severity = none +dotnet_diagnostic.SA1640.severity = none +dotnet_diagnostic.SA1641.severity = none +dotnet_diagnostic.SA1642.severity = none +dotnet_diagnostic.SA1643.severity = none +dotnet_diagnostic.SA1644.severity = none +dotnet_diagnostic.SA1652.severity = none \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..3de582f --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,23 @@ + + + Copyright © Groupe GSoft inc. $([System.DateTime]::UtcNow.ToString(yyyy)) + Anthony Simmon + Groupe GSoft inc. + Apache-2.0 + 10 + enable + disable + An opinionated library that provides base unit test and fixture classes based on the Microsoft.Extensions.* packages used by modern .NET applications. + + + + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions.sln b/src/GSoft.Xunit.Extensions.sln new file mode 100644 index 0000000..f589120 --- /dev/null +++ b/src/GSoft.Xunit.Extensions.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSoft.Xunit.Extensions", "GSoft.Xunit.Extensions\GSoft.Xunit.Extensions.csproj", "{C71B3436-583A-4F7F-8045-91D3926C993B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "files", "files", "{044A4D97-4907-47A6-AB82-565F838FF3A7}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + .editorconfig = .editorconfig + ..\README.md = ..\README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C71B3436-583A-4F7F-8045-91D3926C993B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C71B3436-583A-4F7F-8045-91D3926C993B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C71B3436-583A-4F7F-8045-91D3926C993B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C71B3436-583A-4F7F-8045-91D3926C993B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/GSoft.Xunit.Extensions/BaseIntegrationFixture.cs b/src/GSoft.Xunit.Extensions/BaseIntegrationFixture.cs new file mode 100644 index 0000000..a760be5 --- /dev/null +++ b/src/GSoft.Xunit.Extensions/BaseIntegrationFixture.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; + +namespace GSoft.Xunit.Extensions; + +public abstract class BaseIntegrationFixture : BaseUnitFixture +{ + protected BaseIntegrationFixture() + { + this.Environment = new TestHostEnvironment(); + } + + protected virtual IHostEnvironment Environment { get; } + + protected override IConfigurationBuilder ConfigureConfiguration(IConfigurationBuilder builder) + { + builder.SetBasePath(AppContext.BaseDirectory); + builder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false); + builder.AddJsonFile("appsettings." + this.Environment.EnvironmentName + ".json", optional: true, reloadOnChange: false); + builder.AddEnvironmentVariables(); + + return builder; + } + + public override IServiceCollection ConfigureServices(IServiceCollection services) + { + base.ConfigureServices(services); + services.TryAddSingleton(this.Environment); + + return services; + } + + private sealed class TestHostEnvironment : IHostEnvironment + { + private static readonly bool IsCI = IsContinuousIntegrationServer(); + + public TestHostEnvironment() + { + this.EnvironmentName = DetermineEnvironmentName(); + this.ApplicationName = "Tests"; + this.ContentRootPath = AppContext.BaseDirectory; + this.ContentRootFileProvider = new PhysicalFileProvider(AppContext.BaseDirectory); + } + + public string EnvironmentName { get; set; } + + public string ApplicationName { get; set; } + + public string ContentRootPath { get; set; } + + public IFileProvider ContentRootFileProvider { get; set; } + + private static string DetermineEnvironmentName() + { + // Similar to https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-6.0#environments + if (System.Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") is { Length: > 0 } environmentName) + { + return environmentName; + } + + return IsCI ? "Test" : "Local"; + } + + private static bool IsContinuousIntegrationServer() + { + // The following environment variables are defined by continuous integration servers + // Inspired from https://github.com/watson/ci-info/blob/v3.3.1/vendors.json + var envNames = new[] + { + "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", // Azure Pipelines + "GITHUB_ACTIONS", // GitHub Actions + "TEAMCITY", // TeamCity + }; + + return envNames.Any(x => !string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(x))); + } + } +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/BaseIntegrationTest.cs b/src/GSoft.Xunit.Extensions/BaseIntegrationTest.cs new file mode 100644 index 0000000..bc99c9f --- /dev/null +++ b/src/GSoft.Xunit.Extensions/BaseIntegrationTest.cs @@ -0,0 +1,18 @@ +namespace GSoft.Xunit.Extensions; + +public abstract class BaseIntegrationTest : BaseIntegrationTest +{ + protected BaseIntegrationTest(ITestOutputHelper testOutputHelper) + : base(new EmptyIntegrationFixture(), testOutputHelper) + { + } +} + +public abstract class BaseIntegrationTest : BaseUnitTest + where TFixture : class +{ + protected BaseIntegrationTest(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture, testOutputHelper) + { + } +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/BaseUnitFixture.cs b/src/GSoft.Xunit.Extensions/BaseUnitFixture.cs new file mode 100644 index 0000000..45399e0 --- /dev/null +++ b/src/GSoft.Xunit.Extensions/BaseUnitFixture.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace GSoft.Xunit.Extensions; + +public class BaseUnitFixture : IConfigureServiceCollection, IHasXUnitOutput, IDisposable +{ + private readonly Lazy _lazyConfiguration; + private readonly XunitLoggerProvider _loggerProvider; + + protected BaseUnitFixture() + { + this._lazyConfiguration = new Lazy(this.BuildConfiguration); + this._loggerProvider = new XunitLoggerProvider(); + } + + protected IConfiguration Configuration + { + get => this._lazyConfiguration.Value; + } + + private IConfiguration BuildConfiguration() + { + var configurationBuilder = new ConfigurationBuilder(); + this.ConfigureConfiguration(configurationBuilder); + return configurationBuilder.Build(); + } + + protected virtual IConfigurationBuilder ConfigureConfiguration(IConfigurationBuilder builder) + { + return builder; + } + + public virtual IServiceCollection ConfigureServices(IServiceCollection services) + { + services.AddSingleton(this.Configuration); + services.AddLogging(builder => + { + var loggingSection = this.Configuration.GetSection("Logging"); + if (loggingSection.Exists()) + { + builder.AddConfiguration(loggingSection); + } + + builder.AddProvider(this._loggerProvider); + }); + + return services; + } + + public void SetXunitOutput(ITestOutputHelper outputHelper) + { + this._loggerProvider.Output = outputHelper; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this._loggerProvider.Dispose(); + } + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/BaseUnitTest.cs b/src/GSoft.Xunit.Extensions/BaseUnitTest.cs new file mode 100644 index 0000000..1c042f8 --- /dev/null +++ b/src/GSoft.Xunit.Extensions/BaseUnitTest.cs @@ -0,0 +1,80 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace GSoft.Xunit.Extensions; + +public abstract class BaseUnitTest : BaseUnitTest +{ + protected BaseUnitTest(ITestOutputHelper testOutputHelper) + : base(new EmptyUnitFixture(), testOutputHelper) + { + } +} + +public abstract class BaseUnitTest : IClassFixture, IDisposable + where TFixture : class +{ + private readonly ServiceCollection _services; + private readonly Lazy _lazyServiceProvider; + private readonly Lazy _lazyLogger; + + protected BaseUnitTest(TFixture fixture, ITestOutputHelper testOutputHelper) + { + this._services = new ServiceCollection(); + this._lazyServiceProvider = new Lazy(this.CreateServiceProvider); + this._lazyLogger = new Lazy(this.CreateLogger); + + if (fixture is IHasXUnitOutput xunitOutputFixture) + { + xunitOutputFixture.SetXunitOutput(testOutputHelper); + } + + this.Fixture = fixture; + } + + protected TFixture Fixture { get; } + + protected ILogger Logger + { + get => this._lazyLogger.Value; + } + + protected virtual IServiceProvider Services + { + get => this._lazyServiceProvider.Value; + } + + private ServiceProvider CreateServiceProvider() + { + if (this.Fixture is IConfigureServiceCollection serviceCollectionConfigurator) + { + serviceCollectionConfigurator.ConfigureServices(this._services); + } + + return this._services.BuildServiceProvider(); + } + + private ILogger CreateLogger() + { + var loggerType = typeof(ILogger<>).MakeGenericType(this.GetType()); + return (ILogger)this.Services.GetRequiredService(loggerType); + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (this._lazyServiceProvider.IsValueCreated) + { + this._lazyServiceProvider.Value.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/DecorationServiceCollectionExtensions.cs b/src/GSoft.Xunit.Extensions/DecorationServiceCollectionExtensions.cs new file mode 100644 index 0000000..b83dc8e --- /dev/null +++ b/src/GSoft.Xunit.Extensions/DecorationServiceCollectionExtensions.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection; + +// Inspired from Scrutor: https://github.com/khellang/Scrutor/blob/v4.0.0/src/Scrutor/ServiceCollectionExtensions.Decoration.cs +// Modifying IServiceCollection registrations can be dangerous and not aligned with the Microsoft vision of dependency injection +// That's why these methods only exist in test projects where we want to wrap things in order to do some assertions +public static class DecorationServiceCollectionExtensions +{ + public static IServiceCollection DecorateWithSameLifetime(this IServiceCollection services) + where TService : class + where TDecorator : class, TService + { + return services.DecorateWithSameLifetime((serviceProvider, service) => ActivatorUtilities.CreateInstance(serviceProvider, service)); + } + + public static IServiceCollection DecorateWithSameLifetime(this IServiceCollection services, Func decoratorFactory) + where TDecorator : class, TService + { + return services.DecorateWithSameLifetime((_, service) => decoratorFactory(service)); + } + + public static IServiceCollection DecorateWithSameLifetime(this IServiceCollection services, Func decoratorFactory) + where TService : class + { + return services.DecorateWithSameLifetime(decoratorFactory); + } + + public static IServiceCollection DecorateWithSameLifetime(this IServiceCollection services, Func decoratorFactory) + where TDecorator : class, TService + { + if (!services.TryDecorateDescriptors(typeof(TService), x => x.Decorate(decoratorFactory))) + { + throw new Exception("Could not find a registration of type " + typeof(TService) + " in the service collection"); + } + + return services; + } + + public static IServiceCollection DecorateWithSameLifetime(this IServiceCollection services, Func decoratorFactory) + where TService : class + { + return services.DecorateWithSameLifetime(decoratorFactory); + } + + private static bool TryDecorateDescriptors(this IServiceCollection services, Type serviceType, Func decorator) + { + var descriptors = services.FindDescriptors(serviceType).ToArray(); + + foreach (var (index, descriptor) in descriptors) + { + services.Insert(index, decorator(descriptor)); + services.Remove(descriptor); + } + + return descriptors.Length > 0; + } + + private static IEnumerable<(int Index, ServiceDescriptor Descriptor)> FindDescriptors(this IServiceCollection services, Type serviceType) + { + for (var index = 0; index < services.Count; ++index) + { + var descriptor = services[index]; + if (descriptor.ServiceType == serviceType) + { + yield return (index, descriptor); + } + } + } + + private static ServiceDescriptor Decorate(this ServiceDescriptor descriptor, Func decoratorFactory) + where TDecorator : class, TService + { + object ImplementationFactory(IServiceProvider serviceProvider) + { + var decoratedInstance = serviceProvider.GetInstance(descriptor); + return decoratorFactory(serviceProvider, decoratedInstance); + } + + return ServiceDescriptor.Describe( + serviceType: descriptor.ServiceType, + implementationFactory: ImplementationFactory, + lifetime: descriptor.Lifetime); + } + + private static TService GetInstance(this IServiceProvider serviceProvider, ServiceDescriptor descriptor) + { + if (descriptor.ImplementationInstance != null) + { + return (TService)descriptor.ImplementationInstance; + } + + if (descriptor.ImplementationFactory != null) + { + return (TService)descriptor.ImplementationFactory(serviceProvider); + } + + var implementationType = descriptor.ImplementationType; + if (implementationType != null) + { + if (implementationType == descriptor.ServiceType) + { + // Since implementationType is equal to ServiceType we need explicitly create an implementation type through reflections in order to avoid infinite recursion. + // Should not cause issue with singletons, since singleton will be a decorator and after this fact we can don't care about lifecycle of decorable service (for sure, if IDisposable of decorator disposes underlying type:)) + return (TService)ActivatorUtilities.CreateInstance(serviceProvider, implementationType); + } + + return (TService)ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, implementationType); + } + + throw new InvalidOperationException($"No implementation factory or instance or type found for {descriptor.ServiceType}."); + } +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/EmptyIntegrationFixture.cs b/src/GSoft.Xunit.Extensions/EmptyIntegrationFixture.cs new file mode 100644 index 0000000..9d4dfe1 --- /dev/null +++ b/src/GSoft.Xunit.Extensions/EmptyIntegrationFixture.cs @@ -0,0 +1,5 @@ +namespace GSoft.Xunit.Extensions; + +public sealed class EmptyIntegrationFixture : BaseIntegrationFixture +{ +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/EmptyUnitFixture.cs b/src/GSoft.Xunit.Extensions/EmptyUnitFixture.cs new file mode 100644 index 0000000..2b92dec --- /dev/null +++ b/src/GSoft.Xunit.Extensions/EmptyUnitFixture.cs @@ -0,0 +1,5 @@ +namespace GSoft.Xunit.Extensions; + +public sealed class EmptyUnitFixture : BaseUnitFixture +{ +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/GSoft.Xunit.Extensions.csproj b/src/GSoft.Xunit.Extensions/GSoft.Xunit.Extensions.csproj new file mode 100644 index 0000000..714d196 --- /dev/null +++ b/src/GSoft.Xunit.Extensions/GSoft.Xunit.Extensions.csproj @@ -0,0 +1,27 @@ + + + net462;netcoreapp3.1;net6.0 + True + True + snupkg + GSoft.Xunit.Extensions + GSoft.Xunit.Extensions + true + + + + + + + + + + + + + + + + + + diff --git a/src/GSoft.Xunit.Extensions/IConfigureServiceCollection.cs b/src/GSoft.Xunit.Extensions/IConfigureServiceCollection.cs new file mode 100644 index 0000000..0ad0ad4 --- /dev/null +++ b/src/GSoft.Xunit.Extensions/IConfigureServiceCollection.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace GSoft.Xunit.Extensions; + +public interface IConfigureServiceCollection +{ + IServiceCollection ConfigureServices(IServiceCollection services); +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/IHasXUnitOutput.cs b/src/GSoft.Xunit.Extensions/IHasXUnitOutput.cs new file mode 100644 index 0000000..cd38386 --- /dev/null +++ b/src/GSoft.Xunit.Extensions/IHasXUnitOutput.cs @@ -0,0 +1,6 @@ +namespace GSoft.Xunit.Extensions; + +public interface IHasXUnitOutput +{ + void SetXunitOutput(ITestOutputHelper outputHelper); +} \ No newline at end of file diff --git a/src/GSoft.Xunit.Extensions/XunitLoggerProvider.cs b/src/GSoft.Xunit.Extensions/XunitLoggerProvider.cs new file mode 100644 index 0000000..0c87cc9 --- /dev/null +++ b/src/GSoft.Xunit.Extensions/XunitLoggerProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace GSoft.Xunit.Extensions; + +internal sealed class XunitLoggerProvider : ILoggerProvider, ILogger +{ + private static readonly Dictionary LogLevelStrings = new Dictionary + { + [LogLevel.None] = "NON", + [LogLevel.Trace] = "TRC", + [LogLevel.Debug] = "DBG", + [LogLevel.Information] = "INF", + [LogLevel.Warning] = "WRN", + [LogLevel.Error] = "ERR", + [LogLevel.Critical] = "CRT", + }; + + public ITestOutputHelper? Output { get; set; } + + public ILogger CreateLogger(string categoryName) + { + return this; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (this.Output != null) + { + var message = formatter(state, exception); + this.Output.WriteLine("[{0:HH:mm:ss:ffff} {1}] {2}", DateTime.Now, LogLevelStrings[logLevel], message); + } + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public IDisposable BeginScope(TState state) + { + return new NoopDisposable(); + } + + public void Dispose() + { + } + + private sealed class NoopDisposable : IDisposable + { + public void Dispose() + { + } + } +} \ No newline at end of file