From 5ad53dd95c2a6c667a4be114d8df45d608290250 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Sat, 20 Jan 2018 20:45:19 -0300 Subject: [PATCH 1/2] Add support for recursive mocks When doing `Setup(...)` we automatically setup a `SetupScope` which turns on the `RecursiveMockBehavior`'s automatic setup of null-valued returns just as you would have done if you had done `mock.RecursiveProp.Returns(Mock.Of)` followed by the actual setup of `mock.RecursiveProp.Name.Returns("foo")`. This can now be shrinked to `mock.Setup(x => x.RecursiveProp.Name).Returns("foo")`. Simplifying the detection of an active setup was simplified since the previous approach of making it per-mock (we were keeping a state flag in the mock itself) wouldn't work well in recursive mocks since you'd need to preserve that flag across mocks, and it wasn't really obvious how much benefit we were getting from making it per-mock as opposed to something like: ```c# using (Setup()) { // Set up any number of mocks in a single block } ``` So we enabled precisely that, by moving to a global per-execution context flag kept by an `AsyncLocal` than can propagate threads. NOTE: we don't keep track of how many scopes have been created so far and which ones have been disposed to run off the `SetupScope.IsActive` flag. Doing so might be useful eventually, but it's better to keep the design simple for now since having multiple nested calls that would perform setups is likely quite a rare scenario anyway. --- src/Moq.sln | 9 +- src/Moq/Moq.Sdk.Tests/MockExtensionsTests.cs | 3 + src/Moq/Moq.Sdk/KnownStates.cs | 31 ------- src/Moq/Moq.Sdk/MockState.cs | 16 +++- src/Moq/Moq.Sdk/PropertyBehavior.cs | 2 +- src/Moq/Moq.Sdk/SetupScope.cs | 40 +++++++++ src/Moq/Moq.Sdk/StrictMockBehavior.cs | 2 +- .../Moq.Tests/Mocks/IRecursiveBranchMock.cs | 18 ++-- src/Moq/Moq.Tests/Mocks/IRecursiveRootMock.cs | 18 ++-- src/Moq/Moq.Tests/MoqTests.cs | 2 +- src/Moq/Moq.Tests/RecursiveMocks.cs | 33 -------- src/Moq/Moq.Tests/RecursiveMocksTests.cs | 60 ++++++++++++++ src/Moq/Moq/Extensions.cs | 7 ++ src/Moq/Moq/ISetup.cs | 10 +++ src/Moq/Moq/RecursiveMockBehavior.cs | 66 +++++++++++++++ src/Moq/Moq/Setup.cs | 83 ------------------- src/Moq/Moq/SetupExtension.cs | 48 +++++++++++ src/Moq/Moq/Syntax.cs | 6 ++ src/Stunts/Stunts.Analyzer/StuntCodeAction.cs | 14 +--- src/Stunts/Stunts.Sdk/SymbolExtensions.cs | 2 - src/Stunts/Stunts/Stunts.csproj | 4 - 21 files changed, 284 insertions(+), 190 deletions(-) delete mode 100644 src/Moq/Moq.Sdk/KnownStates.cs create mode 100644 src/Moq/Moq.Sdk/SetupScope.cs delete mode 100644 src/Moq/Moq.Tests/RecursiveMocks.cs create mode 100644 src/Moq/Moq.Tests/RecursiveMocksTests.cs create mode 100644 src/Moq/Moq/ISetup.cs create mode 100644 src/Moq/Moq/RecursiveMockBehavior.cs delete mode 100644 src/Moq/Moq/Setup.cs create mode 100644 src/Moq/Moq/SetupExtension.cs diff --git a/src/Moq.sln b/src/Moq.sln index bbb47dae..1b4d79db 100644 --- a/src/Moq.sln +++ b/src/Moq.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 +VisualStudioVersion = 15.0.27130.2024 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq", "Moq\Moq\Moq.csproj", "{65F3AB28-9A74-405E-83B2-420D5AD5A459}" EndProject @@ -48,6 +48,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{1DBDFC27 build\Version.targets = build\Version.targets EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stunts.Tests", "Stunts\Stunts.Tests\Stunts.Tests.csproj", "{EDBDA217-CA42-4C82-826F-7D990185EC0F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -94,6 +96,10 @@ Global {9A09225F-E0BC-4890-BED4-D9F6F5DAC146}.Debug|Any CPU.Build.0 = Debug|Any CPU {9A09225F-E0BC-4890-BED4-D9F6F5DAC146}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A09225F-E0BC-4890-BED4-D9F6F5DAC146}.Release|Any CPU.Build.0 = Release|Any CPU + {EDBDA217-CA42-4C82-826F-7D990185EC0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDBDA217-CA42-4C82-826F-7D990185EC0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDBDA217-CA42-4C82-826F-7D990185EC0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDBDA217-CA42-4C82-826F-7D990185EC0F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -104,6 +110,7 @@ Global {9ACA3490-778C-4C58-914C-87B9E7C15AC7} = {EEC3EC48-ACB2-4D25-B592-F360F676BE45} {0271D7C1-1A51-4148-944C-1DB4D1946B9C} = {EEC3EC48-ACB2-4D25-B592-F360F676BE45} {1DBDFC27-21EC-4EAC-B51B-84EDC8DBB9D5} = {2093478C-CEA6-4034-BCDE-EDC7A5DD4532} + {EDBDA217-CA42-4C82-826F-7D990185EC0F} = {EEC3EC48-ACB2-4D25-B592-F360F676BE45} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DA4CFD03-827D-482B-9304-83456D2A8115} diff --git a/src/Moq/Moq.Sdk.Tests/MockExtensionsTests.cs b/src/Moq/Moq.Sdk.Tests/MockExtensionsTests.cs index 14cb6bc3..68da529d 100644 --- a/src/Moq/Moq.Sdk.Tests/MockExtensionsTests.cs +++ b/src/Moq/Moq.Sdk.Tests/MockExtensionsTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using Stunts; using Xunit; @@ -137,6 +138,8 @@ class Mock : IMock public IMockSetup LastSetup { get; set; } + public IEnumerable Setups => Behaviors.OfType(); + public IMockBehavior BehaviorFor(IMockSetup setup) => throw new NotImplementedException(); } } diff --git a/src/Moq/Moq.Sdk/KnownStates.cs b/src/Moq/Moq.Sdk/KnownStates.cs deleted file mode 100644 index d31dc9b8..00000000 --- a/src/Moq/Moq.Sdk/KnownStates.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Moq.Sdk.Properties; - -namespace Moq.Sdk -{ - /// - /// Known states a mock can be in. - /// - public static class KnownStates - { - /// - /// The key in containing a - /// specifying if the mock is being set up or not. - /// - public const string Setup = nameof(KnownStates) + "." + nameof(Setup); - - /// - /// Whether the mock is being set up. - /// - /// The mock to inspect. - /// if the mock is being set up (it has the state key - /// set to ), otherwise. - public static bool InSetup(object mock) - { - var target = mock is IMocked mocked ? - mocked.Mock : throw new ArgumentException(Resources.TargetNotMock, nameof(mock)); - - return target.State.TryGetValue(Setup, out var state) && state.GetValueOrDefault(); - } - } -} diff --git a/src/Moq/Moq.Sdk/MockState.cs b/src/Moq/Moq.Sdk/MockState.cs index 085f36c6..22a79c00 100644 --- a/src/Moq/Moq.Sdk/MockState.cs +++ b/src/Moq/Moq.Sdk/MockState.cs @@ -48,14 +48,24 @@ public T GetOrAdd(object key, Func valueFactory) /// regardless of whether there is an existing value assigned. /// public void Set(T value) - => state[typeof(T)] = value; + { + if (value == null) + state.TryRemove(typeof(T), out _); + else + state[typeof(T)] = value; + } /// /// Sets the state of the given type and , /// regardless of whether there is an existing value assigned. /// public void Set(object key, T value) - => state[Key(key)] = value; + { + if (value == null) + state.TryRemove(Key(key), out _); + else + state[Key(key)] = value; + } /// /// Attempts to add the specified value to the mock state. @@ -135,7 +145,7 @@ public bool TryRemove(object key, out T value) value = (T)_value; return result; } - + /// /// Compares the existing value for of the specified with a specified value, /// and if they are equal, updates it with a third value. diff --git a/src/Moq/Moq.Sdk/PropertyBehavior.cs b/src/Moq/Moq.Sdk/PropertyBehavior.cs index fe0d650a..1d61f219 100644 --- a/src/Moq/Moq.Sdk/PropertyBehavior.cs +++ b/src/Moq/Moq.Sdk/PropertyBehavior.cs @@ -40,7 +40,7 @@ public IMethodReturn Invoke(IMethodInvocation invocation, GetNextBehavior getNex return invocation.CreateValueReturn(value); if (invocation.MethodBase.Name.StartsWith("set_", StringComparison.Ordinal) && - (!SetterRequiresSetup || KnownStates.InSetup(invocation.Target))) + (!SetterRequiresSetup || SetupScope.IsActive)) { state.Set("_" + invocation.MethodBase.Name.Substring(4), invocation.Arguments[0]); return invocation.CreateValueReturn(null); diff --git a/src/Moq/Moq.Sdk/SetupScope.cs b/src/Moq/Moq.Sdk/SetupScope.cs new file mode 100644 index 00000000..504c3661 --- /dev/null +++ b/src/Moq/Moq.Sdk/SetupScope.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading; + +namespace Moq +{ + /// + /// Marks the mock as being set up, meaning + /// invocation tracking and strict behavior + /// will be suspended until the scope is + /// is disposed. + /// + /// + /// We make an exception with the namespace for this + /// type, since it's intended to be used by users + /// quite regularly, unless they are leveraging the + /// using static Moq.Syntax; which allows the + /// simpler using (Setup()) { ... } syntax. + /// + public class SetupScope : IDisposable + { + static AsyncLocal setup = new AsyncLocal(); + + /// + /// Initializes the setup scope. + /// + public SetupScope() => setup.Value = true; + + /// + /// Whether there is an active setup scope in the running + /// execution context. + /// + public static bool IsActive => setup.Value == true; + + /// + /// Disposes the scope, setting + /// back to . + /// + public void Dispose() => setup.Value = null; + } +} \ No newline at end of file diff --git a/src/Moq/Moq.Sdk/StrictMockBehavior.cs b/src/Moq/Moq.Sdk/StrictMockBehavior.cs index 5b2e19e9..16f509dc 100644 --- a/src/Moq/Moq.Sdk/StrictMockBehavior.cs +++ b/src/Moq/Moq.Sdk/StrictMockBehavior.cs @@ -24,7 +24,7 @@ public IMethodReturn Invoke(IMethodInvocation invocation, GetNextBehavior getNex { if (invocation == null) throw new ArgumentNullException(nameof(invocation)); - if (!KnownStates.InSetup(invocation?.Target)) + if (!SetupScope.IsActive) throw new StrictMockException(); // Otherwise, fallback to returning default values so that diff --git a/src/Moq/Moq.Tests/Mocks/IRecursiveBranchMock.cs b/src/Moq/Moq.Tests/Mocks/IRecursiveBranchMock.cs index 5ea0eb48..b249c6d5 100644 --- a/src/Moq/Moq.Tests/Mocks/IRecursiveBranchMock.cs +++ b/src/Moq/Moq.Tests/Mocks/IRecursiveBranchMock.cs @@ -7,24 +7,27 @@ // //------------------------------------------------------------------------------ +using Moq.Tests.Recursive; +using System.Threading; +using Moq.Sdk; using System; using System.Collections.ObjectModel; using System.Reflection; using Stunts; using System.Runtime.CompilerServices; -using Moq.Tests.Recursive; -using System.Threading; -using Moq.Sdk; namespace Mocks { public partial class IRecursiveBranchMock : IRecursiveBranch, IStunt, IMocked { readonly BehaviorPipeline pipeline = new BehaviorPipeline(); + IMock mock; [CompilerGenerated] ObservableCollection IStunt.Behaviors => pipeline.Behaviors; + IMock IMocked.Mock => LazyInitializer.EnsureInitialized(ref mock, () => new DefaultMock(this)); + [CompilerGenerated] public IRecursiveLeaf Leaf => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); @@ -33,13 +36,8 @@ public partial class IRecursiveBranchMock : IRecursiveBranch, IStunt, IMocked [CompilerGenerated] public override int GetHashCode() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); [CompilerGenerated] - public override string ToString() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); - - #region IMocked - IMock mock; - + public IRecursiveLeaf GetLeaf(int index) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), index)); [CompilerGenerated] - IMock IMocked.Mock => LazyInitializer.EnsureInitialized(ref mock, () => new DefaultMock(this)); - #endregion + public override string ToString() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); } } \ No newline at end of file diff --git a/src/Moq/Moq.Tests/Mocks/IRecursiveRootMock.cs b/src/Moq/Moq.Tests/Mocks/IRecursiveRootMock.cs index 1837bfd9..573ac9d9 100644 --- a/src/Moq/Moq.Tests/Mocks/IRecursiveRootMock.cs +++ b/src/Moq/Moq.Tests/Mocks/IRecursiveRootMock.cs @@ -7,24 +7,27 @@ // //------------------------------------------------------------------------------ +using Moq.Tests.Recursive; +using System.Threading; +using Moq.Sdk; using System; using System.Collections.ObjectModel; using System.Reflection; using Stunts; using System.Runtime.CompilerServices; -using Moq.Tests.Recursive; -using System.Threading; -using Moq.Sdk; namespace Mocks { public partial class IRecursiveRootMock : IRecursiveRoot, IStunt, IMocked { readonly BehaviorPipeline pipeline = new BehaviorPipeline(); + IMock mock; [CompilerGenerated] ObservableCollection IStunt.Behaviors => pipeline.Behaviors; + IMock IMocked.Mock => LazyInitializer.EnsureInitialized(ref mock, () => new DefaultMock(this)); + [CompilerGenerated] public IRecursiveBranch Branch => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); @@ -33,13 +36,8 @@ public partial class IRecursiveRootMock : IRecursiveRoot, IStunt, IMocked [CompilerGenerated] public override int GetHashCode() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); [CompilerGenerated] - public override string ToString() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); - - #region IMocked - IMock mock; - + public IRecursiveLeaf GetLeaf(int index) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), index)); [CompilerGenerated] - IMock IMocked.Mock => LazyInitializer.EnsureInitialized(ref mock, () => new DefaultMock(this)); - #endregion + public override string ToString() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); } } \ No newline at end of file diff --git a/src/Moq/Moq.Tests/MoqTests.cs b/src/Moq/Moq.Tests/MoqTests.cs index 9c7f2046..4c63fbd7 100644 --- a/src/Moq/Moq.Tests/MoqTests.cs +++ b/src/Moq/Moq.Tests/MoqTests.cs @@ -213,7 +213,7 @@ public void CanSetupPropertyViaReturnsForStrictMock() var calculator = Mock.Of(MockBehavior.Strict); // NOTE: since the mock is strict, we need to tell we're going to set it up - using (calculator.Setup()) + using (Setup()) { calculator.Mode.Returns(CalculatorMode.Scientific); } diff --git a/src/Moq/Moq.Tests/RecursiveMocks.cs b/src/Moq/Moq.Tests/RecursiveMocks.cs deleted file mode 100644 index 97aeb826..00000000 --- a/src/Moq/Moq.Tests/RecursiveMocks.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Xunit; - -namespace Moq.Tests.Recursive -{ - public class RecursiveMocks - { - - [Fact(Skip = "Pending support for recursive mocks")] - public void CanSetupRecursiveMock() - { - var mock = Mock.Of(MockBehavior.Loose); - - mock.Setup(m => m.Branch.Leaf.Name).Returns("foo"); - - Assert.Equal("foo", mock.Branch.Leaf.Name); - } - } - - public interface IRecursiveRoot - { - IRecursiveBranch Branch { get; } - } - - public interface IRecursiveBranch - { - IRecursiveLeaf Leaf { get; } - } - - public interface IRecursiveLeaf - { - string Name { get; set; } - } -} diff --git a/src/Moq/Moq.Tests/RecursiveMocksTests.cs b/src/Moq/Moq.Tests/RecursiveMocksTests.cs new file mode 100644 index 00000000..b56adc02 --- /dev/null +++ b/src/Moq/Moq.Tests/RecursiveMocksTests.cs @@ -0,0 +1,60 @@ +using Xunit; +using static Moq.Syntax; + +namespace Moq.Tests.Recursive +{ + public class RecursiveMocksTests + { + [Fact] + public void CanSetupRecursiveMockProperty() + { + var mock = Mock.Of(MockBehavior.Loose); + + mock.Setup(m => m.Branch.Leaf.Name).Returns("foo"); + + Assert.Equal("foo", mock.Branch.Leaf.Name); + } + + [Fact] + public void CanSetupRecursiveMockMethod() + { + var mock = Mock.Of(MockBehavior.Loose); + + mock.Setup(m => m.Branch.GetLeaf(1).Name).Returns("foo"); + + Assert.Equal("foo", mock.Branch.GetLeaf(1).Name); + Assert.Equal(null, mock.Branch.GetLeaf(0)); + } + + [Fact] + public void CanSetupRecursiveMockMethodInSetupScope() + { + var mock = Mock.Of(MockBehavior.Loose); + + using (Setup()) + { + mock.Branch.GetLeaf(1).Name.Returns("foo"); + } + + Assert.Equal("foo", mock.Branch.GetLeaf(1).Name); + Assert.Equal(null, mock.Branch.GetLeaf(0)); + } + } + + public interface IRecursiveRoot + { + IRecursiveBranch Branch { get; } + } + + public interface IRecursiveBranch + { + IRecursiveLeaf Leaf { get; } + + IRecursiveLeaf GetLeaf(int index); + } + + public interface IRecursiveLeaf + { + string Name { get; set; } + } +} diff --git a/src/Moq/Moq/Extensions.cs b/src/Moq/Moq/Extensions.cs index c19fcc23..b29c9778 100644 --- a/src/Moq/Moq/Extensions.cs +++ b/src/Moq/Moq/Extensions.cs @@ -6,6 +6,8 @@ namespace Moq { static class Extensions { + const string TaskFullName = "System.Threading.Tasks.Task"; + public static void EnsureCompatible(this IMethodInvocation invocation, Delegate @delegate) { var method = @delegate.GetMethodInfo(); @@ -14,5 +16,10 @@ public static void EnsureCompatible(this IMethodInvocation invocation, Delegate // TODO: validate assignability } + + public static bool CanBeIntercepted(this Type type) + => !type.FullName.StartsWith(TaskFullName, StringComparison.Ordinal) && + (type.GetTypeInfo().IsInterface || + (type.GetTypeInfo().IsClass && !type.GetTypeInfo().IsSealed)); } } diff --git a/src/Moq/Moq/ISetup.cs b/src/Moq/Moq/ISetup.cs new file mode 100644 index 00000000..37807284 --- /dev/null +++ b/src/Moq/Moq/ISetup.cs @@ -0,0 +1,10 @@ +namespace Moq +{ + /// + /// Extension point interface for extension methods + /// that act on void methods via . + /// + public interface ISetup : IFluentInterface + { + } +} diff --git a/src/Moq/Moq/RecursiveMockBehavior.cs b/src/Moq/Moq/RecursiveMockBehavior.cs new file mode 100644 index 00000000..9c559cd0 --- /dev/null +++ b/src/Moq/Moq/RecursiveMockBehavior.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using System.Reflection; +using Stunts; + +namespace Moq.Sdk +{ + /// + /// + /// + public class RecursiveMockBehavior : IStuntBehavior + { + public bool AppliesTo(IMethodInvocation invocation) + => SetupScope.IsActive; + + public IMethodReturn Invoke(IMethodInvocation invocation, GetNextBehavior getNext) + { + if (invocation.MethodBase is MethodInfo info && + info.ReturnType != typeof(void) && + info.ReturnType.CanBeIntercepted()) + { + var result = getNext().Invoke(invocation, getNext); + if (result.ReturnValue == null) + { + // Turn the null value into a mock for the current invocation setup + var currentMock = ((IMocked)invocation.Target).Mock; + var recursiveMock = ((IMocked)MockFactory.Default.CreateMock( + // Use the same assembly as the current target + invocation.Target.GetType().GetTypeInfo().Assembly, + info.ReturnType, + new Type[0], + new object[0])).Mock; + + // Clone the current mock's behaviors, except for the setups + foreach (var behavior in currentMock.Behaviors.Where(x => !(x is IMockBehavior))) + recursiveMock.Behaviors.Add(behavior); + + // Set up the current invocation to return the created value + var setup = currentMock.BehaviorFor(MockContext.CurrentSetup); + var returnBehavior = setup.Behaviors.OfType().FirstOrDefault(); + if (returnBehavior != null) + returnBehavior.ValueGetter = () => recursiveMock.Object; + else + setup.Behaviors.Add(new ReturnsBehavior(() => recursiveMock.Object)); + + // Copy over values from the result, so that outputs contain the default values. + var arguments = invocation.Arguments.ToArray(); + var parameters = invocation.MethodBase.GetParameters(); + for (var i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + // This covers both out & ref + if (parameter.ParameterType.IsByRef) + arguments[i] = result.Outputs[parameter.Name]; + } + + return invocation.CreateValueReturn(recursiveMock.Object, arguments); + } + + return result; + } + + return getNext().Invoke(invocation, getNext); + } + } +} diff --git a/src/Moq/Moq/Setup.cs b/src/Moq/Moq/Setup.cs deleted file mode 100644 index a9d2dc32..00000000 --- a/src/Moq/Moq/Setup.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.ComponentModel; -using Moq.Properties; -using Moq.Sdk; -using Stunts; - -namespace Moq -{ - /// - /// Extension for marking a mock as being set up, meaning - /// invocation tracking should be suspended. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static class SetupExtension - { - /// - /// Marks the mock as being set up, meaning - /// invocation tracking and strict behavior - /// should be suspended until the returned - /// is disposed. - /// - /// - /// - public static IDisposable Setup(this object mock) - { - var target = mock is IMocked mocked ? - mocked.Mock : throw new ArgumentException(Strings.TargetNotMock, nameof(mock)); - - return new SetupDisposable(target); - } - - public static ISetup Setup(this T mock, Action action) - { - using (Setup(mock)) - { - action(mock); - // TODO: what if the extension method - return new SetupAdapter(MockContext.CurrentSetup); - } - } - - public static TResult Setup(this T mock, Func function) - { - using (Setup(mock)) - { - var stunt = mock as IStunt ?? throw new ArgumentException(Strings.TargetNotMock, nameof(mock)); - return function(mock); - } - } - - class SetupDisposable : IDisposable - { - IMock mock; - - public SetupDisposable(IMock mock) - { - this.mock = mock; - mock.State.TryAdd(KnownStates.Setup, true); - } - - public void Dispose() => mock.State.TryRemove(KnownStates.Setup, out var _); - } - } - - class SetupAdapter : ISetup, IFluentInterface - { - public SetupAdapter(IMockSetup setup) => Setup = setup; - - internal IMockSetup Setup { get; } - } - - public interface ISetup - { - } -} - -namespace Moq.Sdk -{ - public static class ISetupExtension - { - public static IMockSetup Sdk(this ISetup setup) => (setup as SetupAdapter)?.Setup; - } -} \ No newline at end of file diff --git a/src/Moq/Moq/SetupExtension.cs b/src/Moq/Moq/SetupExtension.cs new file mode 100644 index 00000000..107d83e8 --- /dev/null +++ b/src/Moq/Moq/SetupExtension.cs @@ -0,0 +1,48 @@ +using System; +using System.ComponentModel; +using Moq.Properties; +using Moq.Sdk; +using Stunts; + +namespace Moq +{ + /// + /// Extension for initiating a setup block, meaning + /// invocation tracking and strict mock behavior should + /// be suspended. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class SetupExtension + { + /// + /// Sets up the mock with the given void method call. + /// + public static ISetup Setup(this T mock, Action action) + { + using (new SetupScope()) + { + action(mock); + return DefaultSetup.Default; + } + } + + /// + /// Sets up the mock with the given function. + /// + public static TResult Setup(this T mock, Func function) + { + using (new SetupScope()) + { + var stunt = mock as IStunt ?? throw new ArgumentException(Strings.TargetNotMock, nameof(mock)); + return function(mock); + } + } + + class DefaultSetup : ISetup + { + public static ISetup Default { get; } = new DefaultSetup(); + + DefaultSetup() { } + } + } +} \ No newline at end of file diff --git a/src/Moq/Moq/Syntax.cs b/src/Moq/Moq/Syntax.cs index 22b7fec2..5809480a 100644 --- a/src/Moq/Moq/Syntax.cs +++ b/src/Moq/Moq/Syntax.cs @@ -1,4 +1,5 @@ using System; +using Moq.Sdk; namespace Moq { @@ -55,5 +56,10 @@ public static class Syntax /// as the event arguments. /// public static TEventHandler Raise(params object[] args) => Moq.Raise.Event(args); + + /// + /// Marks a code block as being setup for mocks. Usage: using (Setup()) { ... }. + /// + public static IDisposable Setup() => new SetupScope(); } } diff --git a/src/Stunts/Stunts.Analyzer/StuntCodeAction.cs b/src/Stunts/Stunts.Analyzer/StuntCodeAction.cs index 9aec984c..3c498706 100644 --- a/src/Stunts/Stunts.Analyzer/StuntCodeAction.cs +++ b/src/Stunts/Stunts.Analyzer/StuntCodeAction.cs @@ -50,18 +50,12 @@ protected override async Task GetChangedDocumentAsync(CancellationToke .ToArray(); var generator = SyntaxGenerator.GetGenerator(document.Project); - var stunt = await CreateGenerator(naming).GenerateDocumentAsync(document.Project, symbols, cancellationToken); + var stunts = CreateGenerator(naming); - // This is somewhat expensive, but since we're adding it to the user' solution, we might - // as well make it look great ;) - stunt = await Simplifier.ReduceAsync(stunt).ConfigureAwait(false); - if (document.Project.Language != LanguageNames.VisualBasic) - stunt = await Formatter.FormatAsync(stunt, Formatter.Annotation).ConfigureAwait(false); - - return stunt; + return await CreateStunt(symbols, generator, stunts, cancellationToken); } - async Task CreateStunt(IEnumerable symbols, SyntaxGenerator generator, StuntGenerator stunts, CancellationToken cancellationToken) + async Task CreateStunt(IEnumerable symbols, SyntaxGenerator generator, StuntGenerator stunts, CancellationToken cancellationToken) { var (name, syntax) = stunts.CreateStunt(symbols, generator); @@ -106,7 +100,7 @@ async Task CreateStunt(IEnumerable symbols, SyntaxGe syntax = await stuntDoc.GetSyntaxRootAsync().ConfigureAwait(false); - return stuntDoc.WithSyntaxRoot(syntax).Project.Solution; + return stuntDoc.WithSyntaxRoot(syntax); } } } diff --git a/src/Stunts/Stunts.Sdk/SymbolExtensions.cs b/src/Stunts/Stunts.Sdk/SymbolExtensions.cs index 975681cd..e9baa04d 100644 --- a/src/Stunts/Stunts.Sdk/SymbolExtensions.cs +++ b/src/Stunts/Stunts.Sdk/SymbolExtensions.cs @@ -13,7 +13,6 @@ namespace Stunts public static class SymbolExtensions { const string TaskFullName = "System.Threading.Tasks.Task"; - const string ValueTupleFullName = "System.ValueTuple"; /// /// Retrieves the distinct set of symbols that can be intercepted from the @@ -136,7 +135,6 @@ public static bool TryValidateGeneratorTypes(this IEnumerable types public static bool CanBeIntercepted(this ITypeSymbol symbol) => symbol.CanBeReferencedByName && !symbol.ToString().StartsWith(TaskFullName, StringComparison.Ordinal) && - !symbol.ToString().StartsWith(ValueTupleFullName, StringComparison.Ordinal) && (symbol.TypeKind == TypeKind.Interface || (symbol?.TypeKind == TypeKind.Class && symbol?.IsSealed == false)); diff --git a/src/Stunts/Stunts/Stunts.csproj b/src/Stunts/Stunts/Stunts.csproj index 1ceaa077..2a49a5e4 100644 --- a/src/Stunts/Stunts/Stunts.csproj +++ b/src/Stunts/Stunts/Stunts.csproj @@ -16,8 +16,4 @@ - - - - \ No newline at end of file From 7488d0fe360755c6d25114db2006ba9a1461c22a Mon Sep 17 00:00:00 2001 From: stakx Date: Sat, 20 Jan 2018 18:25:39 +0100 Subject: [PATCH 2/2] Fix typos in comments --- src/Moq/Moq.Sdk/Behavior.cs | 2 +- src/Moq/Moq.Sdk/IMockFactory.cs | 2 +- src/Moq/Moq.Sdk/MockContext.cs | 2 +- src/Moq/Moq/CallbackExtension.cs | 2 +- src/Moq/Moq/ReturnsBehavior.cs | 2 +- src/Moq/Moq/ReturnsExtension.cs | 4 ++-- src/Stunts/Stunts.Analyzer/DiagnosticsExtensions.cs | 2 +- src/Stunts/Stunts.Analyzer/StuntGeneratorAnalyzer.cs | 6 +++--- src/Stunts/Stunts.Analyzer/UnsupportedNestedTypeAnalyzer.cs | 6 +++--- src/Stunts/Stunts.Analyzer/ValidateTypesAnalyzer.cs | 2 +- src/Stunts/Stunts.Sdk/ProcessorPhase.cs | 2 +- src/Stunts/Stunts.Sdk/Processors/DefaultImports.cs | 2 +- .../Stunts.Sdk/Processors/SyntaxGeneratorExtensions.cs | 2 +- src/Stunts/Stunts.Sdk/StuntGenerator.cs | 4 ++-- src/Stunts/Stunts/BehaviorPipeline.cs | 2 +- src/Stunts/Stunts/IStuntFactory.cs | 2 +- src/Stunts/Stunts/StuntExtensions.cs | 6 +++--- src/build/Version.targets | 2 +- 18 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Moq/Moq.Sdk/Behavior.cs b/src/Moq/Moq.Sdk/Behavior.cs index dc1584b1..8829aa14 100644 --- a/src/Moq/Moq.Sdk/Behavior.cs +++ b/src/Moq/Moq.Sdk/Behavior.cs @@ -28,7 +28,7 @@ public Behavior(InvokeBehavior invoke, string displayName) /// delegate and friendly display name. /// /// - /// Use this constructor overload whenver constructing the display + /// Use this constructor overload whenever constructing the display /// name is somewhat expensive. /// public Behavior(InvokeBehavior invoke, Lazy displayName) diff --git a/src/Moq/Moq.Sdk/IMockFactory.cs b/src/Moq/Moq.Sdk/IMockFactory.cs index bf46adb4..34d4b7c8 100644 --- a/src/Moq/Moq.Sdk/IMockFactory.cs +++ b/src/Moq/Moq.Sdk/IMockFactory.cs @@ -15,7 +15,7 @@ public interface IMockFactory /// The base type (or main interface) of the mock. /// Additional interfaces implemented by the mock, or an empty array. /// - /// Contructor arguments if the is a class, rather than an interface, or an empty array. + /// Constructor arguments if the is a class, rather than an interface, or an empty array. /// /// A mock that implements in addition to the specified interfaces (if any). object CreateMock(Assembly mocksAssembly, Type baseType, Type[] implementedInterfaces, object[] construtorArguments); diff --git a/src/Moq/Moq.Sdk/MockContext.cs b/src/Moq/Moq.Sdk/MockContext.cs index 469de904..7135eee0 100644 --- a/src/Moq/Moq.Sdk/MockContext.cs +++ b/src/Moq/Moq.Sdk/MockContext.cs @@ -17,7 +17,7 @@ public static class MockContext /// /// The last invocation on the mock, turned into an /// ready for use together with the - /// method to locate the maching to add new behaviors + /// method to locate the matching to add new behaviors /// when a matching invocation is performed. /// /// diff --git a/src/Moq/Moq/CallbackExtension.cs b/src/Moq/Moq/CallbackExtension.cs index 955d92be..7349d88d 100644 --- a/src/Moq/Moq/CallbackExtension.cs +++ b/src/Moq/Moq/CallbackExtension.cs @@ -23,7 +23,7 @@ static TResult Callback(this TResult target, Action 0) { var wrapped = behavior.Behaviors.Pop(); diff --git a/src/Moq/Moq/ReturnsBehavior.cs b/src/Moq/Moq/ReturnsBehavior.cs index fbe4d02d..a8d68db2 100644 --- a/src/Moq/Moq/ReturnsBehavior.cs +++ b/src/Moq/Moq/ReturnsBehavior.cs @@ -7,7 +7,7 @@ namespace Moq { /// /// A custom behavior for returning values, so that - /// the actual value to return can be replaced on succesive + /// the actual value to return can be replaced on successive /// method calls. /// [DebuggerDisplay("{DebuggerValue}", Name = "Returns", Type = nameof(ReturnsBehavior))] diff --git a/src/Moq/Moq/ReturnsExtension.cs b/src/Moq/Moq/ReturnsExtension.cs index f1361b4a..fd2e788c 100644 --- a/src/Moq/Moq/ReturnsExtension.cs +++ b/src/Moq/Moq/ReturnsExtension.cs @@ -66,8 +66,8 @@ static TResult Returns(Delegate value, InvokeBehavior behavior) var setup = MockContext.CurrentSetup; if (setup != null) { - // TODO: Is this even necessary given that intellisense gives us - // the rigth compiler safety already? + // TODO: Is this even necessary given that IntelliSense gives us + // the right compiler safety already? setup.Invocation.EnsureCompatible(value); var mock = ((IMocked)setup.Invocation.Target).Mock; diff --git a/src/Stunts/Stunts.Analyzer/DiagnosticsExtensions.cs b/src/Stunts/Stunts.Analyzer/DiagnosticsExtensions.cs index 30ff0d40..8c7670ce 100644 --- a/src/Stunts/Stunts.Analyzer/DiagnosticsExtensions.cs +++ b/src/Stunts/Stunts.Analyzer/DiagnosticsExtensions.cs @@ -21,7 +21,7 @@ public static class DiagnosticsExtensions }; /// - /// Gets the diangostics that represent build errors that happen when generated + /// Gets the diagnostics that represent build errors that happen when generated /// code is out of date. /// public static Diagnostic[] GetCompilationErrors(this Compilation compilation) diff --git a/src/Stunts/Stunts.Analyzer/StuntGeneratorAnalyzer.cs b/src/Stunts/Stunts.Analyzer/StuntGeneratorAnalyzer.cs index 074effa9..ca88f156 100644 --- a/src/Stunts/Stunts.Analyzer/StuntGeneratorAnalyzer.cs +++ b/src/Stunts/Stunts.Analyzer/StuntGeneratorAnalyzer.cs @@ -50,12 +50,12 @@ protected StuntGeneratorAnalyzer(NamingConvention naming, Type generatorAttribut public virtual DiagnosticDescriptor OutdatedDiagnostic => StuntDiagnostics.OutdatedStunt; /// - /// Returns the single this analyer supports. + /// Returns the single this analyzer supports. /// public sealed override ImmutableArray SupportedDiagnostics { // NOTE: this creates the return value at this point because both Missing and Outdated - // descriptors can be overriden as a customization point. + // descriptors can be overridden as a customization point. get { return ImmutableArray.Create(MissingDiagnostic, OutdatedDiagnostic); } } @@ -74,7 +74,7 @@ void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) var generator = context.Compilation.GetTypeByMetadataName(generatorAttribute.FullName); if (generator == null) // This may be an extender authoring error, but another analyzer should ensure proper - // metadata references exist. Typically, the same nuget package that adds this analyzer + // metadata references exist. Typically, the same NuGet package that adds this analyzer // also adds the required assembly references, so this should never happen anyway. return; diff --git a/src/Stunts/Stunts.Analyzer/UnsupportedNestedTypeAnalyzer.cs b/src/Stunts/Stunts.Analyzer/UnsupportedNestedTypeAnalyzer.cs index a5ca4342..f47c11e5 100644 --- a/src/Stunts/Stunts.Analyzer/UnsupportedNestedTypeAnalyzer.cs +++ b/src/Stunts/Stunts.Analyzer/UnsupportedNestedTypeAnalyzer.cs @@ -12,7 +12,7 @@ namespace Stunts /// the and reports unsupported nested types for /// codegen. /// - // TODO: this is a stop-gap meassure until we can figure out why the ImplementInterfaces codefix + // TODO: this is a stop-gap measure until we can figure out why the ImplementInterfaces codefix // is not returning any code actions for the CSharpScaffold (maybe VB too?) when the type to // be implemented is a nested interface :(. In the IDE, the code action is properly available after // generating the (non-working, since the interface isn't implemented) mock class. @@ -58,7 +58,7 @@ protected UnsupportedNestedTypeAnalyzer(NamingConvention naming, Type generatorA } /// - /// Returns the single descriptor this analyer supports. + /// Returns the single descriptor this analyzer supports. /// public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(descriptor); @@ -77,7 +77,7 @@ void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) var generator = context.Compilation.GetTypeByMetadataName(generatorAttribute.FullName); if (generator == null) // This may be an extender authoring error, but another analyzer should ensure proper - // metadata references exist. Typically, the same nuget package that adds this analyzer + // metadata references exist. Typically, the same NuGet package that adds this analyzer // also adds the required assembly references, so this should never happen anyway. return; diff --git a/src/Stunts/Stunts.Analyzer/ValidateTypesAnalyzer.cs b/src/Stunts/Stunts.Analyzer/ValidateTypesAnalyzer.cs index 0c5ea99c..3d60026e 100644 --- a/src/Stunts/Stunts.Analyzer/ValidateTypesAnalyzer.cs +++ b/src/Stunts/Stunts.Analyzer/ValidateTypesAnalyzer.cs @@ -33,7 +33,7 @@ protected ValidateTypesAnalyzer(Type generatorAttribute) /// /// Returns the single - /// diagnostic this analyer supports. + /// diagnostic this analyzer supports. /// public sealed override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( diff --git a/src/Stunts/Stunts.Sdk/ProcessorPhase.cs b/src/Stunts/Stunts.Sdk/ProcessorPhase.cs index 291d29ec..dbdb270f 100644 --- a/src/Stunts/Stunts.Sdk/ProcessorPhase.cs +++ b/src/Stunts/Stunts.Sdk/ProcessorPhase.cs @@ -33,7 +33,7 @@ public enum ProcessorPhase /// /// Final phase that allows generators to perform additional generation beyond scaffold - /// and inital stunt rewriting. Members generated in this phase are not rewritten at all + /// and initial stunt rewriting. Members generated in this phase are not rewritten at all /// to use the and can consist of language-specific fixups /// or cleanups to make the generated code more idiomatic than the default code fixes may /// provide. diff --git a/src/Stunts/Stunts.Sdk/Processors/DefaultImports.cs b/src/Stunts/Stunts.Sdk/Processors/DefaultImports.cs index 7c567346..02c84dba 100644 --- a/src/Stunts/Stunts.Sdk/Processors/DefaultImports.cs +++ b/src/Stunts/Stunts.Sdk/Processors/DefaultImports.cs @@ -34,7 +34,7 @@ public class DefaultImports : IDocumentProcessor public DefaultImports() : this(DefaultNamespaces) { } /// - /// Initializes the default imports a specific set of namespaces to add. + /// Initializes the default imports with a specific set of namespaces to add. /// public DefaultImports(params string[] namespaces) => this.namespaces = namespaces; diff --git a/src/Stunts/Stunts.Sdk/Processors/SyntaxGeneratorExtensions.cs b/src/Stunts/Stunts.Sdk/Processors/SyntaxGeneratorExtensions.cs index c66fd112..9d7b2258 100644 --- a/src/Stunts/Stunts.Sdk/Processors/SyntaxGeneratorExtensions.cs +++ b/src/Stunts/Stunts.Sdk/Processors/SyntaxGeneratorExtensions.cs @@ -7,7 +7,7 @@ namespace Stunts.Processors { /// - /// Language angnosti helper methods for code generation. + /// Language agnostic helper methods for code generation. /// static class SyntaxGeneratorExtensions { diff --git a/src/Stunts/Stunts.Sdk/StuntGenerator.cs b/src/Stunts/Stunts.Sdk/StuntGenerator.cs index 60f382d6..9f13d6ab 100644 --- a/src/Stunts/Stunts.Sdk/StuntGenerator.cs +++ b/src/Stunts/Stunts.Sdk/StuntGenerator.cs @@ -188,8 +188,8 @@ public StuntGenerator(NamingConvention naming, IEnumerable p public async Task ApplyProcessors(Document document, CancellationToken cancellationToken) { #if DEBUG - // While debugging the geneneration itself, don't let the cancellation timeouts - // from tests to cause this to fail. + // While debugging the generation itself, don't let the cancellation timeouts + // from tests cause this to fail. if (Debugger.IsAttached) cancellationToken = CancellationToken.None; #endif diff --git a/src/Stunts/Stunts/BehaviorPipeline.cs b/src/Stunts/Stunts/BehaviorPipeline.cs index bac62247..61015ac1 100644 --- a/src/Stunts/Stunts/BehaviorPipeline.cs +++ b/src/Stunts/Stunts/BehaviorPipeline.cs @@ -67,7 +67,7 @@ public BehaviorPipeline() /// Input to the method call. /// The ultimate target of the call. /// Whether to throw the if it has a value after running - /// the beaviors. + /// the behaviors. /// Return value from the pipeline. public IMethodReturn Invoke(IMethodInvocation invocation, InvokeBehavior target, bool throwOnException = false) { diff --git a/src/Stunts/Stunts/IStuntFactory.cs b/src/Stunts/Stunts/IStuntFactory.cs index cb579f8d..956e8bba 100644 --- a/src/Stunts/Stunts/IStuntFactory.cs +++ b/src/Stunts/Stunts/IStuntFactory.cs @@ -15,7 +15,7 @@ public interface IStuntFactory /// The base type (or main interface) of the stunt. /// Additional interfaces implemented by the stunt, or an empty array. /// - /// Contructor arguments if the is a class, rather than an interface, or an empty array. + /// Constructor arguments if the is a class, rather than an interface, or an empty array. /// /// A stunt that implements in addition to the specified interfaces (if any). object CreateStunt(Assembly stuntsAssembly, Type baseType, Type[] implementedInterfaces, object[] construtorArguments); diff --git a/src/Stunts/Stunts/StuntExtensions.cs b/src/Stunts/Stunts/StuntExtensions.cs index 15fb9a64..884769ae 100644 --- a/src/Stunts/Stunts/StuntExtensions.cs +++ b/src/Stunts/Stunts/StuntExtensions.cs @@ -31,7 +31,7 @@ public static IStunt AddBehavior(this IStunt stunt, IStuntBehavior behavior) public static TStunt AddBehavior(this TStunt stunt, InvokeBehavior behavior, AppliesTo appliesTo = null, string name = null) { // We can't just add a constraint to the method signature, because - // proxies are typically geneated and don't expose the IProxy interface directly. + // proxies are typically generated and don't expose the IProxy interface directly. if (stunt is IStunt target) target.Behaviors.Add(StuntBehavior.Create(behavior, appliesTo, name)); else @@ -74,7 +74,7 @@ public static IStunt InsertBehavior(this IStunt stunt, int index, IStuntBehavior } /// - /// Inserts a behavior into the stunt behasvior pipeline at the specified + /// Inserts a behavior into the stunt behavior pipeline at the specified /// index. /// public static TStunt InsertBehavior(this TStunt stunt, int index, InvokeBehavior behavior, AppliesTo appliesTo = null, string name = null) @@ -88,7 +88,7 @@ public static TStunt InsertBehavior(this TStunt stunt, int index, Invoke } /// - /// Inserts a behavior into the stunt behasvior pipeline at the specified + /// Inserts a behavior into the stunt behavior pipeline at the specified /// index. /// public static TStunt InsertBehavior(this TStunt stunt, int index, IStuntBehavior behavior) diff --git a/src/build/Version.targets b/src/build/Version.targets index 1a586207..99bbf446 100644 --- a/src/build/Version.targets +++ b/src/build/Version.targets @@ -20,7 +20,7 @@ AssemblyVersion=$(AssemblyVersion)" /> - + $(GitSemVerDashLabel)-pr$(BUILD_SOURCEBRANCH.Substring(10).TrimEnd('/merge')) $(GitSemVerDashLabel)-pr$(APPVEYOR_PULL_REQUEST_NUMBER)