From 3aeef6118ad5da223d9fab088826aa28cc8d84a8 Mon Sep 17 00:00:00 2001 From: cbhandal Date: Mon, 31 Jan 2022 12:03:22 +0000 Subject: [PATCH 1/3] Stub pre-access wrapper and tests --- CsharpExtras/Api/CsharpExtrasApi.cs | 3 + CsharpExtras/Api/ICsharpExtrasApi.cs | 1 + .../Event/Wrapper/IPreAccessWrapper.cs | 10 ++ .../Event/Wrapper/PreAccessWrapperImpl.cs | 29 ++++ .../Event/Wrapper/PreAccessWrapperTest.cs | 157 ++++++++++++++++++ CsharpExtrasTest/Properties/AssemblyInfo.cs | 20 +++ 6 files changed, 220 insertions(+) create mode 100644 CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs create mode 100644 CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs create mode 100644 CsharpExtrasTest/Event/Wrapper/PreAccessWrapperTest.cs create mode 100644 CsharpExtrasTest/Properties/AssemblyInfo.cs diff --git a/CsharpExtras/Api/CsharpExtrasApi.cs b/CsharpExtras/Api/CsharpExtrasApi.cs index f15b75e..a59937d 100644 --- a/CsharpExtras/Api/CsharpExtrasApi.cs +++ b/CsharpExtras/Api/CsharpExtrasApi.cs @@ -161,5 +161,8 @@ public ISparseArrayBuilder NewSparseArrayBuilder(PositiveInteger dim public ISparseArray2DBuilder NewSparseArray2DBuilder(TVal defaultValue) => new SparseArray2DBuilderImpl(defaultValue, this); + + public IPreAccessWrapper NewPreAccessWrapper(TObj obj, Action preAccessAction) => + new PreAccessWrapperImpl(obj, preAccessAction); } } diff --git a/CsharpExtras/Api/ICsharpExtrasApi.cs b/CsharpExtras/Api/ICsharpExtrasApi.cs index 08396df..5dca8a8 100644 --- a/CsharpExtras/Api/ICsharpExtrasApi.cs +++ b/CsharpExtras/Api/ICsharpExtrasApi.cs @@ -29,6 +29,7 @@ public interface ICsharpExtrasApi IComparer NewDescendingComparer(); IDirectoryDecorator NewDirectoryDecorator(); IValidator NewEmptyValidator(); + IPreAccessWrapper NewPreAccessWrapper(TObj obj, Action preAccessAction); IFileDecorator NewFileDecorator(); IIntegerLeaf NewIntegerLeaf(int payload); IIntegerTree NewIntegerTree(int payload); diff --git a/CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs b/CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs new file mode 100644 index 0000000..acf6e29 --- /dev/null +++ b/CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs @@ -0,0 +1,10 @@ +using System; + +namespace CsharpExtras.Event.Wrapper +{ + public interface IPreAccessWrapper + { + TReturn Get(Func f); + void Run(Action act); + } +} \ No newline at end of file diff --git a/CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs b/CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs new file mode 100644 index 0000000..6f0702a --- /dev/null +++ b/CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CsharpExtras.Event.Wrapper +{ + internal class PreAccessWrapperImpl : IPreAccessWrapper + { + private readonly TObj _object; + private readonly Action _preAccessAction; + + public PreAccessWrapperImpl(TObj obj, Action preAccessAction) + { + _object = obj; + _preAccessAction = preAccessAction ?? throw new ArgumentNullException(nameof(preAccessAction)); + } + + public void Run(Action act) + { + + } + + public TReturn Get(Func f) + { + //TODO: Implement properly + return f(_object); + } + } +} diff --git a/CsharpExtrasTest/Event/Wrapper/PreAccessWrapperTest.cs b/CsharpExtrasTest/Event/Wrapper/PreAccessWrapperTest.cs new file mode 100644 index 0000000..cfb2bdb --- /dev/null +++ b/CsharpExtrasTest/Event/Wrapper/PreAccessWrapperTest.cs @@ -0,0 +1,157 @@ +using CsharpExtras.Api; +using CsharpExtras.Event.Wrapper; +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CsharpExtrasTest.Event.Wrapper +{ + [TestFixture, Category("Unit")] + internal class PreAccessWrapperTest + { + private ICsharpExtrasApi Api { get; } = new CsharpExtrasApi(); + + [Test] + public void GIVEN_Wrapper_WHEN_Get_THEN_ExpectedValueReturned() + { + //Arrange + Mock mockHandler = new Mock(); + Mock mockObj = new Mock(); + Mock mockOtherObj = new Mock(); + Action preAccessAction = o => mockHandler.Object.Handle(o); + Mock> mockGetter = new Mock>(); + + mockHandler.Setup(x => x.Handle(It.IsAny())); + mockGetter.Setup(g => g.Get(mockObj.Object)).Returns(mockOtherObj.Object); + + IPreAccessWrapper wrapper + = Api.NewPreAccessWrapper(mockObj.Object, preAccessAction); + + //Act + ITestObj obj = wrapper.Get(mockGetter.Object.Get); + + //Assert + Assert.AreSame(mockOtherObj.Object, obj); + } + + [Test] + public void GIVEN_Wrapper_WHEN_Get_THEN_AllOperationsRunInExpectedOrder() + { + //Arrange + Mock mockHandler = new Mock(); + Mock mockObj = new Mock(); + Mock mockOtherObj = new Mock(); + Action preAccessAction = o => mockHandler.Object.Handle(o); + Mock> mockGetter = new Mock>(); + + + //Used to verify order of operations. See: https://stackoverflow.com/a/10609506/5134722 + int callOrder = 0; + mockHandler.Setup(x => x.Handle(It.IsAny())) + .Callback(() => Assert.AreEqual(0, callOrder++)).Verifiable(); + mockGetter.Setup(g => g.Get(mockObj.Object)).Returns(mockOtherObj.Object) + .Callback(() => Assert.AreEqual(1, callOrder++)).Verifiable(); + + IPreAccessWrapper wrapper + = Api.NewPreAccessWrapper(mockObj.Object, preAccessAction); + + //Act + wrapper.Get(mockGetter.Object.Get); + + //Assert + mockObj.Verify(); + mockHandler.Verify(); + } + + [Test] + public void GIVEN_Wrapper_WHEN_Run_THEN_AllOperationsRunInExpectedOrder() + { + //Arrange + Mock mockHandler = new Mock(); + Mock mockObj = new Mock(); + Action preAccessAction = o => mockHandler.Object.Handle(o); + + + //Used to verify order of operations. See: https://stackoverflow.com/a/10609506/5134722 + int callOrder = 0; + mockHandler.Setup(x => x.Handle(It.IsAny())) + .Callback(() => Assert.AreEqual(0, callOrder++)).Verifiable(); + mockObj.Setup(o => o.Foo()) + .Callback(() => Assert.AreEqual(1, callOrder++)).Verifiable(); + + IPreAccessWrapper wrapper + = Api.NewPreAccessWrapper(mockObj.Object, preAccessAction); + + //Act + wrapper.Run(o => o.Foo()); + + //Assert + mockObj.Verify(); + mockHandler.Verify(); + } + + [Test] + public void GIVEN_PreAccessException_WHEN_Run_THEN_ExceptionThrownAndActionNotExecuted() + { + //Arrange + Mock mockHandler = new Mock(); + Mock mockObj = new Mock(); + Action preAccessAction = o => mockHandler.Object.Handle(o); + + mockHandler.Setup(x => x.Handle(It.IsAny())) + .Throws(); + mockObj.Setup(o => o.Foo()).Verifiable(); + + IPreAccessWrapper wrapper + = Api.NewPreAccessWrapper(mockObj.Object, preAccessAction); + + //Act + Assert.Throws(() => wrapper.Run(o => o.Foo())); + + //Assert + mockObj.Verify(o => o.Foo(), Times.Never()); + } + + [Test] + public void GIVEN_PreAccessException_WHEN_Get_THEN_ExceptionThrownAndGetNotExecuted() + { + //Arrange + Mock mockHandler = new Mock(); + Mock mockObj = new Mock(); + Mock mockOtherObj = new Mock(); + Action preAccessAction = o => mockHandler.Object.Handle(o); + Mock> mockGetter = new Mock>(); + + mockHandler.Setup(x => x.Handle(It.IsAny())) + .Throws(); + mockGetter.Setup(g => g.Get(mockObj.Object)).Returns(mockOtherObj.Object) + .Verifiable(); + + IPreAccessWrapper wrapper + = Api.NewPreAccessWrapper(mockObj.Object, preAccessAction); + + //Act + Assert.Throws(() => wrapper.Get(mockGetter.Object.Get)); + + //Assert + mockGetter.Verify(g => g.Get(It.IsAny()), Times.Never()); + } + } + + interface ITestGetter + { + TResult Get(ITestObj obj); + } + + interface ITestObj + { + void Foo(); + } + + interface IEventHandler + { + void Handle(ITestObj obj); + } +} diff --git a/CsharpExtrasTest/Properties/AssemblyInfo.cs b/CsharpExtrasTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..92cc8e9 --- /dev/null +++ b/CsharpExtrasTest/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// In SDK-style projects such as this one, several assembly attributes that were historically +// defined in this file are now automatically added during build and populated with +// values defined in project properties. For details of which attributes are included +// and how to customise this process see: https://aka.ms/assembly-info-properties + + +// Setting ComVisible to false makes the types in this assembly not visible to COM +// components. If you need to access a type in this assembly from COM, set the ComVisible +// attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM. + +[assembly: Guid("7909326e-9e35-48ed-9e25-4b74d183b0a0")] + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] From df1245bd76921c2390bdabc2092537982dda77a6 Mon Sep 17 00:00:00 2001 From: cbhandal Date: Mon, 31 Jan 2022 12:07:53 +0000 Subject: [PATCH 2/3] Implementation and documentation --- CsharpExtras/Api/ICsharpExtrasApi.cs | 8 ++++++++ CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs | 15 +++++++++++++++ .../Event/Wrapper/PreAccessWrapperImpl.cs | 5 +++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CsharpExtras/Api/ICsharpExtrasApi.cs b/CsharpExtras/Api/ICsharpExtrasApi.cs index 5dca8a8..4a0a8b7 100644 --- a/CsharpExtras/Api/ICsharpExtrasApi.cs +++ b/CsharpExtras/Api/ICsharpExtrasApi.cs @@ -29,6 +29,14 @@ public interface ICsharpExtrasApi IComparer NewDescendingComparer(); IDirectoryDecorator NewDirectoryDecorator(); IValidator NewEmptyValidator(); + + /// + /// Creates a new pre-access wrapper, containing a wrapped object and a pre-access action to run before every access to that object + /// + /// The object type + /// An object instance which will be wrapped + /// An action to run before any access to the given object + /// A new pre-access wrapper object IPreAccessWrapper NewPreAccessWrapper(TObj obj, Action preAccessAction); IFileDecorator NewFileDecorator(); IIntegerLeaf NewIntegerLeaf(int payload); diff --git a/CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs b/CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs index acf6e29..3b5179c 100644 --- a/CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs +++ b/CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs @@ -2,9 +2,24 @@ namespace CsharpExtras.Event.Wrapper { + /// + /// Encapsulates access to some object, so that accessing that object is always preceded by a pre-access action + /// + /// public interface IPreAccessWrapper { + /// + /// Gets the result of applying some function to the object + /// + /// The return type of the function + /// The function to apply + /// The result of the function TReturn Get(Func f); + + /// + /// Runs the given action on the object + /// + /// The action to run on the object void Run(Action act); } } \ No newline at end of file diff --git a/CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs b/CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs index 6f0702a..403f100 100644 --- a/CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs +++ b/CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs @@ -17,12 +17,13 @@ public PreAccessWrapperImpl(TObj obj, Action preAccessAction) public void Run(Action act) { - + _preAccessAction(_object); + act(_object); } public TReturn Get(Func f) { - //TODO: Implement properly + _preAccessAction(_object); return f(_object); } } From 829ece4df59f2475afd372fbb4daec3bf5a56746 Mon Sep 17 00:00:00 2001 From: cbhandal Date: Mon, 31 Jan 2022 12:10:16 +0000 Subject: [PATCH 3/3] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bccd30..f5b391b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added general-purpose Sparse Array type for any amount of dimensions with builder and some basic functions (#108) - Added sparse 2D array type for 2D-specific operations (#120) - Added a shift operator for sparse arrays (#122) +- Added a pre-access wrapper, which wraps an object and ensures an action is run before any access to that object (#131_ ### Added - Support for writing a 1D one-based array to a 2D one-based array (#9)