Skip to content

Commit

Permalink
Support FieldReference
Browse files Browse the repository at this point in the history
  • Loading branch information
justindbaur committed Sep 29, 2024
1 parent f84bf08 commit 58066b5
Show file tree
Hide file tree
Showing 21 changed files with 267 additions and 34 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,4 @@ dotnet_style_prefer_collection_expression = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion

dotnet_diagnostic.CA1811.severity = warning
dotnet_diagnostic.CA1508.severity = warning
8 changes: 4 additions & 4 deletions example/Example.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
41 changes: 41 additions & 0 deletions example/FieldTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Pretender;

namespace Example.Tests
{
public static class FieldConstants
{
public const string MyConstant = "something";
}

public interface IFieldTest
{
int MyMethod(string myArg);
}


public class FieldTests
{
[Theory]
[InlineData("something", true)]
[InlineData("something_else", false)]
public void ReferenceFieldConstant(string actualArg, bool shouldMatch)
{
var pretend = Pretend.That<IFieldTest>();

pretend
.Setup(i => i.MyMethod(FieldConstants.MyConstant))
.Returns(1);

var test = pretend.Create();

if (false)
{
throw new Exception("something");
}

var result = test.MyMethod(actualArg);

Assert.Equal(shouldMatch, result == 1);
}
}
}
2 changes: 1 addition & 1 deletion perf/Comparison/Comparison.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.8" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
</ItemGroup>
Expand Down
26 changes: 26 additions & 0 deletions src/Pretender.SourceGenerator/Emitter/CapturedArgumentEmitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Pretender.SourceGenerator.SetupArguments;
using Pretender.SourceGenerator.Writing;

