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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<GlobalPackageReference Include="Roslynator.CodeFixes" Version="4.15.0" />
<GlobalPackageReference Include="Roslynator.Refactorings" Version="4.15.0" />
<GlobalPackageReference Include="FakeItEasy.Analyzer.CSharp" Version="6.1.1" />
<PackageVersion Include="BeaKona.AutoInterfaceGenerator" Version="1.0.46" />
<PackageVersion Include="Verify.XunitV3" Version="31.20.0" />
<PackageVersion Include="xunit.analyzers" Version="1.27.0" />
<GlobalPackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
<IsMagicProject>true</IsMagicProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BeaKona.AutoInterfaceGenerator" PrivateAssets="all" />
<PackageReference Include="TUnit.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Testing\Rocket.Surgery.Extensions.Testing.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="template.scriban" />
</ItemGroup>
</Project>
411 changes: 411 additions & 0 deletions src/Testing.TUnit/TestRecord.Interop.cs

Large diffs are not rendered by default.

41 changes: 20 additions & 21 deletions src/Testing.TUnit/TestRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,32 @@
/// <remarks>
/// The xunit test context
/// </remarks>
/// <param name="context"></param>
/// <param name="logEventLevel"></param>
/// <param name="outputTemplate"></param>
/// <param name="configureLogger"></param>
[PublicAPI]
public abstract class TUnitTestRecord<TContext>(
TestContext context,
LogEventLevel logEventLevel = LogEventLevel.Verbose,
string? outputTemplate = null,
Action<TContext, LoggerConfiguration>? configureLogger = null
) : RocketSurgeryTestContext<TContext>(
configureLogger,
logEventLevel,
outputTemplate
)
where TContext : RocketSurgeryTestContext<TContext>, ILoggingTestContext
public abstract partial class TestRecord<TContext>

Check notice on line 16 in src/Testing.TUnit/TestRecord.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Testing.TUnit/TestRecord.cs#L16

'partial' is gratuitous in this context.
(LogEventLevel logEventLevel = LogEventLevel.Verbose, string? outputTemplate = null, Action<TContext, LoggerConfiguration>? configureLogger = null)
: RocketSurgeryTestContext<TContext>(configureLogger, logEventLevel, outputTemplate)
where TContext : RocketSurgeryTestContext<TContext>
{
private readonly TestContext _context = context;
/// <summary>
/// Represents the current test context for xUnit tests.
Comment on lines 10 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚪ LOW RISK

Nitpick: The documentation here refers to 'xunit test context', but this class is part of the TUnit implementation. Update all XML summaries and remarks to correctly reflect TUnit.

/// </summary>
/// <remarks>
/// Provides access to the active test context during the execution of a test.
/// This property is typically used to retrieve contextual information or perform
/// test-specific logging operations.
/// </remarks>
public TestContext TestContext { get; } = TestContext.Current!;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 HIGH RISK

The TestContext property relies on TestContext.Current, which may be null if accessed outside of a TUnit test execution. Explicitly checking for null and throwing a clear exception is recommended to avoid confusing NullReferenceExceptions.

Suggested change
public TestContext TestContext { get; } = TestContext.Current!;
public TestContext TestContext { get; } = TestContext.Current ?? throw new InvalidOperationException("TUnit TestContext is not available in the current execution context.");


private readonly LogEventLevel _logEventLevel = logEventLevel;

/// <inheritdoc />
protected override void ConfigureLogger(TContext context, LoggerConfiguration loggerConfiguration) => loggerConfiguration
.MinimumLevel.Is(_logEventLevel)
.WriteTo.Sink(new Sink(_context));
protected override void ConfigureLogger(TContext context, LoggerConfiguration loggerConfiguration) =>
loggerConfiguration

Check failure on line 35 in src/Testing.TUnit/TestRecord.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Testing.TUnit/TestRecord.cs#L35

Potential null dereference detected.
.MinimumLevel.Is(_logEventLevel)
.WriteTo.Sink(new Sink(TestContext));
}

/// <summary>
Expand All @@ -41,22 +43,19 @@
[PublicAPI]
public class TestRecord
(
TestContext context,
LogEventLevel logEventLevel = LogEventLevel.Verbose,
string? outputTemplate = null,
Action<TestRecord, LoggerConfiguration>? configureLogger = null)
: TUnitTestRecord<TestRecord>(context, logEventLevel, outputTemplate, configureLogger)
: TestRecord<TestRecord>(logEventLevel, outputTemplate, configureLogger)
{
/// <summary>
/// Create the test record
/// </summary>
/// <param name="testContext"></param>
/// <param name="logEventLevel"></param>
/// <param name="outputTemplate"></param>
/// <returns></returns>
public static TestRecord Create(
TestContext testContext,
LogEventLevel logEventLevel = LogEventLevel.Verbose,
string? outputTemplate = null
) => new(testContext, logEventLevel, outputTemplate);
) => new(logEventLevel, outputTemplate);

