diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 7ecd38a41..9230f13ed 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,41 +2,58 @@
"version": "2.0.0",
"tasks": [
{
- "label": "build",
- "command": "dotnet",
- "type": "process",
- "args": [
- "build",
- "${workspaceFolder}/src/AnalysisPrograms/AnalysisPrograms.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary"
- ],
- "problemMatcher": "$msCompile"
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/src/AnalysisPrograms/AnalysisPrograms.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
},
{
- "label": "publish",
- "command": "dotnet",
- "type": "process",
- "args": [
- "publish",
- "${workspaceFolder}/src/AnalysisPrograms/AnalysisPrograms.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary"
- ],
- "problemMatcher": "$msCompile"
+ "label": "test",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "test"
+ ],
+ "problemMatcher": "$msCompile",
+ "group": {
+ "kind": "test",
+ "isDefault": true
+ }
},
{
- "label": "watch",
- "command": "dotnet",
- "type": "process",
- "args": [
- "watch",
- "run",
- "${workspaceFolder}/src/AnalysisPrograms/AnalysisPrograms.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary"
- ],
- "problemMatcher": "$msCompile"
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/src/AnalysisPrograms/AnalysisPrograms.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "${workspaceFolder}/src/AnalysisPrograms/AnalysisPrograms.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
}
]
}
\ No newline at end of file
diff --git a/src/Acoustics.Shared/Extensions/ProcessExtensions.cs b/src/Acoustics.Shared/Extensions/ProcessExtensions.cs
new file mode 100644
index 000000000..c0d6918ac
--- /dev/null
+++ b/src/Acoustics.Shared/Extensions/ProcessExtensions.cs
@@ -0,0 +1,110 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
+//
+//
+// Defines the Utilities type.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace System
+{
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Runtime.InteropServices;
+ using static Acoustics.Shared.AppConfigHelper;
+
+ public static class ProcessExtensions
+ {
+ ///
+ /// A utility class to determine a process parent.
+ /// http://stackoverflow.com/a/3346055.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ParentProcessUtilities
+ {
+#if DEBUG
+ #pragma warning disable SA1310
+ // These members must match PROCESS_BASIC_INFORMATION
+ internal IntPtr Reserved1;
+
+ internal IntPtr PebBaseAddress;
+
+ internal IntPtr Reserved2_0;
+
+ internal IntPtr Reserved2_1;
+
+ internal IntPtr UniqueProcessId;
+
+ internal IntPtr InheritedFromUniqueProcessId;
+ #pragma warning restore
+
+ // TODO This import is windows only, replace with .NET Core API
+ [DllImport("ntdll.dll")]
+ private static extern int NtQueryInformationProcess(
+ IntPtr processHandle,
+ int processInformationClass,
+ ref ParentProcessUtilities processInformation,
+ int processInformationLength,
+ out int returnLength);
+
+ #pragma warning disable SA1202
+ ///
+ /// !WARNING Windows OS only
+ /// Gets the parent process of the current process.
+ /// Linux support blocked by https://github.com/dotnet/runtime/issues/24423.
+ ///
+ /// An instance of the Process class.
+ public static Process GetParentProcess()
+ {
+ return GetParentProcess(Process.GetCurrentProcess().Handle);
+ }
+
+ ///
+ /// !WARNING Windows OS only
+ /// Gets the parent process of specified process.
+ ///
+ /// The process id.
+ /// An instance of the Process class.
+ public static Process GetParentProcess(int id)
+ {
+ Process process = Process.GetProcessById(id);
+ return GetParentProcess(process.Handle);
+ }
+
+ ///
+ /// !WARNING Windows OS only
+ /// Gets the parent process of a specified process.
+ ///
+ /// The process handle.
+ /// An instance of the Process class.
+ public static Process GetParentProcess(IntPtr handle)
+ {
+ if (!IsWindows)
+ {
+ return null;
+ }
+
+ ParentProcessUtilities pbi = default;
+
+ int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out var _);
+ if (status != 0)
+ {
+ throw new Win32Exception(status);
+ }
+
+ try
+ {
+ return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
+ }
+ catch (ArgumentException)
+ {
+ // not found
+ return null;
+ }
+ }
+ #pragma warning restore
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Acoustics.Shared/Extensions/ExtensionsSystem.cs b/src/Acoustics.Shared/Extensions/SystemExtensions.cs
similarity index 76%
rename from src/Acoustics.Shared/Extensions/ExtensionsSystem.cs
rename to src/Acoustics.Shared/Extensions/SystemExtensions.cs
index 89c6c084a..a027abd32 100644
--- a/src/Acoustics.Shared/Extensions/ExtensionsSystem.cs
+++ b/src/Acoustics.Shared/Extensions/SystemExtensions.cs
@@ -1,5 +1,5 @@
// --------------------------------------------------------------------------------------------------------------------
-//
+//
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
//
//
@@ -9,27 +9,30 @@
namespace System
{
- using Collections.Generic;
- using Collections.Specialized;
- using ComponentModel;
- using Diagnostics;
- using IO;
- using Linq;
- using Linq.Expressions;
- using Runtime.InteropServices;
- using Runtime.Serialization;
- using Runtime.Serialization.Formatters.Binary;
- using Text;
- using Text.RegularExpressions;
-
- public static class ExtensionsGeneral
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.ComponentModel;
+ using System.IO;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using System.Runtime.Serialization;
+ using System.Runtime.Serialization.Formatters.Binary;
+ using System.Text;
+ using System.Text.RegularExpressions;
+
+ public static class SystemExtensions
{
- /// Deserialise byte array to object.
+ ///
+ /// Gets ExecutingDirectory.
+ ///
+ public static string ExecutingDirectory => Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location);
+
+ /// Deserialize byte array to object.
///
///
/// The bytes.
///
- /// Deserialised object.
+ /// Deserialized object.
///
public static object BinaryDeserialize(this byte[] bytes)
{
@@ -46,7 +49,7 @@ public static object BinaryDeserialize(this byte[] bytes)
}
///
- /// Deserialise byte array to object.
+ /// Deserialize byte array to object.
///
///
/// The bytes.
@@ -55,7 +58,7 @@ public static object BinaryDeserialize(this byte[] bytes)
/// The binder.
///
///
- /// Deserialised object.
+ /// Deserialized object.
///
public static object BinaryDeserialize(this byte[] bytes, SerializationBinder binder)
{
@@ -65,7 +68,6 @@ public static object BinaryDeserialize(this byte[] bytes, SerializationBinder bi
}
var formatter = new BinaryFormatter { Binder = binder };
-
using (var stream = new MemoryStream(bytes))
{
return formatter.Deserialize(stream);
@@ -73,12 +75,12 @@ public static object BinaryDeserialize(this byte[] bytes, SerializationBinder bi
}
///
- /// Convert an object to it's binary serialised form.
+ /// Convert an object to it's binary serialized form.
///
///
- /// Object to serialise.
+ /// Object to serialize.
///
- /// Serialised object.
+ /// Serialized object.
///
public static byte[] BinarySerialize(this object o)
{
@@ -219,11 +221,6 @@ public static string ToByteDisplay(this long byteCount)
return size;
}
- ///
- /// Gets ExecutingDirectory.
- ///
- public static string ExecutingDirectory => Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location);
-
///
/// Check if a dictionary has a value for a key.
///
@@ -310,20 +307,20 @@ public static void AddRange(this IList list, IEnumerable values)
/// IQueryable to page.
/// Page number (begins at 1).
/// Number of items per page.
- ///
+ /// Paged LINQ to SQL.
public static IQueryable Page(this IQueryable source, int page, int pageSize)
{
return source.Skip((page - 1) * pageSize).Take(pageSize);
}
///
- /// Paging for LINQ
+ /// Paging for LINQ.
///
/// IEnumerable of 'object type' to page.
/// IEnumerable to page.
/// Page number (begins at 1).
/// Number of items per page.
- ///
+ /// Paged LINQ.
public static IEnumerable Page(this IEnumerable source, int page, int pageSize)
{
return source.Skip((page - 1) * pageSize).Take(pageSize);
@@ -336,7 +333,7 @@ public static IEnumerable Page(this IEnumerable sourc
/// IQueryable to page.
/// Index of first item to return.
/// number of items to return.
- ///
+ /// Paged IQueryable object.
public static IQueryable PageByIndex(this IQueryable value, int? startIndex, int? length)
{
if (startIndex != null)
@@ -402,99 +399,20 @@ public static Expression> False()
return f => false;
}
- public static Expression> Or(this Expression> expr1,
- Expression> expr2)
+ public static Expression> Or(
+ this Expression> expr1,
+ Expression> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast());
- return Expression.Lambda>
- (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
+ return Expression.Lambda>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}
- public static Expression> And(this Expression> expr1,
- Expression> expr2)
+ public static Expression> And(
+ this Expression> expr1,
+ Expression> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast());
return Expression.Lambda>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
}
-
- public static class ProcessExtensions
- {
-#if DEBUG
-
- ///
- /// A utility class to determine a process parent.
- /// http://stackoverflow.com/a/3346055
- ///
- [StructLayout(LayoutKind.Sequential)]
- public struct ParentProcessUtilities
- {
- // These members must match PROCESS_BASIC_INFORMATION
- internal IntPtr Reserved1;
-
- internal IntPtr PebBaseAddress;
-
- internal IntPtr Reserved2_0;
-
- internal IntPtr Reserved2_1;
-
- internal IntPtr UniqueProcessId;
-
- internal IntPtr InheritedFromUniqueProcessId;
-
- [DllImport("ntdll.dll")]
- private static extern int NtQueryInformationProcess(
- IntPtr processHandle,
- int processInformationClass,
- ref ParentProcessUtilities processInformation,
- int processInformationLength,
- out int returnLength);
-
- ///
- /// Gets the parent process of the current process.
- ///
- /// An instance of the Process class.
- public static Process GetParentProcess()
- {
- return GetParentProcess(Process.GetCurrentProcess().Handle);
- }
-
- ///
- /// Gets the parent process of specified process.
- ///
- /// The process id.
- /// An instance of the Process class.
- public static Process GetParentProcess(int id)
- {
- Process process = Process.GetProcessById(id);
- return GetParentProcess(process.Handle);
- }
-
- ///
- /// Gets the parent process of a specified process.
- ///
- /// The process handle.
- /// An instance of the Process class.
- public static Process GetParentProcess(IntPtr handle)
- {
- ParentProcessUtilities pbi = new ParentProcessUtilities();
- int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out var returnLength);
- if (status != 0)
- {
- throw new Win32Exception(status);
- }
-
- try
- {
- return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
- }
- catch (ArgumentException)
- {
- // not found
- return null;
- }
- }
- }
-#endif
- }
}
\ No newline at end of file
diff --git a/src/AnalysisPrograms/Production/MainEntryUtilities.cs b/src/AnalysisPrograms/Production/MainEntryUtilities.cs
index 658994e00..d04fa8c81 100644
--- a/src/AnalysisPrograms/Production/MainEntryUtilities.cs
+++ b/src/AnalysisPrograms/Production/MainEntryUtilities.cs
@@ -73,8 +73,6 @@ internal enum Usages
NoAction,
}
- public static bool AppConfigOverridden { get; private set; }
-
///
/// Gets the default log level set by an environment variable.
///
@@ -101,6 +99,13 @@ internal enum Usages
///
internal static bool ApAutoAttach { get; private set; }
+ // https://stackoverflow.com/questions/3167617/determine-if-code-is-running-as-part-of-a-unit-test
+ internal static bool IsMsTestRunningMe { get; } =
+ AppDomain
+ .CurrentDomain
+ .GetAssemblies()
+ .Any(a => a.FullName.StartsWith("Microsoft.VisualStudio.TestPlatform.TestFramework"));
+
public static void SetLogVerbosity(LogVerbosity logVerbosity, bool quietConsole = false)
{
var modifiedLevel = VerbosityToLevel(logVerbosity);
@@ -279,21 +284,21 @@ internal static void HangBeforeExit()
// if Michael is debugging with visual studio, this will prevent the window closing.
Process parentProcess = ProcessExtensions.ParentProcessUtilities.GetParentProcess();
- if (parentProcess.ProcessName == "devenv")
+ if (Debugger.IsAttached)
{
- LoggedConsole.WriteSuccessLine("FINISHED: Press RETURN key to exit.");
- Console.ReadLine();
+ if (parentProcess == null)
+ {
+ LoggedConsole.WriteWarnLine("WARNING: Unable to detect parent process, this is a windows specific tool.");
+ }
+ else if (parentProcess?.ProcessName == "devenv")
+ {
+ LoggedConsole.WriteSuccessLine("FINISHED: Press RETURN key to exit.");
+ Console.ReadLine();
+ }
}
#endif
}
- // https://stackoverflow.com/questions/3167617/determine-if-code-is-running-as-part-of-a-unit-test
- internal static bool IsMsTestRunningMe { get; } =
- AppDomain
- .CurrentDomain
- .GetAssemblies()
- .Any(a => a.FullName.StartsWith("Microsoft.VisualStudio.TestPlatform.TestFramework"));
-
internal static void PrintUsage(string message, Usages usageStyle, string commandName = null)
{
//Contract.Requires(usageStyle != Usages.Single || commandName != null);
@@ -380,8 +385,6 @@ private static void PrepareForErrors()
{
ExitCode = ExceptionLookup.UnhandledExceptionErrorCode;
- AppConfigOverridden = CheckAndUpdateApplicationConfig();
-
if (CheckForDataAnnotations() is string message)
{
Console.WriteLine(message);
@@ -491,7 +494,6 @@ private static void LogProgramStats()
ProcessorCount,
ExecutionTime = (DateTime.Now - thisProcess.StartTime).TotalSeconds,
PeakWorkingSet = thisProcess.PeakWorkingSet64,
- AppConfigOverridden,
};
var statsString = "Programs stats:\n" + Json.SerializeToString(stats, prettyPrint: true);
diff --git a/tests/Acoustics.Test/AnalysisPrograms/Production/MainEntryUtilitiesTests.cs b/tests/Acoustics.Test/AnalysisPrograms/Production/MainEntryUtilitiesTests.cs
new file mode 100644
index 000000000..3776f0108
--- /dev/null
+++ b/tests/Acoustics.Test/AnalysisPrograms/Production/MainEntryUtilitiesTests.cs
@@ -0,0 +1,23 @@
+//
+// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
+//
+
+namespace Acoustics.Test.AnalysisPrograms.Production
+{
+ using System;
+ using Acoustics.Test.TestHelpers;
+ using global::AnalysisPrograms;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class MainEntryUtilitiesTests
+ {
+#if DEBUG
+ [TestMethod]
+ public void TestHangBeforeExitDoesNotThrowException()
+ {
+ Assertions.DoesNotThrow(() => MainEntry.HangBeforeExit());
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/tests/Acoustics.Test/Shared/Extensions/ProcessExtensionsTests.cs b/tests/Acoustics.Test/Shared/Extensions/ProcessExtensionsTests.cs
new file mode 100644
index 000000000..4e357d786
--- /dev/null
+++ b/tests/Acoustics.Test/Shared/Extensions/ProcessExtensionsTests.cs
@@ -0,0 +1,28 @@
+//
+// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
+//
+
+namespace Acoustics.Test.Shared.Extensions
+{
+#if DEBUG
+ using Acoustics.Test.TestHelpers;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using static System.ProcessExtensions.ParentProcessUtilities;
+
+ [TestClass]
+ public class ProcessExtensionsTests
+ {
+ [PlatformSpecificTestMethod("Windows", Reason = "https://github.com/dotnet/runtime/issues/24423")]
+ public void TestGetParentProcessReturnsProcessOnWindows()
+ {
+ Assert.IsNotNull(GetParentProcess());
+ }
+
+ [PlatformSpecificTestMethod("!Windows", Reason = "https://github.com/dotnet/runtime/issues/24423")]
+ public void TestGetParentProcessReturnsNullWhenNotWindows()
+ {
+ Assert.IsNull(GetParentProcess());
+ }
+ }
+#endif
+}
\ No newline at end of file
diff --git a/tests/Acoustics.Test/TestHelpers/Assertions.cs b/tests/Acoustics.Test/TestHelpers/Assertions.cs
index 2937e9b0c..d28f30a51 100644
--- a/tests/Acoustics.Test/TestHelpers/Assertions.cs
+++ b/tests/Acoustics.Test/TestHelpers/Assertions.cs
@@ -15,6 +15,12 @@ namespace Acoustics.Test.TestHelpers
public static class Assertions
{
+ public enum DiffStyle
+ {
+ Full,
+ Minimal,
+ }
+
[DebuggerHidden]
public static void AreEqual(this Assert assert, long expected, long actual, long delta, string message = null)
{
@@ -282,12 +288,6 @@ public static void NotContains(this StringAssert assert, string value, string su
$"String\n{value}\n should not contain `{substring}`. {message}");
}
- public enum DiffStyle
- {
- Full,
- Minimal,
- }
-
public static void StringEqualWithDiff(this Assert assert, string expectedValue, string actualValue)
{
ShouldEqualWithDiff(actualValue, expectedValue, DiffStyle.Full, Console.Out);
@@ -308,6 +308,25 @@ public static void ShouldEqualWithDiff(this string actualValue, string expectedV
ShouldEqualWithDiff(actualValue, expectedValue, diffStyle, Console.Out);
}
+ public static void DoesNotThrow(Action expressionUnderTest, string exceptionMessage = "Expected exception was thrown by target of invocation.")
+ where T : Exception
+ {
+ try
+ {
+ expressionUnderTest();
+ }
+ catch (T)
+ {
+ Assert.Fail(exceptionMessage);
+ }
+ catch (Exception)
+ {
+ Assert.IsTrue(true);
+ }
+
+ Assert.IsTrue(true);
+ }
+
public static void ShouldEqualWithDiff(this string actualValue, string expectedValue, DiffStyle diffStyle, TextWriter output)
{
if (actualValue == null || expectedValue == null)