namespace Pretender.SourceGenerator.Emitter
{
internal class CapturedArgumentEmitter : SetupArgumentEmitter
{
public CapturedArgumentEmitter(SetupArgumentSpec argumentSpec) : base(argumentSpec)
{

}

public override bool NeedsCapturer => true;

public override void EmitArgumentMatcher(IndentedTextWriter writer, CancellationToken cancellationToken)
{
EmitArgumentAccessor(writer);
writer.WriteLine($"var {Parameter.Name}_capturedArg = ({Parameter.Type.ToUnknownTypeString()})capturedArguments[{Parameter.Ordinal}];");
writer.WriteLine($"if ({Parameter.Name}_arg != {Parameter.Name}_capturedArg)");
using (writer.WriteBlock())
{
writer.WriteLine("return false;");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

namespace Pretender.SourceGenerator.Emitter
{
internal class CaptureInvocationArgumentEmitter : SetupArgumentEmitter
internal class CapturedMatcherInvocationEmitter : SetupArgumentEmitter
{
public CaptureInvocationArgumentEmitter(SetupArgumentSpec argumentSpec) : base(argumentSpec)
public CapturedMatcherInvocationEmitter(SetupArgumentSpec argumentSpec) : base(argumentSpec)
{
}

public override bool NeedsCapturer => true;
public override bool NeedsMatcher => true;

public override void EmitArgumentMatcher(IndentedTextWriter writer, CancellationToken cancellationToken)
{
Expand Down
14 changes: 11 additions & 3 deletions src/Pretender.SourceGenerator/Emitter/SetupActionEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,24 @@ public void Emit(IndentedTextWriter writer, CancellationToken cancellationToken)

writer.WriteLine();
writer.WriteLine("var listener = MatcherListener.StartListening();");
writer.WriteLine("setup.Method.Invoke(setup.Target, [fake]);");
writer.WriteLine("listener.Dispose();");
writer.WriteLine("try");
using (writer.WriteBlock())
{
writer.WriteLine("setup.Method.Invoke(setup.Target, [fake]);");
}
writer.WriteLine("finally");
using (writer.WriteBlock())
{
writer.WriteLine("listener.Dispose();");
}
writer.WriteLine();

writer.WriteLine("var capturedArguments = singleUseCallHandler.Arguments;");
writer.WriteLine();
}

int index = 0;
foreach (var a in _setupArgumentEmitters.Where(a => a.NeedsCapturer))
foreach (var a in _setupArgumentEmitters.Where(a => a.NeedsMatcher))
{
writer.WriteLine($"var {a.Parameter.Name}_capturedMatcher = listener.Matchers[{index}];");
index++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ protected SetupArgumentEmitter(SetupArgumentSpec argumentSpec)
public virtual bool EmitsMatcher => true;
public ImmutableArray<ILocalSymbol> NeededLocals { get; }
public virtual bool NeedsCapturer { get; }
public virtual bool NeedsMatcher { get; }

public abstract void EmitArgumentMatcher(IndentedTextWriter writer, CancellationToken cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0-beta1.23420.2">
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0-beta1.24318.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0-1.final" />
<PackageReference Include="PolySharp" Version="1.14.0">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0-2.final" />
<PackageReference Include="PolySharp" Version="1.14.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,17 @@ public SetupArgumentParser(SetupArgumentSpec setupArgumentSpec)
OperationKind.Literal => (new LiteralArgumentEmitter((ILiteralOperation)argumentValue, _setupArgumentSpec), null),
OperationKind.Invocation => ParseInvocation((IInvocationOperation)argumentValue, cancellationToken),
OperationKind.LocalReference => (new LocalReferenceArgumentEmitter((ILocalReferenceOperation)argumentValue, _setupArgumentSpec), null),
OperationKind.FieldReference => ParseFieldReference((IFieldReferenceOperation)argumentValue, cancellationToken),
_ => throw new NotImplementedException($"{argumentValue.Kind} is not a supported operation in setup arguments."),
};
}

private (SetupArgumentEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics) ParseFieldReference(IFieldReferenceOperation fieldReference, CancellationToken cancellationToken)
{
// For now fields references are just captured, we can change this to aggressively rewrite the callsite if there is desire
return (new CapturedArgumentEmitter(_setupArgumentSpec), null);
}

private (SetupArgumentEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics) ParseInvocation(IInvocationOperation invocation, CancellationToken cancellationToken)
{
if (TryGetMatcherAttributeType(invocation, out var matcherType, cancellationToken))
Expand All @@ -62,7 +69,7 @@ public SetupArgumentParser(SetupArgumentSpec setupArgumentSpec)
if (invocation.Arguments.Length > 0)
{
// TODO: Some of these might be safe to rewrite
return (new CaptureInvocationArgumentEmitter(_setupArgumentSpec), null);
return (new CapturedMatcherInvocationEmitter(_setupArgumentSpec), null);
}

return (new MatcherArgumentEmitter(matcherType, _setupArgumentSpec), null);
Expand Down
1 change: 1 addition & 0 deletions src/Pretender/CallInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public CallInfo(MethodInfo methodInfo, object?[] arguments)
{
MethodInfo = methodInfo;
Arguments = arguments;

}

public MethodInfo MethodInfo { get; }
Expand Down
5 changes: 3 additions & 2 deletions src/Pretender/Internals/BaseCompiledSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ public void SetBehavior(Behavior behavior)
_behavior = behavior;
}

public void ExecuteCore(CallInfo callInfo)
public bool ExecuteCore(CallInfo callInfo)
{
if (!Matches(callInfo))
{
return;
return false;
}
TimesCalled++;
return true;
}

public bool Matches(CallInfo callInfo)
Expand Down
8 changes: 7 additions & 1 deletion src/Pretender/Internals/ReturningCompiledSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ public class ReturningCompiledSetup<T, TResult>(Pretend<T> pretend, MethodInfo m
[DebuggerStepThrough]
public void Execute(CallInfo callInfo)
{
ExecuteCore(callInfo);
var matched = ExecuteCore(callInfo);

if (!matched)
{
callInfo.ReturnValue ??= _defaultValue;
return;
}

// Run behavior
if (_behavior is null)
Expand Down
2 changes: 1 addition & 1 deletion src/Pretender/Internals/SingleUseCallHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Pretender.Internals
/// <summary>
/// **FOR INTERNAL USE ONLY**
/// </summary>
public class SingleUseCallHandler<T> : ICallHandler
public sealed class SingleUseCallHandler<T> : ICallHandler
{
public object?[] Arguments { get; private set; } = null!;

Expand Down
7 changes: 6 additions & 1 deletion src/Pretender/Internals/VoidCompiledSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ public class VoidCompiledSetup<T>(Pretend<T> pretend, MethodInfo methodInfo, Mat
[DebuggerStepThrough]
public void Execute(CallInfo callInfo)
{
ExecuteCore(callInfo);
var matched = ExecuteCore(callInfo);

if (!matched)
{
return;
}

// Run behavior
if (_behavior is null)
Expand Down
2 changes: 1 addition & 1 deletion src/Pretender/Pretender.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
8 changes: 4 additions & 4 deletions test/Pretender.Tests/Pretender.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// <auto-generated>

#nullable enable annotations
#nullable disable warnings

// Suppress warnings about [Obsolete] member usage in generated code.
#pragma warning disable CS0612, CS0618

namespace System.Runtime.CompilerServices
{
using System;
using System.CodeDom.Compiler;

[GeneratedCode("Pretender.SourceGenerator", "1.0.0.0")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
{
}
}
}

namespace Pretender.SourceGeneration
{
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Pretender;
using Pretender.Internals;

file class PretendITest : global::FieldReference.ITest
{
public static readonly MethodInfo Method_MethodInfo = typeof(PretendITest).GetMethod(nameof(Method))!;

private readonly ICallHandler _callHandler;

public PretendITest(ICallHandler callHandler)
{
_callHandler = callHandler;
}

public void Method(string arg)
{
object?[] __arguments__ = [arg];
var __callInfo__ = new CallInfo(Method_MethodInfo, __arguments__);
_callHandler.Handle(__callInfo__);
}
}

file static class SetupInterceptors
{
[InterceptsLocation(@"MyTest.cs", 22, 17)]
internal static IPretendSetup<global::FieldReference.ITest> Setup0(this Pretend<global::FieldReference.ITest> pretend, Action<global::FieldReference.ITest> setupExpression)
{
return pretend.GetOrCreateSetup(0, static (pretend, expr) =>
{
Matcher matchCall = (callInfo, setup) =>
{
var singleUseCallHandler = new SingleUseCallHandler();
var fake = new PretendITest(singleUseCallHandler);

var listener = MatcherListener.StartListening();
try
{
setup.Method.Invoke(setup.Target, [fake]);
}
finally
{
listener.Dispose();
}

var capturedArguments = singleUseCallHandler.Arguments;

var arg_arg = (string)callInfo.Arguments[0];
var arg_capturedArg = (string)capturedArguments[0];
if (arg_arg != arg_capturedArg)
{
return false;
}
return true;
};
return new VoidCompiledSetup<global::FieldReference.ITest>();
}, setupExpression);
}
}

file static class VerifyInterceptors
{
}

file static class CreateInterceptors
{
}
}
Loading

0 comments on commit 58066b5

Please sign in to comment.