Check failure on line 60 in src/Testing.TUnit/TestRecord.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Testing.TUnit/TestRecord.cs#L60

Potential null dereference detected.
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
<IsMagicProject>true</IsMagicProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BeaKona.AutoInterfaceGenerator" PrivateAssets="all" />
<PackageReference Include="xunit.v3.extensibility.core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Testing\Rocket.Surgery.Extensions.Testing.csproj" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="template.scriban" />
</ItemGroup>
</Project>
100 changes: 2 additions & 98 deletions src/Testing.XUnit3/XUnitTestContext.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Serilog;
using Serilog.Events;
using Xunit;
using Xunit.Sdk;

namespace Rocket.Surgery.Extensions.Testing;

Expand All @@ -16,7 +15,7 @@
/// <param name="outputTemplate"></param>
/// <param name="configureLogger"></param>
[PublicAPI]
public abstract class XUnitTestContext<TContext>(
public abstract partial class XUnitTestContext<TContext>(

Check notice on line 18 in src/Testing.XUnit3/XUnitTestContext.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Testing.XUnit3/XUnitTestContext.cs#L18

'partial' is gratuitous in this context.
ITestContextAccessor testContextAccessor,
LogEventLevel logEventLevel = LogEventLevel.Verbose,
string? outputTemplate = null,
Expand All @@ -25,109 +24,14 @@
where TContext : RocketSurgeryTestContext<TContext>, ILoggingTestContext, ITestContext
{
private readonly LogEventLevel _logEventLevel = logEventLevel;
[BeaKona.AutoInterface(TemplateFileName = "template.scriban")]
private readonly ITestContext _testContext = testContextAccessor.Current;
private ConcurrentDictionary<string, object?> _keyValueStorage;

/// <inheritdoc />
protected override void ConfigureLogger(TContext context, LoggerConfiguration loggerConfiguration) => loggerConfiguration
.MinimumLevel.Is(_logEventLevel)
.WriteTo.Sink(new XUnitSink(context));

/// <inheritdoc />
public void AddAttachment(string name, string value) => _testContext.AddAttachment(name, value);

/// <inheritdoc />
public void AddAttachment(string name, string value, bool replaceExistingValue) => _testContext.AddAttachment(name, value, replaceExistingValue);

/// <inheritdoc />
public void AddAttachment(string name, byte[] value, string mediaType = "application/octet-stream") => _testContext.AddAttachment(name, value, mediaType);

/// <inheritdoc />
public void AddAttachment(string name, byte[] value, bool replaceExistingValue, string mediaType = "application/octet-stream") => throw new NotImplementedException();

/// <inheritdoc />
public void AddWarning(string message) => _testContext.AddWarning(message);

/// <inheritdoc />
public void CancelCurrentTest() => _testContext.CancelCurrentTest();

/// <inheritdoc />
public ValueTask<object?> GetFixture(Type fixtureType) => _testContext.GetFixture(fixtureType);

/// <inheritdoc />
public void SendDiagnosticMessage(string message) => _testContext.SendDiagnosticMessage(message);

/// <inheritdoc />
public void SendDiagnosticMessage(string format, object? arg0) => _testContext.SendDiagnosticMessage(format, arg0);

/// <inheritdoc />
public void SendDiagnosticMessage(string format, object? arg0, object? arg1) => _testContext.SendDiagnosticMessage(format, arg0, arg1);

/// <inheritdoc />
public void SendDiagnosticMessage(string format, object? arg0, object? arg1, object? arg2) => _testContext.SendDiagnosticMessage(format, arg0, arg1, arg2);

/// <inheritdoc />
public void SendDiagnosticMessage(string format, params object?[] args) => _testContext.SendDiagnosticMessage(format, args);

/// <inheritdoc />
public IReadOnlyDictionary<string, TestAttachment>? Attachments => _testContext.Attachments;

/// <inheritdoc />
public CancellationToken CancellationToken => _testContext.CancellationToken;

/// <inheritdoc />
ConcurrentDictionary<string, object?> ITestContext.KeyValueStorage => _keyValueStorage;

/// <inheritdoc />
public TestPipelineStage PipelineStage => _testContext.PipelineStage;

/// <inheritdoc />
public ITest? Test => _testContext.Test;

/// <inheritdoc />
public ITestAssembly? TestAssembly => _testContext.TestAssembly;

/// <inheritdoc />
public TestEngineStatus? TestAssemblyStatus => _testContext.TestAssemblyStatus;

/// <inheritdoc />
public ITestCase? TestCase => _testContext.TestCase;

/// <inheritdoc />
public TestEngineStatus? TestCaseStatus => _testContext.TestCaseStatus;

/// <inheritdoc />
public ITestClass? TestClass => _testContext.TestClass;

/// <inheritdoc />
public object? TestClassInstance => _testContext.TestClassInstance;

/// <inheritdoc />
public TestEngineStatus? TestClassStatus => _testContext.TestClassStatus;

/// <inheritdoc />
public ITestCollection? TestCollection => _testContext.TestCollection;

/// <inheritdoc />
public TestEngineStatus? TestCollectionStatus => _testContext.TestCollectionStatus;

/// <inheritdoc />
public ITestMethod? TestMethod => _testContext.TestMethod;

/// <inheritdoc />
public TestEngineStatus? TestMethodStatus => _testContext.TestMethodStatus;

/// <inheritdoc />
public ITestOutputHelper? TestOutputHelper => _testContext.TestOutputHelper;

/// <inheritdoc />
public TestResultState? TestState => _testContext.TestState;

/// <inheritdoc />
public TestEngineStatus? TestStatus => _testContext.TestStatus;

/// <inheritdoc />
public IReadOnlyList<string>? Warnings => _testContext.Warnings;
}

/// <summary>
Expand Down
74 changes: 74 additions & 0 deletions src/Testing.XUnit3/template.scriban
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{{~for method in methods~}}
/// <inheritdoc />
public {{method.is_async?"async ":""}}{{method.return_type}} {{method.name}}({{method.arguments_definition}})
{
{{~if method.is_async~}}
{{~for reference in references~}}
var temp{{for.index}} = (({{interface}})this.{{reference}}).{{method.name}}({{method.call_arguments}}).ConfigureAwait(false);
{{~end~}}
{{~for reference in references~}}
{{for.last && method.return_expected ? "return " : ""}}await temp{{for.index}};
{{~end~}}
{{~else~}}
{{~for reference in references~}}
{{for.last && method.return_expected ? "return " : ""}}(({{interface}})this.{{reference}}).{{method.name}}({{method.call_arguments}});
{{~end~}}
{{~end~}}
}

{{~end~}}
{{~for property in properties~}}
/// <inheritdoc />
public {{property.type}} {{property.name}}
{
{{~if property.have_getter~}}
get
{
return (({{interface}})this.{{references[0]}}).{{property.name}};
}
{{~end~}}
{{~if property.have_setter~}}
set

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 HIGH RISK

The property setter implementation is empty and ignores the 'value' parameter. It should delegate the assignment to the underlying reference to prevent silent failures when setting properties on the context.

{
}
{{~end~}}
}

{{~end~}}
{{~for indexer in indexers~}}
/// <inheritdoc />
public {{indexer.type}} {{indexer.name}}[{{indexer.parameters_definition}}]
{
{{~if indexer.have_getter~}}
get
{
return (({{interface}})this.{{references[0]}})[{{indexer.call_parameters}}];
}
{{~end~}}
{{~if indexer.have_setter~}}
set
{
Comment on lines +49 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 HIGH RISK

The indexer setter implementation is empty. Any attempt to set a value via an indexer on the generated classes will be silently ignored. This should delegate to the underlying reference.

}
{{~end~}}
}

{{~end~}}
{{~for event in events~}}
/// <inheritdoc />
public event {{event.type}} {{event.name}}
{
add
{
{{~for reference in references~}}
(({{interface}})this.{{reference}}).{{event.name}} += value;
{{~end~}}
}
remove
{
{{~for reference in references~}}
(({{interface}})this.{{reference}}).{{event.name}} -= value;
{{~end~}}
}
}

{{~end~}}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Rocket.Surgery.Extensions.Testing.TUnit.Tests.Generators;

public class GeneratorContextTests() : LoggerTest<TestRecord>(TestRecord.Create(global::TUnit.Core.TestContext.Current!))
public class GeneratorContextTests() : LoggerTest<TestRecord>(TestRecord.Create())
{
[Test]
public async Task Should_Build_A_Context()
Expand Down
2 changes: 1 addition & 1 deletion test/Testing.TUnit.Tests/LoggerTestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Rocket.Surgery.Extensions.Testing.TUnit.Tests;

public class LoggerTestTests() : LoggerTest<TestRecord>(TestRecord.Create(global::TUnit.Core.TestContext.Current!))
public class LoggerTestTests() : LoggerTest<TestRecord>(TestRecord.Create())
{
[Test]
public Task Should_Create_A_Log_Stream()
Expand Down
Loading