From 2d12919b4ce7b23b4e7d8d5c2bde47a8d71ff2a9 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 30 Sep 2024 20:46:53 +0200 Subject: [PATCH 1/5] :memo: updated DocFX --- .docfx/api/namespaces/Codebelt.Extensions.Xunit.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.md b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.md index 5992b23..6ebc893 100644 --- a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.md +++ b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.md @@ -13,3 +13,4 @@ Complements: [xUnit: Capturing Output](https://xunit.net/docs/capturing-output) |Type|Ext|Methods| |--:|:-:|---| |ITestOutputHelper|⬇️|`WriteLines`| +|String|⬇️|`ReplaceLineEndings` (TFM netstandard2.0)| From 44d88fbfca0c9e49e9df63811982ec86b64bc1af Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 30 Sep 2024 20:47:53 +0200 Subject: [PATCH 2/5] :arrow_up: bump dependencies --- Directory.Build.props | 4 ++-- .../Codebelt.Extensions.Xunit.Hosting.AspNetCore.csproj | 4 ++-- .../Codebelt.Extensions.Xunit.Hosting.csproj | 2 +- .../Codebelt.Extensions.Xunit.csproj | 6 +++--- ...odebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 51c0b55..edd3089 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -70,8 +70,8 @@ - - + + all diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Codebelt.Extensions.Xunit.Hosting.AspNetCore.csproj b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Codebelt.Extensions.Xunit.Hosting.AspNetCore.csproj index 02f5b10..0d0173e 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Codebelt.Extensions.Xunit.Hosting.AspNetCore.csproj +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Codebelt.Extensions.Xunit.Hosting.AspNetCore.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj b/src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj index d9ac137..d8b4cf5 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj +++ b/src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj b/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj index ef274f3..f09b8d9 100644 --- a/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj +++ b/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj @@ -5,13 +5,13 @@ - The Cuemon.Extensions.Xunit namespace contains types that provides a uniform way of doing unit testing. The namespace relates to the Xunit.Abstractions namespace. + The Codebelt.Extensions.Xunit namespace contains types that provides a uniform way of doing unit testing. The namespace relates to the Xunit.Abstractions namespace. test test-output test-disposable test-cleanup - - + + diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj index 22e9295..f87662d 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj @@ -6,9 +6,9 @@ - - - + + + From 0d4e3cec2367b79ad17c0f163d52dccfeb62f459 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 30 Sep 2024 20:50:40 +0200 Subject: [PATCH 3/5] :twisted_rightwards_arrows: extracted and migrated Cuemon.Disposable abstraction to Test; this was done to reduce the risk of circular reference challenges as Cuemon relies on this library. Functionality wise, the change is backward compatible. --- .../GlobalSuppressions.cs | 8 ++ src/Codebelt.Extensions.Xunit/Test.cs | 44 ++++++- .../Assets/ManagedDisposable.cs | 27 ++++ .../Assets/UnmanagedDisposable.cs | 123 ++++++++++++++++++ .../Codebelt.Extensions.Xunit.Tests.csproj | 4 + .../DisposableTest.cs | 61 +++++++++ 6 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 src/Codebelt.Extensions.Xunit/GlobalSuppressions.cs create mode 100644 test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs create mode 100644 test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs create mode 100644 test/Codebelt.Extensions.Xunit.Tests/DisposableTest.cs diff --git a/src/Codebelt.Extensions.Xunit/GlobalSuppressions.cs b/src/Codebelt.Extensions.Xunit/GlobalSuppressions.cs new file mode 100644 index 0000000..fbdf1b5 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Code Smell", "S3881:\"IDisposable\" should be implemented correctly", Justification = "This is a base class implementation of the IDisposable interface tailored to avoid wrong implementations.", Scope = "type", Target = "~T:Codebelt.Extensions.Xunit.Test")] diff --git a/src/Codebelt.Extensions.Xunit/Test.cs b/src/Codebelt.Extensions.Xunit/Test.cs index 9ec94f3..e252682 100644 --- a/src/Codebelt.Extensions.Xunit/Test.cs +++ b/src/Codebelt.Extensions.Xunit/Test.cs @@ -9,9 +9,8 @@ namespace Codebelt.Extensions.Xunit /// /// Represents the base class from which all implementations of unit testing should derive. /// - /// /// - public abstract class Test : Disposable, ITest + public abstract class Test : ITest { /// /// Provides a way, with wildcard support, to determine if matches . @@ -71,10 +70,47 @@ protected Test(ITestOutputHelper output = null, Type callerType = null) protected bool HasTestOutput => TestOutput != null; /// - /// Called when this object is being disposed by either or having disposing set to true and is false. + /// Gets a value indicating whether this object is disposed. /// - protected override void OnDisposeManagedResources() + /// true if this object is disposed; otherwise, false. + public bool Disposed { get; private set; } + + /// + /// Called when this object is being disposed by either or having disposing set to true and is false. + /// + protected virtual void OnDisposeManagedResources() + { + } + + /// + /// Called when this object is being disposed by either or and is false. + /// + protected virtual void OnDisposeUnmanagedResources() + { + } + + /// + /// Releases all resources used by the object. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected void Dispose(bool disposing) { + if (Disposed) { return; } + if (disposing) + { + OnDisposeManagedResources(); + } + OnDisposeUnmanagedResources(); + Disposed = true; } } } diff --git a/test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs b/test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs new file mode 100644 index 0000000..fc7a706 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs @@ -0,0 +1,27 @@ +using System.IO; +using Cuemon; + +namespace Codebelt.Extensions.Xunit.Assets +{ + public class ManagedDisposable : Disposable + { + public ManagedDisposable() + { + Stream = new MemoryStream(); + } + + public MemoryStream Stream { get; private set; } + + protected override void OnDisposeManagedResources() + { + try + { + Stream?.Dispose(); + } + finally + { + Stream = null; + } + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs b/test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs new file mode 100644 index 0000000..852f6cd --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs @@ -0,0 +1,123 @@ +using System; +using System.Runtime.InteropServices; +using Cuemon; +#if NET48_OR_GREATER +using NativeLibraryLoader; +#endif +namespace Codebelt.Extensions.Xunit.Assets +{ + public class UnmanagedDisposable : FinalizeDisposable + { + internal IntPtr _handle = IntPtr.Zero; + internal IntPtr _libHandle = IntPtr.Zero; + + public delegate bool CloseHandle(IntPtr hObject); + + public delegate IntPtr CreateFileDelegate(string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + public delegate IntPtr PtSname(int fd); + +#if NET48_OR_GREATER + internal NativeLibrary _nativeLibrary; +#endif + + public UnmanagedDisposable() + { +#if NET6_0_OR_GREATER + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + if (NativeLibrary.TryLoad("kernel32.dll", GetType().Assembly, DllImportSearchPath.System32, out _libHandle)) + { + if (NativeLibrary.TryGetExport(_libHandle, "CreateFileW", out var functionHandle)) + { + var createFileFunc = Marshal.GetDelegateForFunctionPointer(functionHandle); + _handle = createFileFunc(@"C:\TestFile.txt", + 0x80000000, //access read-only + 1, //share-read + IntPtr.Zero, + 3, //open existing + 0, + IntPtr.Zero); + } + } + } + else if (Environment.OSVersion.Platform == PlatformID.Unix) + { + if (NativeLibrary.TryLoad("libc.so.6", GetType().Assembly, DllImportSearchPath.SafeDirectories, out _libHandle)) + { + _handle = _libHandle; // i don't know of any native methods on unix + } + } +#else + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + _nativeLibrary = new NativeLibrary("kernel32.dll"); + _libHandle = _nativeLibrary.Handle; + var functionHandle = _nativeLibrary.LoadFunction("CreateFileW"); + var createFileFunc = Marshal.GetDelegateForFunctionPointer(functionHandle); + _handle = createFileFunc(@"C:\TestFile.txt", + 0x80000000, //access read-only + 1, //share-read + IntPtr.Zero, + 3, //open existing + 0, + IntPtr.Zero); + } + else if (Environment.OSVersion.Platform == PlatformID.Unix) + { + _nativeLibrary = new NativeLibrary("libc.so.6"); + _libHandle = _nativeLibrary.Handle; + _handle = _libHandle; // i don't know of any native methods on unix + } +#endif + } + + protected override void OnDisposeManagedResources() + { + + } + + protected override void OnDisposeUnmanagedResources() + { +#if NET6_0_OR_GREATER + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + if (_handle != IntPtr.Zero) + { + if (NativeLibrary.TryGetExport(_libHandle, "CloseHandle", out var closeHandle)) + { + var closeHandleAction = Marshal.GetDelegateForFunctionPointer(closeHandle); + closeHandleAction(_handle); + } + } + NativeLibrary.Free(_libHandle); + } + else if (Environment.OSVersion.Platform == PlatformID.Unix) + { + NativeLibrary.Free(_libHandle); + } +#else + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + if (_handle != IntPtr.Zero) + { + var closeHandle = _nativeLibrary.LoadFunction("CloseHandle"); + var closeHandleAction = Marshal.GetDelegateForFunctionPointer(closeHandle); + closeHandleAction(_handle); + } + _nativeLibrary.Dispose(); + } + else if (Environment.OSVersion.Platform == PlatformID.Unix) + { + _nativeLibrary.Dispose(); + } +#endif + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj b/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj index 421731e..9fe983e 100644 --- a/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj +++ b/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj @@ -4,4 +4,8 @@ Codebelt.Extensions.Xunit + + + + diff --git a/test/Codebelt.Extensions.Xunit.Tests/DisposableTest.cs b/test/Codebelt.Extensions.Xunit.Tests/DisposableTest.cs new file mode 100644 index 0000000..e0646e9 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Tests/DisposableTest.cs @@ -0,0 +1,61 @@ +using System; +using Codebelt.Extensions.Xunit.Assets; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit +{ + public class DisposableTest : Test + { + public DisposableTest(ITestOutputHelper output) : base(output) + { + } + + + + [Fact] + public void ManagedDisposable_VerifyThatAssetIsBeingDisposed() + { + ManagedDisposable mdRef = null; + using (var md = new ManagedDisposable()) + { + mdRef = md; + Assert.NotNull(md.Stream); + Assert.Equal(0, md.Stream.Length); + Assert.False(mdRef.Disposed); + } + Assert.NotNull(mdRef); + Assert.Null(mdRef.Stream); + Assert.True(mdRef.Disposed); + } + + private WeakReference unmanaged = null; + + [Fact] + public void UnmanagedDisposable_VerifyThatAssetIsBeingDisposedOnFinalize() + { + Action body = () => + { + var o = new UnmanagedDisposable(); + Assert.NotEqual(IntPtr.Zero, o._libHandle); + Assert.NotEqual(IntPtr.Zero, o._handle); + unmanaged = new WeakReference(o, true); + }; + + try + { + body(); + } + finally + { + GC.Collect(0, GCCollectionMode.Forced); + GC.WaitForPendingFinalizers(); + } + + if (unmanaged.TryGetTarget(out var ud2)) + { + Assert.True(ud2.Disposed); + } + } + } +} From 8677dd66698c24b25b21f8b54a4f07804e11bd6c Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 30 Sep 2024 21:23:01 +0200 Subject: [PATCH 4/5] :white_check_mark: applied some suggestions from coderabbit --- .../Assets/ManagedDisposable.cs | 3 +-- .../Assets/UnmanagedDisposable.cs | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs b/test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs index fc7a706..9720eeb 100644 --- a/test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs +++ b/test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs @@ -1,9 +1,8 @@ using System.IO; -using Cuemon; namespace Codebelt.Extensions.Xunit.Assets { - public class ManagedDisposable : Disposable + public class ManagedDisposable : Test { public ManagedDisposable() { diff --git a/test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs b/test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs index 852f6cd..fb07024 100644 --- a/test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs +++ b/test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs @@ -1,12 +1,11 @@ using System; using System.Runtime.InteropServices; -using Cuemon; #if NET48_OR_GREATER using NativeLibraryLoader; #endif namespace Codebelt.Extensions.Xunit.Assets { - public class UnmanagedDisposable : FinalizeDisposable + public class UnmanagedDisposable : Test { internal IntPtr _handle = IntPtr.Zero; internal IntPtr _libHandle = IntPtr.Zero; @@ -30,7 +29,7 @@ public delegate IntPtr CreateFileDelegate(string lpFileName, public UnmanagedDisposable() { #if NET6_0_OR_GREATER - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { if (NativeLibrary.TryLoad("kernel32.dll", GetType().Assembly, DllImportSearchPath.System32, out _libHandle)) { @@ -47,7 +46,7 @@ public UnmanagedDisposable() } } } - else if (Environment.OSVersion.Platform == PlatformID.Unix) + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { if (NativeLibrary.TryLoad("libc.so.6", GetType().Assembly, DllImportSearchPath.SafeDirectories, out _libHandle)) { @@ -55,7 +54,7 @@ public UnmanagedDisposable() } } #else - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _nativeLibrary = new NativeLibrary("kernel32.dll"); _libHandle = _nativeLibrary.Handle; @@ -69,7 +68,7 @@ public UnmanagedDisposable() 0, IntPtr.Zero); } - else if (Environment.OSVersion.Platform == PlatformID.Unix) + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { _nativeLibrary = new NativeLibrary("libc.so.6"); _libHandle = _nativeLibrary.Handle; @@ -78,6 +77,12 @@ public UnmanagedDisposable() #endif } + ~UnmanagedDisposable() + { + Dispose(false); + } + + protected override void OnDisposeManagedResources() { @@ -86,7 +91,7 @@ protected override void OnDisposeManagedResources() protected override void OnDisposeUnmanagedResources() { #if NET6_0_OR_GREATER - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { if (_handle != IntPtr.Zero) { @@ -98,12 +103,12 @@ protected override void OnDisposeUnmanagedResources() } NativeLibrary.Free(_libHandle); } - else if (Environment.OSVersion.Platform == PlatformID.Unix) + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { NativeLibrary.Free(_libHandle); } #else - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { if (_handle != IntPtr.Zero) { @@ -113,7 +118,7 @@ protected override void OnDisposeUnmanagedResources() } _nativeLibrary.Dispose(); } - else if (Environment.OSVersion.Platform == PlatformID.Unix) + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { _nativeLibrary.Dispose(); } From c50d29bb431626fb93d351136a190b2b5f517fa8 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 30 Sep 2024 21:23:16 +0200 Subject: [PATCH 5/5] :pencil2: fixed Disposable -> Test --- src/Codebelt.Extensions.Xunit/Test.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit/Test.cs b/src/Codebelt.Extensions.Xunit/Test.cs index e252682..02172cb 100644 --- a/src/Codebelt.Extensions.Xunit/Test.cs +++ b/src/Codebelt.Extensions.Xunit/Test.cs @@ -70,9 +70,9 @@ protected Test(ITestOutputHelper output = null, Type callerType = null) protected bool HasTestOutput => TestOutput != null; /// - /// Gets a value indicating whether this object is disposed. + /// Gets a value indicating whether this object is disposed. /// - /// true if this object is disposed; otherwise, false. + /// true if this object is disposed; otherwise, false. public bool Disposed { get; private set; } /// @@ -90,7 +90,7 @@ protected virtual void OnDisposeUnmanagedResources() } /// - /// Releases all resources used by the object. + /// Releases all resources used by the object. /// public void Dispose() { @@ -99,7 +99,7 @@ public void Dispose() } /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected void Dispose(bool disposing)