Skip to content
Draft
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
@@ -0,0 +1,260 @@
namespace NServiceBus.Core.Analyzer.Tests.EnvironmentSupport;

using System.Collections.Generic;
using System.Threading.Tasks;
using Helpers;
using NUnit.Framework;

[TestFixture]
public class NotSupportedInEnvironmentAnalyzerTests : AnalyzerTestFixture<NotSupportedInEnvironmentAnalyzer>
{
[Test]
public Task NoEnvironmentProperty_NoDiagnostics()
{
var source =
"""
using NServiceBus;
public class TestApi
{
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Not supported in Azure Functions")]
public static void ForbiddenMethod() { }
}
public class TestClass
{
public void TestMethod()
{
TestApi.ForbiddenMethod();
}
}
""";

return Assert(source);
}

[Test]
public Task MethodInvocationFlagged()
{
var source =
"""
using NServiceBus;
public class TestApi
{
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Method not supported")]
public static void ForbiddenMethod() { }
}
public class TestClass
{
public void TestMethod()
{
[|TestApi.ForbiddenMethod()|];
}
}
""";

var options = new Dictionary<string, string>
{
{ "build_property.NServiceBusEnvironment", "AzureFunctionsIsolated" }
};

return Assert(DiagnosticIds.NotSupportedInEnvironment, source, options);
}

[Test]
public Task TypeLevelAttribute_TriggersDiagnostic()
{
var source =
"""
using NServiceBus;
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Type not supported")]
public class ForbiddenType
{
public void Method() { }
}
public class TestClass
{
public void TestMethod()
{
var instance = [|new ForbiddenType()|];
}
}
""";

var options = new Dictionary<string, string>
{
{ "build_property.NServiceBusEnvironment", "AzureFunctionsIsolated" }
};

return Assert(DiagnosticIds.NotSupportedInEnvironment, source, options);
}

[Test]
public Task MethodLevelOverridesTypeLevelReason()
{
var source =
"""
using NServiceBus;
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Type level reason")]
public class TestApi
{
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Method level reason")]
public static void ForbiddenMethod() { }
}
public class TestUsage
{
public void TestMethod()
{
[|TestApi.ForbiddenMethod()|];
}
}
""";

var options = new Dictionary<string, string>
{
{ "build_property.NServiceBusEnvironment", "AzureFunctionsIsolated" }
};

return Assert(DiagnosticIds.NotSupportedInEnvironment, source, options);
}

[Test]
public Task ObjectCreationFlagged()
{
var source =
"""
using NServiceBus;
public class ForbiddenClass
{
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Constructor not supported")]
public ForbiddenClass() { }
}
public class TestClass
{
public void TestMethod()
{
var instance = [|new ForbiddenClass()|];
}
}
""";

var options = new Dictionary<string, string>
{
{ "build_property.NServiceBusEnvironment", "AzureFunctionsIsolated" }
};

return Assert(DiagnosticIds.NotSupportedInEnvironment, source, options);
}

[Test]
public Task PropertyReferenceFlagged()
{
var source =
"""
using NServiceBus;
public class TestApi
{
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Property not supported")]
public static string ForbiddenProperty { get; set; }
}
public class TestClass
{
public void TestMethod()
{
var value = [|TestApi.ForbiddenProperty|];
}
}
""";

var options = new Dictionary<string, string>
{
{ "build_property.NServiceBusEnvironment", "AzureFunctionsIsolated" }
};

return Assert(DiagnosticIds.NotSupportedInEnvironment, source, options);
}

[Test]
public Task DifferentEnvironment_NoDiagnostic()
{
var source =
"""
using NServiceBus;
public class TestApi
{
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Not supported")]
public static void ForbiddenMethod() { }
}
public class TestClass
{
public void TestMethod()
{
TestApi.ForbiddenMethod();
}
}
""";

var options = new Dictionary<string, string>
{
{ "build_property.NServiceBusEnvironment", "SomeOtherEnvironment" }
};

return Assert(source, options);
}

[Test]
public Task MultipleAttributes_MatchingEnvironmentTriggersDiagnostic()
{
var source =
"""
using NServiceBus;
public class TestApi
{
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Not in Azure Functions")]
[NotSupportedInEnvironment("LambdaEnvironment", "Not in Lambda")]
Comment on lines +211 to +212
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be too fancy to support wildcards? Ie to have ServerLess here and then have lambda define ServerLess.Lambda ?

public static void ForbiddenMethod() { }
}
public class TestClass
{
public void TestMethod()
{
[|TestApi.ForbiddenMethod()|];
}
}
""";

var options = new Dictionary<string, string>
{
{ "build_property.NServiceBusEnvironment", "AzureFunctionsIsolated" }
};

return Assert(DiagnosticIds.NotSupportedInEnvironment, source, options);
}

[Test]
public Task MultipleAttributes_NonMatchingEnvironmentNoDiagnostic()
{
var source =
"""
using NServiceBus;
public class TestApi
{
[NotSupportedInEnvironment("AzureFunctionsIsolated", "Not in Azure Functions")]
[NotSupportedInEnvironment("LambdaEnvironment", "Not in Lambda")]
public static void ForbiddenMethod() { }
}
public class TestClass
{
public void TestMethod()
{
TestApi.ForbiddenMethod();
}
}
""";

var options = new Dictionary<string, string>
{
{ "build_property.NServiceBusEnvironment", "ContainerEnvironment" }
};

return Assert(source, options);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace NServiceBus.Core.Analyzer.Tests.Helpers;
namespace NServiceBus.Core.Analyzer.Tests.Helpers;

using System;
using System.Collections.Generic;
Expand All @@ -21,12 +21,18 @@
protected virtual LanguageVersion AnalyzerLanguageVersion => LanguageVersion.CSharp14;

protected Task Assert(string markupCode, CancellationToken cancellationToken = default) =>
Assert([], markupCode, [], true, cancellationToken);
Assert([], markupCode, [], true, null, cancellationToken);

protected Task Assert(string markupCode, Dictionary<string, string> globalOptions, CancellationToken cancellationToken = default) =>
Assert([], markupCode, [], true, globalOptions, cancellationToken);

protected Task Assert(string expectedDiagnosticId, string markupCode, CancellationToken cancellationToken = default) =>
Assert([expectedDiagnosticId], markupCode, [], true, cancellationToken);
Assert([expectedDiagnosticId], markupCode, [], true, null, cancellationToken);

protected Task Assert(string expectedDiagnosticId, string markupCode, Dictionary<string, string> globalOptions, CancellationToken cancellationToken = default) =>
Assert([expectedDiagnosticId], markupCode, [], true, globalOptions, cancellationToken);

protected async Task Assert(string[] expectedDiagnosticIds, string markupCode, string[] ignoreDiagnosticIds = null, bool mustCompile = true, CancellationToken cancellationToken = default)
protected async Task Assert(string[] expectedDiagnosticIds, string markupCode, string[] ignoreDiagnosticIds = null, bool mustCompile = true, Dictionary<string, string> globalOptions = null, CancellationToken cancellationToken = default)
{
ignoreDiagnosticIds ??= [];

Expand All @@ -44,7 +50,7 @@ protected async Task Assert(string[] expectedDiagnosticIds, string markupCode, s
var compilation = await project.GetCompilationAsync(cancellationToken);
compilation.Compile(mustCompile);

var analyzerDiagnostics = (await compilation.GetAnalyzerDiagnostics(new TAnalyzer(), cancellationToken))
var analyzerDiagnostics = (await compilation.GetAnalyzerDiagnostics(new TAnalyzer(), globalOptions, cancellationToken))
.Where(d => !ignoreDiagnosticIds.Contains(d.Id))
.ToList();
WriteAnalyzerDiagnostics(analyzerDiagnostics);
Expand Down Expand Up @@ -110,7 +116,7 @@ static AnalyzerTestFixture() => ProjectReferences =
MetadataReference.CreateFromFile(Assembly.Load("System.Console").Location),
MetadataReference.CreateFromFile(Assembly.Load("System.Private.CoreLib").Location),
MetadataReference.CreateFromFile(typeof(EndpointConfiguration).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(IUniformSession).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(AnalyzerTestFixture<>).GetTypeInfo().Assembly.Location), // This allows loading stubs
MetadataReference.CreateFromFile(typeof(IMessage).GetTypeInfo().Assembly.Location)
];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
namespace NServiceBus.Core.Analyzer.Tests.Helpers;
namespace NServiceBus.Core.Analyzer.Tests.Helpers;

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
Expand All @@ -15,36 +14,40 @@ static class CompilationExtensions
{
public static void Compile(this Compilation compilation, bool throwOnFailure = true)
{
using (var peStream = new MemoryStream())
using var peStream = new MemoryStream();
var emitResult = compilation.Emit(peStream);

if (emitResult.Success)
{
return;
}

if (throwOnFailure)
{
var emitResult = compilation.Emit(peStream);

if (!emitResult.Success)
{
if (throwOnFailure)
{
throw new Exception("Compilation failed.");
}
else
{
Debug.WriteLine("Compilation failed.");
}
}
throw new Exception("Compilation failed.");
}

Debug.WriteLine("Compilation failed.");
}

public static async Task<IEnumerable<Diagnostic>> GetAnalyzerDiagnostics(this Compilation compilation, DiagnosticAnalyzer analyzer, CancellationToken cancellationToken = default)
=> await compilation.GetAnalyzerDiagnostics(analyzer, null, cancellationToken);

public static async Task<IEnumerable<Diagnostic>> GetAnalyzerDiagnostics(this Compilation compilation, DiagnosticAnalyzer analyzer, Dictionary<string, string> globalOptions, CancellationToken cancellationToken = default)
{
var exceptions = new List<Exception>();

var optionsProvider = new TestAnalyzerConfigOptionsProvider(globalOptions);
var analyzerOptions = new AnalyzerOptions([], optionsProvider);

var analysisOptions = new CompilationWithAnalyzersOptions(
new AnalyzerOptions(ImmutableArray<AdditionalText>.Empty),
analyzerOptions,
(exception, _, __) => exceptions.Add(exception),
concurrentAnalysis: false,
logAnalyzerExecutionTime: false);

var diagnostics = await compilation
.WithAnalyzers(ImmutableArray.Create(analyzer), analysisOptions)
.WithAnalyzers([analyzer], analysisOptions)
.GetAnalyzerDiagnosticsAsync(cancellationToken);

if (exceptions.Count != 0)
Expand All @@ -56,4 +59,20 @@ public static async Task<IEnumerable<Diagnostic>> GetAnalyzerDiagnostics(this Co
.OrderBy(diagnostic => diagnostic.Location.SourceSpan)
.ThenBy(diagnostic => diagnostic.Id);
}
}

class TestAnalyzerConfigOptionsProvider(Dictionary<string, string> globalOptions) : AnalyzerConfigOptionsProvider
{
readonly Dictionary<string, string> globalOptions = globalOptions ?? [];

public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => new TestAnalyzerConfigOptions(globalOptions);
public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => new TestAnalyzerConfigOptions(globalOptions);
public override AnalyzerConfigOptions GlobalOptions => new TestAnalyzerConfigOptions(globalOptions);
}

class TestAnalyzerConfigOptions(Dictionary<string, string> options) : AnalyzerConfigOptions
{
readonly Dictionary<string, string> options = options ?? [];

public override bool TryGetValue(string key, out string value) => options.TryGetValue(key, out value!);
}
}
Loading