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
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,12 @@ internal static class TUnit_TestProject_AsyncMethodDataSourceDrivenTests__TestSo
{
new global::TUnit.Core.MethodDataSourceAttribute("AsyncDataMethodWithArgs")
{
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromEnumerable(static () => global::TUnit.TestProject.AsyncMethodDataSourceDrivenTests.AsyncDataMethodWithArgs()),
Arguments = new object[] { 5 },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromEnumerable(static () =>
{
var arguments = new object[] { 5 };
return global::TUnit.TestProject.AsyncMethodDataSourceDrivenTests.AsyncDataMethodWithArgs(((int)arguments[0]));
}),
},
},
returnType: typeof(global::System.Threading.Tasks.Task),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { 5 },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down Expand Up @@ -280,6 +281,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 5, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand All @@ -289,6 +291,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 7, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { 5 },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down Expand Up @@ -280,6 +281,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 5, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand All @@ -289,6 +291,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 7, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { 5 },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down Expand Up @@ -280,6 +281,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 5, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand All @@ -289,6 +291,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 7, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { 5 },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down Expand Up @@ -280,6 +281,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 5, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand All @@ -289,6 +291,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 7, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ public void Own()
[Test]
public async Task AttributeWriter_FallsBackToTypedConstants_ForAttributesFromReferencedCompilation()
{
var baseCompilation = CreateCompilation("CrossCompilationBase", BaseSource);
var derivedCompilation = CreateCompilation("CrossCompilationDerived", DerivedSource, baseCompilation.ToMetadataReference());
var references = CreateIsolatedReferences();
var baseCompilation = CreateCompilation("CrossCompilationBase", BaseSource, references);
var derivedCompilation = CreateCompilation("CrossCompilationDerived", DerivedSource, references, baseCompilation.ToMetadataReference());

var derivedType = derivedCompilation.GetTypeByMetadataName("DerivedTests")!;
var baseAttributes = derivedType.BaseType!.GetAttributes();
Expand All @@ -99,10 +100,12 @@ public async Task AttributeWriter_FallsBackToTypedConstants_ForAttributesFromRef
[Test]
public async Task TestMetadataGenerator_DoesNotThrow_ForAttributesFromReferencedCompilation()
{
var baseCompilation = CreateCompilation("CrossCompilationBase", BaseSource);
var references = CreateIsolatedReferences();
var baseCompilation = CreateCompilation("CrossCompilationBase", BaseSource, references);
var derivedCompilation = CreateCompilation(
"CrossCompilationDerived",
DerivedSource,
references,
baseCompilation.ToMetadataReference())
.AddSyntaxTrees(
Parse(
Expand Down Expand Up @@ -170,17 +173,29 @@ public class UnconditionalSuppressMessageAttribute : Attribute;
await Assert.That(compilationErrors).IsEmpty();
}

private static CSharpCompilation CreateCompilation(string assemblyName, string source, params MetadataReference[] additionalReferences)
private static CSharpCompilation CreateCompilation(string assemblyName, string source, MetadataReference[] references, params MetadataReference[] additionalReferences)
{
return CSharpCompilation.Create(
assemblyName,
[Parse(source)],
[..ReferencesHelper.References, ..additionalReferences],
[..references, ..additionalReferences],
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
}

private static readonly CSharpParseOptions ParseOptions = new(LanguageVersion.Preview);

// Roslyn caches assembly symbols on each reference's metadata. When the shared
// ReferencesHelper.References are also bound by tests running in parallel, the derived
// compilation can pick different cached symbols than the base compilation did. It then sees
// the base project through retargeting symbols, whose attributes have no syntax reference, and
// the IDE-shape precondition fails. Fresh references per test avoid that race.
private static MetadataReference[] CreateIsolatedReferences()
{
return ReferencesHelper.References
.Select(reference => (MetadataReference) MetadataReference.CreateFromFile(reference.FilePath!))
.ToArray();
}

private static SyntaxTree Parse(string source)
{
return CSharpSyntaxTree.ParseText(source, ParseOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { 5 },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down Expand Up @@ -280,6 +281,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
{
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 5, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand All @@ -289,6 +291,7 @@ internal static class TUnit_TestProject_MethodDataSourceDrivenTests__TestSource
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
{
Arguments = new object[] { "Hello World!", 7, true },
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromValue(static () => global::TUnit.TestProject.MethodDataSourceDrivenTests.SomeMethod()),
},
new global::TUnit.Core.MethodDataSourceAttribute("SomeMethod")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.SourceGenerators.Testing" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
<!-- Host the generator (built against Roslyn 4.7) on the Roslyn that a current SDK loads, the way
the Roslyn414 variant runs in a real build. Roslyn 4.7 cannot bind fixtures that use newer
syntax, such as collection expressions in attribute arguments, so it would snapshot what the
generator does with broken input. -->
<PackageReference Include="Microsoft.CodeAnalysis.Common" VersionOverride="4.14.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" VersionOverride="4.14.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" VersionOverride="4.14.0" />
<PackageReference Include="Microsoft.Testing.Extensions.HangDump" />
<PackageReference Include="NuGet.Protocol" />
<PackageReference Include="Sourcy.DotNet">
Expand Down
49 changes: 45 additions & 4 deletions tests/TUnit.Core.SourceGenerator.Tests/TestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public Task RunTest(string inputFile, RunTestOptions runTestOptions, Func<string

public class TestsBase<TGenerator> where TGenerator : IIncrementalGenerator, new()
{
// TUnit.TestProject builds with LangVersion=preview (Directory.Build.props). The referenced
// Roslyn's default parse options reject newer syntax such as collection expressions, so the
// generator would see broken trees and the snapshots would not match a real build.
private static readonly CSharpParseOptions ParseOptions = new(LanguageVersion.Preview);

public Task RunTest(string inputFile, Func<string[], Task> assertions)
{
return RunTest(inputFile, new RunTestOptions(), assertions);
Expand Down Expand Up @@ -87,7 +92,12 @@ public class ExcludeFromCodeCoverageAttribute : Attribute;
"""
namespace System.Diagnostics.CodeAnalysis;

public class UnconditionalSuppressMessageAttribute : Attribute;
public class UnconditionalSuppressMessageAttribute(string category, string checkId) : Attribute
{
public string Category { get; } = category;
public string CheckId { get; } = checkId;
public string? Justification { get; set; }
}
""",
#if NET
..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => File.ReadAllTextAsync(x))),
Expand All @@ -101,7 +111,8 @@ ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => File.ReadAllText
var generator = new TGenerator();

// Source generators should be tested using 'GeneratorDriver'.
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
// Generated trees must share the input's language version, or Roslyn rejects the compilation.
GeneratorDriver driver = CSharpGeneratorDriver.Create([generator.AsSourceGenerator()], parseOptions: ParseOptions);

if (runTestOptions.BuildProperties != null)
{
Expand All @@ -116,12 +127,12 @@ ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => File.ReadAllText
var compilation = CSharpCompilation.Create(
GetType().Name,
[
CSharpSyntaxTree.ParseText(source)
CSharpSyntaxTree.ParseText(source, ParseOptions)
],
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
)
.WithReferences(ReferencesHelper.References)
.AddSyntaxTrees(additionalSources.Select(x => CSharpSyntaxTree.ParseText(x)));
.AddSyntaxTrees(additionalSources.Select(x => CSharpSyntaxTree.ParseText(x, ParseOptions)));

foreach (var additionalPackage in runTestOptions.AdditionalPackages)
{
Expand All @@ -130,6 +141,8 @@ ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => File.ReadAllText
compilation = compilation.AddReferences(downloaded);
}

ThrowIfInputCannotBeParsed(compilation);

// Run generators. Don't forget to use the new compilation rather than the previous one.
driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out var diagnostics);

Expand Down Expand Up @@ -196,6 +209,34 @@ Have you added required references and additional files?
await verifyTask;
}

// Fixtures are not required to compile here: some reference packages or types that the harness
// does not load. Syntax errors and the binding errors that a too-old compiler reports for valid
// newer syntax are different, because the generator then sees a tree a real build never produces
// and the snapshot silently records the wrong output (#7162).
private static readonly HashSet<string> UnsupportedSyntaxDiagnosticIds =
[
"CS0182", // attribute argument must be constant: collection expressions in attributes on Roslyn < 4.8
"CS0225", // params parameter must be an array: params collections on Roslyn < 4.12
"CS8652", // feature is in preview
];

private static void ThrowIfInputCannotBeParsed(Compilation compilation)
{
var errors = compilation.SyntaxTrees
.SelectMany(tree => tree.GetDiagnostics())
.Concat(compilation.GetDeclarationDiagnostics().Where(d => UnsupportedSyntaxDiagnosticIds.Contains(d.Id)))
.Where(d => d.Severity == DiagnosticSeverity.Error)
.Select(d => d.ToString())
.Distinct()
.ToArray();

if (errors.Length > 0)
{
throw new InvalidOperationException(
$"The test input does not parse as a real build would:{Environment.NewLine}{string.Join(Environment.NewLine, errors)}");
}
}

private static bool IsError(Diagnostic x)
{
if (x.Severity == DiagnosticSeverity.Error)
Expand Down
Loading