Skip to content

Commit

Permalink
Add automated tests (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
meziantou authored Feb 23, 2024
1 parent bcb1bf0 commit 88510d4
Show file tree
Hide file tree
Showing 20 changed files with 535 additions and 90 deletions.
34 changes: 2 additions & 32 deletions Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,6 @@ Process {
$outputDir = Join-Path $PSScriptRoot ".output"
$nupkgsPath = Join-Path $outputDir "*.nupkg"

$testProjectName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName())
$testProjectDir = Join-Path $outputDir $testProjectName
$testProjectPath = Join-Path $testProjectDir "$testProjectName.csproj"
$testNugetConfigPath = Join-Path $testProjectDir "nuget.config"
$testNugetConfigContents = @"
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="local" value="$outputDir" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget">
<package pattern="*" />
</packageSource>
<packageSource key="local">
<package pattern="Workleap.DotNet.CodingStandards" />
</packageSource>
</packageSourceMapping>
</configuration>
"@

try {
Push-Location $workingDir
Remove-Item $outputDir -Force -Recurse -ErrorAction SilentlyContinue
Expand All @@ -54,14 +30,8 @@ Process {
# Pack using NuGet.exe
Exec { & nuget pack Workleap.DotNet.CodingStandards.nuspec -OutputDirectory $outputDir -Version $version -ForceEnglishOutput }

# Create a new test console project, add our newly created package and try to build it in release mode
# The default .NET console project template with top-level statements should not trigger any warnings
# We treat warnings as errors even though it's supposed to be already enabled by our package,
# just in case the package is not working as expected
Exec { & dotnet new console --name $testProjectName --output $testProjectDir }
Set-Content -Path $testNugetConfigPath -Value $testNugetConfigContents
Exec { & dotnet add $testProjectPath package Workleap.DotNet.CodingStandards --version $version }
Exec { & dotnet build $testProjectPath --configuration Release /p:TreatWarningsAsErrors=true }
# Run tests
Exec { & dotnet test --configuration Release --logger "console;verbosity=detailed" }

# Push to a NuGet feed if the environment variables are set
if (($null -ne $env:NUGET_SOURCE ) -and ($null -ne $env:NUGET_API_KEY)) {
Expand Down
10 changes: 10 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Workleap.DotNet.CodingStandards" Version="0.1.0" />
</ItemGroup>
</Project>
29 changes: 29 additions & 0 deletions src/build/Workleap.DotNet.CodingStandards.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project>
<PropertyGroup>
<ReportAnalyzer Condition="'$(ReportAnalyzer)' == ''">true</ReportAnalyzer>
<Features Condition="'$(Features)' == ''">strict</Features>
<Deterministic Condition="'$(Deterministic)' == ''">true</Deterministic>
<EnableNETAnalyzers Condition="'$(EnableNETAnalyzers)' == ''">true</EnableNETAnalyzers>
<AnalysisLevel Condition="'$(AnalysisLevel)' == ''">latest-all</AnalysisLevel>
<EnforceCodeStyleInBuild Condition="'$(EnforceCodeStyleInBuild)' == ''">true</EnforceCodeStyleInBuild>

<!-- https://learn.microsoft.com/en-us/nuget/release-notes/nuget-5.5#summary-whats-new-in-55 -->
<RestoreUseStaticGraphEvaluation Condition="'$(RestoreUseStaticGraphEvaluation)' == ''">true</RestoreUseStaticGraphEvaluation>

<!-- Enable ContinuousIntegrationBuild when running on CI -->
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">true</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(GITLAB_CI)' == 'true'">true</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(TEAMCITY_VERSION)' != ''">true</ContinuousIntegrationBuild>

<!-- TreatWarningsAsErrors is enabled for release builds, unless explicitly set -->
<TreatWarningsAsErrors Condition="'$(Configuration)' == 'Release' AND '$(TreatWarningsAsErrors)' == ''">true</TreatWarningsAsErrors>

<!-- https://devblogs.microsoft.com/visualstudio/vs-toolbox-accelerate-your-builds-of-sdk-style-net-projects/ -->
<AccelerateBuildsInVisualStudio Condition="'$(AccelerateBuildsInVisualStudio)' == ''">true</AccelerateBuildsInVisualStudio>

<!-- GenerateDocumentationFile must be set to true for IDE0005 (Remove unnecessary usings/imports) to work -->
<GenerateDocumentationFile Condition="'$(GenerateDocumentationFile)' == ''">true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
31 changes: 31 additions & 0 deletions src/build/Workleap.DotNet.CodingStandards.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project>
<!-- Register the EditorConfig files to the project -->
<!-- Remember that a particular .NET analysis rule can only be configured once across all imported global EditorConfig files -->
<ItemGroup>
<!-- Basic EditorConfig settings such as encoding, indentation, etc. -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\1_FileDefaults.editorconfig" />

<!-- C# code style -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\2_CodeStyle.editorconfig" />

<!-- .NET analyzers configuration for all projects, enforcing C# code style, quality, performance and security -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\3_AllProjectsAnalyzers.editorconfig" />

<!-- Configure ReSharper analyzers that overlaps with built-in .NET analyzers (only appears in Rider and VisualStudio IDEs) -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\4_ReSharperAnalyzers.editorconfig" />

<!-- .NET analyzers configuration only for test projects -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\5_TestProjectsAnalyzers.editorconfig" Condition="'$(IsTestProject)' == 'true'" />
</ItemGroup>

<!-- Banned Symbols -->
<PropertyGroup>
<IncludeDefaultBannedSymbols Condition="$(IncludeDefaultBannedSymbols) == ''">true</IncludeDefaultBannedSymbols>
</PropertyGroup>

<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\..\files\BannedSymbols.txt"
Condition="$(IncludeDefaultBannedSymbols) == 'true'"
Visible="false" />
</ItemGroup>
</Project>
3 changes: 3 additions & 0 deletions src/buildMultiTargeting/Workleap.DotNet.CodingStandards.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)\..\build\Workleap.DotNet.CodingStandards.props" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)\..\build\Workleap.DotNet.CodingStandards.targets" />
</Project>
30 changes: 2 additions & 28 deletions src/buildTransitive/Workleap.DotNet.CodingStandards.props
Original file line number Diff line number Diff line change
@@ -1,29 +1,3 @@
<Project>
<PropertyGroup>
<ReportAnalyzer Condition="'$(ReportAnalyzer)' == ''">true</ReportAnalyzer>
<Features Condition="'$(Features)' == ''">strict</Features>
<Deterministic Condition="'$(Deterministic)' == ''">true</Deterministic>
<EnableNETAnalyzers Condition="'$(EnableNETAnalyzers)' == ''">true</EnableNETAnalyzers>
<AnalysisLevel Condition="'$(AnalysisLevel)' == ''">latest-all</AnalysisLevel>
<EnforceCodeStyleInBuild Condition="'$(EnforceCodeStyleInBuild)' == ''">true</EnforceCodeStyleInBuild>

<!-- https://learn.microsoft.com/en-us/nuget/release-notes/nuget-5.5#summary-whats-new-in-55 -->
<RestoreUseStaticGraphEvaluation Condition="'$(RestoreUseStaticGraphEvaluation)' == ''">true</RestoreUseStaticGraphEvaluation>

<!-- Enable ContinuousIntegrationBuild when running on CI -->
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">true</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(GITLAB_CI)' == 'true'">true</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(TEAMCITY_VERSION)' != ''">true</ContinuousIntegrationBuild>

<!-- TreatWarningsAsErrors is enabled for release builds, unless explicitly set -->
<TreatWarningsAsErrors Condition="'$(Configuration)' == 'Release' AND '$(TreatWarningsAsErrors)' == ''">true</TreatWarningsAsErrors>

<!-- https://devblogs.microsoft.com/visualstudio/vs-toolbox-accelerate-your-builds-of-sdk-style-net-projects/ -->
<AccelerateBuildsInVisualStudio Condition="'$(AccelerateBuildsInVisualStudio)' == ''">true</AccelerateBuildsInVisualStudio>

<!-- GenerateDocumentationFile must be set to true for IDE0005 (Remove unnecessary usings/imports) to work -->
<GenerateDocumentationFile Condition="'$(GenerateDocumentationFile)' == ''">true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
<Import Project="$(MSBuildThisFileDirectory)\..\build\Workleap.DotNet.CodingStandards.props" />
</Project>
32 changes: 2 additions & 30 deletions src/buildTransitive/Workleap.DotNet.CodingStandards.targets
Original file line number Diff line number Diff line change
@@ -1,31 +1,3 @@
<Project>
<!-- Register the EditorConfig files to the project -->
<!-- Remember that a particular .NET analysis rule can only be configured once across all imported global EditorConfig files -->
<ItemGroup>
<!-- Basic EditorConfig settings such as encoding, indentation, etc. -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\1_FileDefaults.editorconfig" />

<!-- C# code style -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\2_CodeStyle.editorconfig" />

<!-- .NET analyzers configuration for all projects, enforcing C# code style, quality, performance and security -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\3_AllProjectsAnalyzers.editorconfig" />

<!-- Configure ReSharper analyzers that overlaps with built-in .NET analyzers (only appears in Rider and VisualStudio IDEs) -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\4_ReSharperAnalyzers.editorconfig" />

<!-- .NET analyzers configuration only for test projects -->
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\5_TestProjectsAnalyzers.editorconfig" Condition="'$(IsTestProject)' == 'true'" />
</ItemGroup>

<!-- Banned Symbols -->
<PropertyGroup>
<IncludeDefaultBannedSymbols Condition="$(IncludeDefaultBannedSymbols) == ''">true</IncludeDefaultBannedSymbols>
</PropertyGroup>

<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\..\files\BannedSymbols.txt"
Condition="$(IncludeDefaultBannedSymbols) == 'true'"
Visible="false" />
</ItemGroup>
</Project>
<Import Project="$(MSBuildThisFileDirectory)\..\build\Workleap.DotNet.CodingStandards.targets" />
</Project>
66 changes: 66 additions & 0 deletions tests/Workleap.DotNet.CodingStandards.Tests/CodingStandardTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Workleap.DotNet.CodingStandards.Tests.Helpers;
using Xunit.Abstractions;

namespace Workleap.DotNet.CodingStandards.Tests;

public sealed class CodingStandardTests(PackageFixture fixture, ITestOutputHelper testOutputHelper) : IClassFixture<PackageFixture>
{
[Fact]
public async Task BannedSymbolsAreReported()
{
using var project = new ProjectBuilder(fixture, testOutputHelper);
project.AddCsprojFile();
project.AddFile("sample.cs", "_ = System.DateTime.Now;");
var data = await project.BuildAndGetOutput();
Assert.True(data.HasWarning("RS0030"));
}

[Fact]
public async Task WarningsAsErrorOnGitHubActions()
{
using var project = new ProjectBuilder(fixture, testOutputHelper);
project.AddCsprojFile();
project.AddFile("sample.cs", "_ = System.DateTime.Now;");
var data = await project.BuildAndGetOutput(["--configuration", "Release", "/p:GITHUB_ACTIONS=true"]);
Assert.True(data.HasError("RS0030"));
}

[Fact]
public async Task NamingConvention_Invalid()
{
using var project = new ProjectBuilder(fixture, testOutputHelper);
project.AddCsprojFile();
project.AddFile("sample.cs", """
_ = "";
class Sample
{
private readonly int field;
public Sample(int a) => field = a;
public int A() => field;
}
""");
var data = await project.BuildAndGetOutput(["--configuration", "Release"]);
Assert.True(data.HasError("IDE1006"));
}

[Fact]
public async Task NamingConvention_Valid()
{
using var project = new ProjectBuilder(fixture, testOutputHelper);
project.AddCsprojFile();
project.AddFile("sample.cs", """
_ = "";
class Sample
{
private int _field;
}
""");
var data = await project.BuildAndGetOutput(["--configuration", "Release"]);
Assert.False(data.HasError("IDE1006"));
Assert.False(data.HasWarning("IDE1006"));
}
}
15 changes: 15 additions & 0 deletions tests/Workleap.DotNet.CodingStandards.Tests/Helpers/PathHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Workleap.DotNet.CodingStandards.Tests.Helpers;

internal static class PathHelpers
{
public static string GetRootDirectory()
{
var directory = Environment.CurrentDirectory;
while (directory != null && !Directory.Exists(Path.Combine(directory, ".git")))
{
directory = Path.GetDirectoryName(directory);
}

return directory ?? throw new InvalidOperationException("Cannot find the root of the git repository");
}
}
100 changes: 100 additions & 0 deletions tests/Workleap.DotNet.CodingStandards.Tests/Helpers/ProjectBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System.Xml.Linq;
using Xunit.Abstractions;
using System.Text.Json;
using CliWrap;

namespace Workleap.DotNet.CodingStandards.Tests.Helpers;

internal sealed class ProjectBuilder : IDisposable
{
private const string SarifFileName = "BuildOutput.sarif";

private readonly TemporaryDirectory _directory;
private readonly ITestOutputHelper _testOutputHelper;

public ProjectBuilder(PackageFixture fixture, ITestOutputHelper testOutputHelper)
{
this._testOutputHelper = testOutputHelper;

this._directory = TemporaryDirectory.Create();
this._directory.CreateTextFile("NuGet.config", $"""
<configuration>
<config>
<add key="globalPackagesFolder" value="{fixture.PackageDirectory}/packages" />
</config>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="TestSource" value="{fixture.PackageDirectory}" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="TestSource">
<package pattern="Workleap.DotNet.CodingStandards" />
</packageSource>
</packageSourceMapping>
</configuration>
""");

File.Copy(Path.Combine(PathHelpers.GetRootDirectory(), "global.json"), this._directory.GetPath("global.json"));
}

public void AddFile(string relativePath, string content)
{
File.WriteAllText(this._directory.GetPath(relativePath), content);
}

public void AddCsprojFile(Dictionary<string, string>? properties = null)
{
var element = new XElement("PropertyGroup");
if (properties != null)
{
foreach (var prop in properties)
{
element.Add(new XElement(prop.Key), prop.Value);
}
}

var content = $"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>exe</OutputType>
<TargetFramework>net$(NETCoreAppMaximumVersion)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ErrorLog>{SarifFileName},version=2.1</ErrorLog>
</PropertyGroup>
{element}
<ItemGroup>
<PackageReference Include="Workleap.DotNet.CodingStandards" Version="*" />
</ItemGroup>
</Project>
""";

File.WriteAllText(this._directory.GetPath("test.csproj"), content);
}

public async Task<SarifFile> BuildAndGetOutput(string[]? buildArguments = null)
{
var result = await Cli.Wrap("dotnet")
.WithWorkingDirectory(this._directory.FullPath)
.WithArguments(["build", .. (buildArguments ?? [])])
.WithEnvironmentVariables(env => env.Set("CI", null).Set("GITHUB_ACTIONS", null))
.WithStandardOutputPipe(PipeTarget.ToDelegate(this._testOutputHelper.WriteLine))
.WithStandardErrorPipe(PipeTarget.ToDelegate(this._testOutputHelper.WriteLine))
.WithValidation(CommandResultValidation.None)
.ExecuteAsync();

this._testOutputHelper.WriteLine("Process exit code: " + result.ExitCode);

var bytes = await File.ReadAllBytesAsync(this._directory.GetPath(SarifFileName));
var sarif = JsonSerializer.Deserialize<SarifFile>(bytes) ?? throw new InvalidOperationException("The sarif file is invalid");
this._testOutputHelper.WriteLine("Sarif result:\n" + string.Join("\n", sarif.AllResults().Select(r => r.ToString())));
return sarif;
}

public void Dispose() => this._directory.Dispose();
}
Loading

0 comments on commit 88510d4

Please sign in to comment.