Skip to content

Commit 94458e6

Browse files
committed
Little Cleanup and Tests
1 parent 923445a commit 94458e6

File tree

19 files changed

+303
-38
lines changed

19 files changed

+303
-38
lines changed

pretender.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{DA78B66F-E
2525
EndProject
2626
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Comparison", "perf\Comparison\Comparison.csproj", "{96C653E3-D10B-47A0-8E42-0B93119AE145}"
2727
EndProject
28+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pretender.Tests", "test\Pretender.Tests\Pretender.Tests.csproj", "{17552274-CC28-438A-80BB-4A161F95AB11}"
29+
EndProject
2830
Global
2931
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3032
Debug|Any CPU = Debug|Any CPU
@@ -95,6 +97,18 @@ Global
9597
{96C653E3-D10B-47A0-8E42-0B93119AE145}.Release|x64.Build.0 = Release|Any CPU
9698
{96C653E3-D10B-47A0-8E42-0B93119AE145}.Release|x86.ActiveCfg = Release|Any CPU
9799
{96C653E3-D10B-47A0-8E42-0B93119AE145}.Release|x86.Build.0 = Release|Any CPU
100+
{17552274-CC28-438A-80BB-4A161F95AB11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
101+
{17552274-CC28-438A-80BB-4A161F95AB11}.Debug|Any CPU.Build.0 = Debug|Any CPU
102+
{17552274-CC28-438A-80BB-4A161F95AB11}.Debug|x64.ActiveCfg = Debug|Any CPU
103+
{17552274-CC28-438A-80BB-4A161F95AB11}.Debug|x64.Build.0 = Debug|Any CPU
104+
{17552274-CC28-438A-80BB-4A161F95AB11}.Debug|x86.ActiveCfg = Debug|Any CPU
105+
{17552274-CC28-438A-80BB-4A161F95AB11}.Debug|x86.Build.0 = Debug|Any CPU
106+
{17552274-CC28-438A-80BB-4A161F95AB11}.Release|Any CPU.ActiveCfg = Release|Any CPU
107+
{17552274-CC28-438A-80BB-4A161F95AB11}.Release|Any CPU.Build.0 = Release|Any CPU
108+
{17552274-CC28-438A-80BB-4A161F95AB11}.Release|x64.ActiveCfg = Release|Any CPU
109+
{17552274-CC28-438A-80BB-4A161F95AB11}.Release|x64.Build.0 = Release|Any CPU
110+
{17552274-CC28-438A-80BB-4A161F95AB11}.Release|x86.ActiveCfg = Release|Any CPU
111+
{17552274-CC28-438A-80BB-4A161F95AB11}.Release|x86.Build.0 = Release|Any CPU
98112
EndGlobalSection
99113
GlobalSection(SolutionProperties) = preSolution
100114
HideSolutionNode = FALSE
@@ -104,6 +118,7 @@ Global
104118
{90CC0802-39BB-4390-8A06-C7FD0C14D5C7} = {DFB5E6EE-B017-40FD-BC67-CE0471060A68}
105119
{09EB76D6-C82D-48A9-A0F7-B9BCC10B7621} = {2667A3D7-30CA-4DF5-B2F4-A7554C6D3ADD}
106120
{96C653E3-D10B-47A0-8E42-0B93119AE145} = {DA78B66F-EE75-46B5-8EC8-6F498A8AFC64}
121+
{17552274-CC28-438A-80BB-4A161F95AB11} = {2667A3D7-30CA-4DF5-B2F4-A7554C6D3ADD}
107122
EndGlobalSection
108123
GlobalSection(ExtensibilityGlobals) = postSolution
109124
SolutionGuid = {FD826CBD-9A31-44A4-B352-E637B9FABD6B}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Collections.Immutable;
2+
using Microsoft.CodeAnalysis;
3+
using static Pretender.SourceGenerator.PretenderSourceGenerator;
4+
5+
namespace Pretender.SourceGenerator.Parser
6+
{
7+
internal class PretendParser
8+
{
9+
10+
public PretendParser(PretendInvocation pretendInvocation, CompilationData compilationData)
11+
{
12+
PretendInvocation = pretendInvocation;
13+
}
14+
15+
public PretendInvocation PretendInvocation { get; }
16+
17+
public (object? Emitter, ImmutableArray<Diagnostic>? Diagnostics) GetEmitter(CancellationToken cancellationToken)
18+
{
19+
20+
return (null, null);
21+
}
22+
}
23+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Diagnostics;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using Microsoft.CodeAnalysis.Operations;
5+
using Pretender.SourceGenerator.Parser;
6+
7+
namespace Pretender.SourceGenerator
8+
{
9+
internal class PretendInvocation
10+
{
11+
public PretendInvocation(ITypeSymbol pretendType, Location location, bool fillExisting)
12+
{
13+
PretendType = pretendType;
14+
Location = location;
15+
FillExisting = fillExisting;
16+
}
17+
18+
public ITypeSymbol PretendType { get; }
19+
public Location Location { get; }
20+
public bool FillExisting { get; }
21+
22+
public static bool IsCandidateSyntaxNode(SyntaxNode node)
23+
{
24+
// Pretend.That<T>();
25+
if (node is InvocationExpressionSyntax
26+
{
27+
Expression: MemberAccessExpressionSyntax
28+
{
29+
// TODO: Will this work with a using static Pretender.Pretend
30+
// ...
31+
// That<IInterface>();
32+
Expression: IdentifierNameSyntax { Identifier.ValueText: "Pretend" },
33+
Name: GenericNameSyntax { Identifier.ValueText: "That", TypeArgumentList.Arguments.Count: 1 },
34+
}
35+
})
36+
{
37+
return true;
38+
}
39+
40+
// TODO: Also do Attribute
41+
42+
return false;
43+
}
44+
45+
public static PretendInvocation? Create(GeneratorSyntaxContext context, CancellationToken cancellationToken)
46+
{
47+
Debug.Assert(IsCandidateSyntaxNode(context.Node));
48+
var operation = context.SemanticModel.GetOperation(context.Node, cancellationToken);
49+
if (operation is IInvocationOperation invocation)
50+
{
51+
cancellationToken.ThrowIfCancellationRequested();
52+
return CreateFromGeneric(invocation);
53+
}
54+
// TODO: Support attribute
55+
56+
return null;
57+
}
58+
59+
private static PretendInvocation? CreateFromGeneric(IInvocationOperation operation)
60+
{
61+
if (operation.TargetMethod is not IMethodSymbol
62+
{
63+
Name: "That",
64+
ContainingType: INamedTypeSymbol namedTypeSymbol,
65+
TypeArguments.Length: 1,
66+
} || !KnownTypeSymbols.IsPretend(namedTypeSymbol))
67+
{
68+
return null;
69+
}
70+
71+
return new PretendInvocation(operation.TargetMethod.TypeArguments[0], operation.Syntax.GetLocation(), false);
72+
}
73+
}
74+
}

src/Pretender.SourceGenerator/PretenderSourceGenerator.cs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2424
#region Pretend
2525
IncrementalValuesProvider<PretendEntrypoint> pretendsWithDiagnostics =
2626
context.SyntaxProvider.CreateSyntaxProvider(
27-
predicate: static (node, token) =>
28-
{
29-
// Pretend.That<T>();
30-
if (node is InvocationExpressionSyntax
31-
{
32-
Expression: MemberAccessExpressionSyntax
33-
{
34-
// TODO: Will this work with a using static Pretender.Pretend
35-
// ...
36-
// That<IInterface>();
37-
Expression: IdentifierNameSyntax { Identifier.ValueText: "Pretend" },
38-
Name: GenericNameSyntax { Identifier.ValueText: "That", TypeArgumentList.Arguments.Count: 1 },
39-
},
40-
})
41-
{
42-
return true;
43-
}
44-
45-
// TODO: Allow constructor and shortcut Pretend.Of<T>();
46-
return false;
47-
},
27+
predicate: (node, _) => PretendInvocation.IsCandidateSyntaxNode(node),
4828
transform: static (context, token) =>
4929
{
5030
var operation = context.SemanticModel.GetOperation(context.Node, token);

src/Pretender.SourceGenerator/SetupCreationSpec.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Collections.Immutable;
1+
using System.Collections.Immutable;
32
using System.Diagnostics;
43
using Microsoft.CodeAnalysis;
54
using Microsoft.CodeAnalysis.CSharp;
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
namespace Pretender.Behaviors
22
{
3-
public delegate void Callback(ref CallInfo callInfo);
4-
53
internal class CallbackBehavior : Behavior
64
{
7-
private readonly Callback _action;
5+
private readonly Action<CallInfo> _action;
86

9-
public CallbackBehavior(Callback action)
7+
public CallbackBehavior(Action<CallInfo> action)
108
{
119
_action = action;
1210
}
1311

1412
public override void Execute(CallInfo callInfo)
1513
{
16-
_action(ref callInfo);
14+
_action(callInfo);
1715
}
1816
}
1917
}

src/Pretender/Called.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Pretender
1+
using System.Diagnostics;
2+
3+
namespace Pretender
24
{
35
public readonly struct Called
46
{
@@ -15,15 +17,31 @@ private Called(int from, int to, CalledKind calledKind)
1517

1618
enum CalledKind
1719
{
18-
Exact
20+
Exact,
21+
AtLeast,
22+
Range,
1923
}
2024

2125
public static Called Exactly(int expectedCalls)
2226
=> new(expectedCalls, expectedCalls, CalledKind.Exact);
2327

28+
public static Called AtLeastOnce()
29+
=> new(1, int.MaxValue, CalledKind.AtLeast);
30+
31+
public static implicit operator Called(Range range)
32+
{
33+
if (range.Start.IsFromEnd || range.End.IsFromEnd)
34+
{
35+
throw new ArgumentException();
36+
}
37+
38+
return new(range.Start.Value, range.End.Value, CalledKind.Range);
39+
}
40+
2441
public static implicit operator Called(int expectedCalls)
2542
=> new(expectedCalls, expectedCalls, CalledKind.Exact);
2643

44+
[StackTraceHidden]
2745
public void Validate(int callCount)
2846
{
2947
switch (_calledKind)
@@ -35,10 +53,26 @@ public void Validate(int callCount)
3553
throw new Exception("It was not called exactly that many times.");
3654
}
3755
break;
56+
case CalledKind.AtLeast:
57+
if (callCount < _from)
58+
{
59+
throw new Exception($"It was not called at least {_from} time(s)");
60+
}
61+
break;
62+
case CalledKind.Range:
63+
if (callCount < _from || callCount >= _to)
64+
{
65+
throw new Exception($"It was not between the range {_from}..{_to}");
66+
}
67+
break;
3868
default:
3969
throw new Exception("Invalid call kind.");
4070
}
71+
}
4172

73+
public override string ToString()
74+
{
75+
return $"From = {_from}, To = {_to}, Kind = {_calledKind}";
4276
}
4377
}
4478
}

src/Pretender/It.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public static class It
77
[Matcher<AnyMatcher>]
88
public static T IsAny<T>()
99
{
10-
// This method is never normally invoked during its normal usage inside an expression
10+
// This method is never normally invoked during its normal usage
1111
return default!;
1212
}
1313

src/Pretender/Matchers/AnyMatcher.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
{
33
public sealed class AnyMatcher : IMatcher
44
{
5-
public static AnyMatcher Instance = new();
6-
75
public bool Matches(object? argument)
86
{
97
return true;

src/Pretender/Matchers/IMatcher.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
namespace Pretender.Matchers
22
{
3+
/// <summary>
4+
///
5+
/// </summary>
6+
/// <remarks>
7+
/// Matchers don't actually need to implement this interface, matchers are used by duck-typing.
8+
/// So as long as they implement a `Matches` method taking one argument and returning a bool it will be used.
9+
/// </remarks>
310
public interface IMatcher
411
{
512
bool Matches(object? argument);

0 commit comments

Comments
 (0)