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)|
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/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..02172cb 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.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 @@
-
-
-
+
+
+
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..9720eeb
--- /dev/null
+++ b/test/Codebelt.Extensions.Xunit.Tests/Assets/ManagedDisposable.cs
@@ -0,0 +1,26 @@
+using System.IO;
+
+namespace Codebelt.Extensions.Xunit.Assets
+{
+ public class ManagedDisposable : Test
+ {
+ 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..fb07024
--- /dev/null
+++ b/test/Codebelt.Extensions.Xunit.Tests/Assets/UnmanagedDisposable.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Runtime.InteropServices;
+#if NET48_OR_GREATER
+using NativeLibraryLoader;
+#endif
+namespace Codebelt.Extensions.Xunit.Assets
+{
+ public class UnmanagedDisposable : Test
+ {
+ 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 (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ 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 (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ 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 (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ _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 (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ _nativeLibrary = new NativeLibrary("libc.so.6");
+ _libHandle = _nativeLibrary.Handle;
+ _handle = _libHandle; // i don't know of any native methods on unix
+ }
+#endif
+ }
+
+ ~UnmanagedDisposable()
+ {
+ Dispose(false);
+ }
+
+
+ protected override void OnDisposeManagedResources()
+ {
+
+ }
+
+ protected override void OnDisposeUnmanagedResources()
+ {
+#if NET6_0_OR_GREATER
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ if (_handle != IntPtr.Zero)
+ {
+ if (NativeLibrary.TryGetExport(_libHandle, "CloseHandle", out var closeHandle))
+ {
+ var closeHandleAction = Marshal.GetDelegateForFunctionPointer(closeHandle);
+ closeHandleAction(_handle);
+ }
+ }
+ NativeLibrary.Free(_libHandle);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ NativeLibrary.Free(_libHandle);
+ }
+#else
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ if (_handle != IntPtr.Zero)
+ {
+ var closeHandle = _nativeLibrary.LoadFunction("CloseHandle");
+ var closeHandleAction = Marshal.GetDelegateForFunctionPointer(closeHandle);
+ closeHandleAction(_handle);
+ }
+ _nativeLibrary.Dispose();
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ _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);
+ }
+ }
+ }
+}