Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/main.yml → .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ name: Release

on:
push:
branches: [ "main" ]
branches:
- develop
pull_request:
branches: [ "main" ]
branches:
- develop

jobs:
build:
Expand All @@ -17,15 +19,15 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: 'csharp'

- name: Setup .NET 10.0
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.x

Expand All @@ -39,4 +41,4 @@ jobs:
run: dotnet test --configuration Release --no-build --verbosity normal --framework net8.0

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
13 changes: 10 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ jobs:
actions: read
contents: read
security-events: write
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup .NET 10.0
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.x

Expand All @@ -32,5 +33,11 @@ jobs:
- name: Pack
run: dotnet pack --configuration Release --no-build --output .

- name: NuGet Login
uses: NuGet/login@v1
id: nuget-login
with:
user: ${{secrets.NUGET_USER}}

- name: Push to NuGet
run: dotnet nuget push "*.nupkg" --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --skip-duplicate
run: dotnet nuget push "*.nupkg" --api-key ${{steps.nuget-login.outputs.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --skip-duplicate
42 changes: 33 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,44 @@

Test helpers and extension methods to simplify testing of .NET source generators.

[![main](https://img.shields.io/github/actions/workflow/status/jscarle/SourceGeneratorTestHelpers/main.yml?logo=github)](https://github.com/jscarle/SourceGeneratorTestHelpers)
[![develop](https://img.shields.io/github/actions/workflow/status/jscarle/SourceGeneratorTestHelpers/develop.yml?logo=github)](https://github.com/jscarle/SourceGeneratorTestHelpers)
[![nuget](https://img.shields.io/nuget/v/SourceGeneratorTestHelpers)](https://www.nuget.org/packages/SourceGeneratorTestHelpers)
[![downloads](https://img.shields.io/nuget/dt/SourceGeneratorTestHelpers)](https://www.nuget.org/packages/SourceGeneratorTestHelpers)

## Testing a source generator

```csharp
var result = SourceGenerator.Run<YourSourceGenerator>("your source");
var (generator, result) = SourceGenerator.Run<YourSourceGenerator>("your source");
```

You can also run a generator against multiple source files by passing any `IEnumerable<string>`.

```csharp
var sources = new[]
{
"namespace Tests; public partial class First;",
"namespace Tests; public partial class Second;"
};

var (generator, result) = SourceGenerator.Run<YourSourceGenerator>(sources);
```

## Testing an incremental source generator

```csharp
var result = IncrementalGenerator.Run<YourSourceGenerator>("your source");
var (generator, result) = IncrementalGenerator.Run<YourSourceGenerator>("your source");
```

The same multiple-source overload is available for incremental generators.

```csharp
var sources = new[]
{
"namespace Tests; public partial class First;",
"namespace Tests; public partial class Second;"
};

var (generator, result) = IncrementalGenerator.Run<YourSourceGenerator>(sources);
```

## Obtaining the generated source
Expand Down Expand Up @@ -50,15 +74,15 @@ Using one of the testing framework packages below, you can also assert the diffe
[![MSTest](https://img.shields.io/nuget/dt/SourceGeneratorTestHelpers.MSTest?label=MSTest)](https://www.nuget.org/packages/SourceGeneratorTestHelpers.MSTest)

```csharp
var result = IncrementalGenerator.Run<YourSourceGenerator>("your source");
var (_, result) = IncrementalGenerator.Run<YourSourceGenerator>("your source");

result.ShouldProduce("TestId.g.cs", "expected source");
```

_Note: If you do not wish to assert on errors produced during diagnostics of the source generator run, you can simply disable them as such._

```csharp
var result = IncrementalGenerator.Run<YourSourceGenerator>("your source");
var (_, result) = IncrementalGenerator.Run<YourSourceGenerator>("your source");

result.ShouldProduce("TestId.g.cs", "expected source", false);
```
Expand All @@ -75,7 +99,7 @@ public class SourceGeneratorTests
[Fact]
public Task ShouldProductTestId()
{
var result = IncrementalGenerator.Run<YourSourceGenerator>("your source");
var (_, result) = IncrementalGenerator.Run<YourSourceGenerator>("your source");
return result.VerifyAsync("TestId.g.cs");
}
}
Expand All @@ -90,7 +114,7 @@ public class SourceGeneratorTests
[Test]
public Task ShouldProductTestId()
{
var result = IncrementalGenerator.Run<YourSourceGenerator>("your source");
var (_, result) = IncrementalGenerator.Run<YourSourceGenerator>("your source");
return result.VerifyAsync("TestId.g.cs");
}
}
Expand All @@ -106,8 +130,8 @@ public class SourceGeneratorTests :
[TestMethod]
public Task ShouldProductTestId()
{
var result = IncrementalGenerator.Run<YourSourceGenerator>("your source");
return VerifyAsync("TestId.g.cs");
var (_, result) = IncrementalGenerator.Run<YourSourceGenerator>("your source");
return VerifyAsync(result, "TestId.g.cs");
}
}
```
64 changes: 9 additions & 55 deletions src/SourceGeneratorTestHelpers.MSTest/GeneratorDriverTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,16 @@

namespace SourceGeneratorTestHelpers.MSTest;

internal abstract class GeneratorDriverTestBase : VerifyBase
public abstract class GeneratorDriverTestBase : VerifyBase
{
/// <summary>Verifies that the generated source from a <see cref="GeneratorDriverRunResult"/> with a specific file path ending matches the expected source.</summary>
/// <param name="result">The <see cref="GeneratorDriverRunResult"/> to get the source from.</param>
/// <param name="filePathEndsWith">The string that the generated source's file path should end with.</param>
/// <param name="expectedSource">The expected source that the generated source should match.</param>
/// <exception cref="ArgumentNullException">If <paramref name="result"/> is null.</exception>
public static void ShouldProduce(
GeneratorDriverRunResult result,
string filePathEndsWith,
string expectedSource
)
public static void ShouldProduce(GeneratorDriverRunResult result, string filePathEndsWith, string expectedSource)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(result);
#else
if (result is null)
throw new ArgumentNullException(nameof(result));
#endif

result.InternalShouldProduce(filePathEndsWith, expectedSource, false, false, _ => { });
}
Expand All @@ -43,39 +34,21 @@ public static void ShouldProduce(
bool assertOnWarnings = false
)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(result.Result);
#else
if (result.Result is null)
throw new ArgumentNullException(nameof(result));
#endif

result.CompilationDiagnostics.InternalAssertOnDiagnostics(
assertOnErrors,
assertOnWarnings,
message => throw new AssertFailedException(message),
result.CompilationDiagnostics.InternalAssertOnDiagnostics(assertOnErrors, assertOnWarnings, message => throw new AssertFailedException(message),
"There were errors in the compilation."
);
result.Result.Diagnostics.InternalAssertOnDiagnostics(
assertOnErrors,
assertOnWarnings,
message => throw new AssertFailedException(message),
result.Result.Diagnostics.InternalAssertOnDiagnostics(assertOnErrors, assertOnWarnings, message => throw new AssertFailedException(message),
"There were errors in the output generated by the source generator."
);

result.Result.InternalShouldProduce(
filePathEndsWith,
expectedSource,
assertOnErrors,
assertOnWarnings,
result.Result.InternalShouldProduce(filePathEndsWith, expectedSource, assertOnErrors, assertOnWarnings,
message => throw new AssertFailedException(message)
);
}

/// <summary>
/// Verifies that the generated source from a <see cref="GeneratorDriverRunResult"/> with a specific file path using
/// <see cref="VerifyBase.Verify(string?, string, VerifySettings?, string)"/>.
/// </summary>
/// <summary>Verifies that the generated source from a <see cref="GeneratorDriverRunResult"/> with a specific file path using <see cref="VerifyBase.Verify(string?, string, VerifySettings?, string)"/>.</summary>
/// <param name="result">The <see cref="GeneratorDriverRunResult"/> to get the source from.</param>
/// <param name="filePathEndsWith">The string that the generated source's file path should end with.</param>
/// <param name="verifySettings">The verify settings.</param>
Expand All @@ -88,12 +61,7 @@ public SettingsTask VerifyAsync(
[CallerFilePath] string sourceFile = ""
)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(result);
#else
if (result is null)
throw new ArgumentNullException(nameof(result));
#endif

var generatedSource = result.InternalGetSource(filePathEndsWith);
var source = generatedSource.HasValue ? generatedSource.Value.Source : "";
Expand All @@ -102,10 +70,7 @@ public SettingsTask VerifyAsync(
return Verify(source, "txt", verifySettings, sourceFile);
}

/// <summary>
/// Verifies that the generated source from a <see cref="GeneratorDriverRunResult"/> with a specific file path using
/// <see cref="VerifyBase.Verify(string?, string, VerifySettings?, string)"/>.
/// </summary>
/// <summary>Verifies that the generated source from a <see cref="GeneratorDriverRunResult"/> with a specific file path using <see cref="VerifyBase.Verify(string?, string, VerifySettings?, string)"/>.</summary>
/// <param name="result">The <see cref="GeneratorDriverRunResult"/> to get the source from.</param>
/// <param name="filePathEndsWith">The string that the generated source's file path should end with.</param>
/// <param name="assertOnErrors"><see langword="true"/> to assert on reported errors, <see langword="false"/> othwerwise. Defaults to <see langword="true"/>.</param>
Expand All @@ -122,23 +87,12 @@ public SettingsTask VerifyAsync(
[CallerFilePath] string sourceFile = ""
)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(result.Result);
#else
if (result.Result is null)
throw new ArgumentNullException(nameof(result));
#endif

result.CompilationDiagnostics.InternalAssertOnDiagnostics(
assertOnErrors,
assertOnWarnings,
message => throw new AssertFailedException(message),
result.CompilationDiagnostics.InternalAssertOnDiagnostics(assertOnErrors, assertOnWarnings, message => throw new AssertFailedException(message),
"There were errors in the compilation."
);
result.Result.Diagnostics.InternalAssertOnDiagnostics(
assertOnErrors,
assertOnWarnings,
message => throw new AssertFailedException(message),
result.Result.Diagnostics.InternalAssertOnDiagnostics(assertOnErrors, assertOnWarnings, message => throw new AssertFailedException(message),
"There were errors in the output generated by the source generator."
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<ImplicitUsings>true</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>SourceGeneratorTestHelpers.MSTest</RootNamespace>
<LangVersion>latest</LangVersion>
<Version>10.0.0</Version>
<Version>10.1.0</Version>
<Title>SourceGeneratorTestHelpers.MSTest</Title>
<Authors>Jean-Sebastien Carle</Authors>
<Description>Test helpers and extension methods to simplify testing of .NET source generators.</Description>
<Copyright>Copyright © Jean-Sebastien Carle 2025</Copyright>
<Copyright>Copyright © Jean-Sebastien Carle 2025-2026</Copyright>
<PackageId>SourceGeneratorTestHelpers.MSTest</PackageId>
<PackageProjectUrl>https://github.com/jscarle/SourceGeneratorTestHelpers</PackageProjectUrl>
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/jscarle/SourceGeneratorTestHelpers</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>testing source-generators mstest</PackageTags>
<AssemblyVersion>10.0.0.0</AssemblyVersion>
<FileVersion>10.0.0.0</FileVersion>
<AssemblyVersion>10.1.0.0</AssemblyVersion>
<FileVersion>10.1.0.0</FileVersion>
<NeutralLanguage>en-US</NeutralLanguage>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
Expand All @@ -28,17 +28,18 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Optimize>true</Optimize>
<IsPackable>true</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageReference Include="SourceGeneratorTestHelpers" Version="10.0.0" />
<PackageReference Include="MSTest.TestFramework" Version="4.0.2" />
<PackageReference Include="Verify.MSTest" Version="20.8.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="[4.14.0]"/>
<PackageReference Include="SourceGeneratorTestHelpers" Version="10.1.0"/>
<PackageReference Include="MSTest.TestFramework" Version="4.2.3"/>
<PackageReference Include="Verify.MSTest" Version="31.20.0"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.300" PrivateAssets="All"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
<None Include="..\..\LICENSE.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
Expand All @@ -51,4 +52,9 @@
</None>
</ItemGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

</Project>
Loading
Loading