Browse Source

Add ProcessRunner and PIDFile (#4225)

* feat(Foundation): PIDFile and ProcessRunner #4064
* feat(Thread): optional signal blocking on POSIX #2978
* fix(ProcessRunner):remove logger, code enhancement #4225
* feat(Foundation): add PIDFile and ProcessRunner Tests #4064
* fix(Foundation): failing ProcessRunner Test #4064
* fix(PIDFile): remove append argument #4064
* remove Windows TODO from ProcessRunner #4064
* feat(ProcessRunnerTest): add line to checkTimeout #4064
* fix(ProcessRunner): add done flag to run() #4064
* fix(ProcessRunnerTest): add missing pidFile argument #4064
* chore(ProcessRunner): remove comments #4064
* fix(ProcessRunner): add runCount flag #4064
* fix(test): SharedLibrary and Class tests paths
* fix(ProcessRunner): thread sanitizer reported data races #4064
* fix(build): pass env var to testrunner #4064
* chore(PIDFile): remove ; in comments #4064
* feat(ProcessRunner): add Win argument format #4064
* fix(Tests): add ProcessRunnerTest to vcxproj #4064
* fix(Tests): change path to TestApp #4064
* feat(Tests): windows processrunner tests #4064
* fix(Tests): duplicate  ProcessRunnerTest in TestSuite vcxproj  #4064
* fix(CodeQL): sw declaration hides variable  #4064
* fix test binaries path for cmake
* fix(Build): missing include/PIDFile.h buildWin #4064
* fix(Build): add PocoFoundation depend in buildWin #4064
* feat(ProcessRunner): test process launching multiple threads #2976

---------

Co-authored-by: Pavle <[email protected]>
Co-authored-by: Alex Fabijanic <[email protected]>
Pavle Dragisic 2 years ago
parent
commit
70bb3a40de
36 changed files with 1469 additions and 95 deletions
  1. 1 1
      Data/src/sql-parser/src/sqlparser_win.h
  2. 4 0
      Foundation/Foundation_vs140.vcxproj
  3. 12 0
      Foundation/Foundation_vs140.vcxproj.filters
  4. 4 0
      Foundation/Foundation_vs150.vcxproj
  5. 12 0
      Foundation/Foundation_vs150.vcxproj.filters
  6. 4 0
      Foundation/Foundation_vs160.vcxproj
  7. 12 0
      Foundation/Foundation_vs160.vcxproj.filters
  8. 4 0
      Foundation/Foundation_vs170.vcxproj
  9. 12 0
      Foundation/Foundation_vs170.vcxproj.filters
  10. 1 1
      Foundation/Makefile
  11. 101 0
      Foundation/include/Poco/PIDFile.h
  12. 199 0
      Foundation/include/Poco/ProcessRunner.h
  13. 2 1
      Foundation/include/Poco/Process_UNIX.h
  14. 14 2
      Foundation/include/Poco/Thread.h
  15. 1 0
      Foundation/include/Poco/Thread_POSIX.h
  16. 122 0
      Foundation/src/PIDFile.cpp
  17. 217 0
      Foundation/src/ProcessRunner.cpp
  18. 8 2
      Foundation/src/Thread.cpp
  19. 15 0
      Foundation/src/Thread_POSIX.cpp
  20. 1 1
      Foundation/testsuite/Makefile-Driver
  21. 54 36
      Foundation/testsuite/TestApp_vs170.vcxproj
  22. 2 0
      Foundation/testsuite/TestSuite_vs170.vcxproj
  23. 6 0
      Foundation/testsuite/TestSuite_vs170.vcxproj.filters
  24. 49 28
      Foundation/testsuite/src/ClassLoaderTest.cpp
  25. 1 0
      Foundation/testsuite/src/ClassLoaderTest.h
  26. 348 0
      Foundation/testsuite/src/ProcessRunnerTest.cpp
  27. 44 0
      Foundation/testsuite/src/ProcessRunnerTest.h
  28. 3 0
      Foundation/testsuite/src/ProcessTest.cpp
  29. 1 0
      Foundation/testsuite/src/ProcessTest.h
  30. 2 0
      Foundation/testsuite/src/ProcessesTestSuite.cpp
  31. 43 21
      Foundation/testsuite/src/SharedLibraryTest.cpp
  32. 1 0
      Foundation/testsuite/src/SharedLibraryTest.h
  33. 165 0
      Foundation/testsuite/src/TestApp.cpp
  34. 1 1
      build/script/runtests.sh
  35. 2 0
      poco_env.bash
  36. 1 1
      tsan.suppress

+ 1 - 1
Data/src/sql-parser/src/sqlparser_win.h

@@ -11,7 +11,7 @@
 
 #ifdef Data_API
 	#define SQLParser_API Data_API
-	#ifdef Data_EXPORTS
+	#if defined(Data_EXPORTS) && !defined(SQLParser_EXPORTS)
 		#define SQLParser_EXPORTS
 	#endif
 #else

+ 4 - 0
Foundation/Foundation_vs140.vcxproj

@@ -1093,6 +1093,7 @@
     </ClCompile>
     <ClCompile Include="src\pcre2_valid_utf.c" />
     <ClCompile Include="src\pcre2_xclass.c" />
+    <ClCompile Include="src\PIDFile.cpp" />
     <ClCompile Include="src\Pipe.cpp" />
     <ClCompile Include="src\PipeImpl.cpp" />
     <ClCompile Include="src\PipeImpl_DUMMY.cpp">
@@ -1168,6 +1169,7 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release_static_mt|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release_static_mt|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunner.cpp" />
     <ClCompile Include="src\PurgeStrategy.cpp" />
     <ClCompile Include="src\Random.cpp" />
     <ClCompile Include="src\RandomStream.cpp" />
@@ -1655,6 +1657,7 @@
     <ClInclude Include="include\Poco\Path_WIN32U.h" />
     <ClInclude Include="include\Poco\PatternFormatter.h" />
     <ClInclude Include="include\Poco\PBKDF2Engine.h" />
+    <ClInclude Include="include\Poco\PIDFile.h" />
     <ClInclude Include="include\Poco\Pipe.h" />
     <ClInclude Include="include\Poco\PipeImpl.h" />
     <ClInclude Include="include\Poco\PipeImpl_DUMMY.h" />
@@ -1673,6 +1676,7 @@
     <ClInclude Include="include\Poco\Process.h" />
     <ClInclude Include="include\Poco\Process_UNIX.h" />
     <ClInclude Include="include\Poco\Process_WIN32U.h" />
+    <ClInclude Include="include\Poco\ProcessRunner.h" />
     <ClInclude Include="include\Poco\PurgeStrategy.h" />
     <ClInclude Include="include\Poco\Random.h" />
     <ClInclude Include="include\Poco\RandomStream.h" />

+ 12 - 0
Foundation/Foundation_vs140.vcxproj.filters

@@ -642,6 +642,9 @@
     <ClCompile Include="src\NamedMutex_WIN32U.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\PIDFile.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\Pipe.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -669,6 +672,9 @@
     <ClCompile Include="src\Process_WIN32U.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunner.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\SharedMemory.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -1514,6 +1520,9 @@
     <ClInclude Include="include\Poco\NamedMutex_WIN32U.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="include\Poco\PIDFile.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="include\Poco\Pipe.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
@@ -1541,6 +1550,9 @@
     <ClInclude Include="include\Poco\Process_WIN32U.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="include\Poco\ProcessRunner.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="include\Poco\SharedMemory.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>

+ 4 - 0
Foundation/Foundation_vs150.vcxproj

@@ -1093,6 +1093,7 @@
     </ClCompile>
     <ClCompile Include="src\pcre2_valid_utf.c" />
     <ClCompile Include="src\pcre2_xclass.c" />
+    <ClCompile Include="src\PIDFile.cpp" />
     <ClCompile Include="src\Pipe.cpp" />
     <ClCompile Include="src\PipeImpl.cpp" />
     <ClCompile Include="src\PipeImpl_DUMMY.cpp">
@@ -1168,6 +1169,7 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release_static_mt|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release_static_mt|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunner.cpp" />
     <ClCompile Include="src\PurgeStrategy.cpp" />
     <ClCompile Include="src\Random.cpp" />
     <ClCompile Include="src\RandomStream.cpp" />
@@ -1655,6 +1657,7 @@
     <ClInclude Include="include\Poco\Path_WIN32U.h" />
     <ClInclude Include="include\Poco\PatternFormatter.h" />
     <ClInclude Include="include\Poco\PBKDF2Engine.h" />
+    <ClInclude Include="include\Poco\PIDFile.h" />
     <ClInclude Include="include\Poco\Pipe.h" />
     <ClInclude Include="include\Poco\PipeImpl.h" />
     <ClInclude Include="include\Poco\PipeImpl_DUMMY.h" />
@@ -1673,6 +1676,7 @@
     <ClInclude Include="include\Poco\Process.h" />
     <ClInclude Include="include\Poco\Process_UNIX.h" />
     <ClInclude Include="include\Poco\Process_WIN32U.h" />
+    <ClInclude Include="include\Poco\ProcessRunner.h" />
     <ClInclude Include="include\Poco\PurgeStrategy.h" />
     <ClInclude Include="include\Poco\Random.h" />
     <ClInclude Include="include\Poco\RandomStream.h" />

+ 12 - 0
Foundation/Foundation_vs150.vcxproj.filters

@@ -642,6 +642,9 @@
     <ClCompile Include="src\NamedMutex_WIN32U.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\PIDFile.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\Pipe.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -669,6 +672,9 @@
     <ClCompile Include="src\Process_WIN32U.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunner.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\SharedMemory.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -1514,6 +1520,9 @@
     <ClInclude Include="include\Poco\NamedMutex_WIN32U.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="include\Poco\PIDFile.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="include\Poco\Pipe.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
@@ -1541,6 +1550,9 @@
     <ClInclude Include="include\Poco\Process_WIN32U.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="include\Poco\ProcessRunner.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="include\Poco\SharedMemory.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>

+ 4 - 0
Foundation/Foundation_vs160.vcxproj

@@ -1099,6 +1099,7 @@
     </ClCompile>
     <ClCompile Include="src\pcre2_valid_utf.c" />
     <ClCompile Include="src\pcre2_xclass.c" />
+    <ClCompile Include="src\PIDFile.cpp" />
     <ClCompile Include="src\Pipe.cpp" />
     <ClCompile Include="src\PipeImpl.cpp" />
     <ClCompile Include="src\PipeImpl_DUMMY.cpp">
@@ -1174,6 +1175,7 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release_static_mt|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release_static_mt|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunner.cpp" />
     <ClCompile Include="src\PurgeStrategy.cpp" />
     <ClCompile Include="src\Random.cpp" />
     <ClCompile Include="src\RandomStream.cpp" />
@@ -1661,6 +1663,7 @@
     <ClInclude Include="include\Poco\Path_WIN32U.h" />
     <ClInclude Include="include\Poco\PatternFormatter.h" />
     <ClInclude Include="include\Poco\PBKDF2Engine.h" />
+    <ClInclude Include="include\Poco\PIDFile.h" />
     <ClInclude Include="include\Poco\Pipe.h" />
     <ClInclude Include="include\Poco\PipeImpl.h" />
     <ClInclude Include="include\Poco\PipeImpl_DUMMY.h" />
@@ -1679,6 +1682,7 @@
     <ClInclude Include="include\Poco\Process.h" />
     <ClInclude Include="include\Poco\Process_UNIX.h" />
     <ClInclude Include="include\Poco\Process_WIN32U.h" />
+    <ClInclude Include="include\Poco\ProcessRunner.h" />
     <ClInclude Include="include\Poco\PurgeStrategy.h" />
     <ClInclude Include="include\Poco\Random.h" />
     <ClInclude Include="include\Poco\RandomStream.h" />

+ 12 - 0
Foundation/Foundation_vs160.vcxproj.filters

@@ -642,6 +642,9 @@
     <ClCompile Include="src\NamedMutex_WIN32U.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\PIDFile.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\Pipe.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -669,6 +672,9 @@
     <ClCompile Include="src\Process_WIN32U.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunner.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\SharedMemory.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -1514,6 +1520,9 @@
     <ClInclude Include="include\Poco\NamedMutex_WIN32U.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="include\Poco\PIDFile.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="include\Poco\Pipe.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
@@ -1541,6 +1550,9 @@
     <ClInclude Include="include\Poco\Process_WIN32U.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="include\Poco\ProcessRunner.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="include\Poco\SharedMemory.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>

+ 4 - 0
Foundation/Foundation_vs170.vcxproj

@@ -1550,6 +1550,7 @@
     </ClCompile>
     <ClCompile Include="src\pcre2_valid_utf.c" />
     <ClCompile Include="src\pcre2_xclass.c" />
+    <ClCompile Include="src\PIDFile.cpp" />
     <ClCompile Include="src\Pipe.cpp" />
     <ClCompile Include="src\PipeImpl.cpp" />
     <ClCompile Include="src\PipeImpl_DUMMY.cpp">
@@ -1655,6 +1656,7 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release_static_mt|x64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release_static_mt|ARM64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunner.cpp" />
     <ClCompile Include="src\PurgeStrategy.cpp" />
     <ClCompile Include="src\Random.cpp" />
     <ClCompile Include="src\RandomStream.cpp" />
@@ -2244,6 +2246,7 @@
     <ClInclude Include="include\Poco\Path_WIN32U.h" />
     <ClInclude Include="include\Poco\PatternFormatter.h" />
     <ClInclude Include="include\Poco\PBKDF2Engine.h" />
+    <ClInclude Include="include\Poco\PIDFile.h" />
     <ClInclude Include="include\Poco\Pipe.h" />
     <ClInclude Include="include\Poco\PipeImpl.h" />
     <ClInclude Include="include\Poco\PipeImpl_DUMMY.h" />
@@ -2262,6 +2265,7 @@
     <ClInclude Include="include\Poco\Process.h" />
     <ClInclude Include="include\Poco\Process_UNIX.h" />
     <ClInclude Include="include\Poco\Process_WIN32U.h" />
+    <ClInclude Include="include\Poco\ProcessRunner.h" />
     <ClInclude Include="include\Poco\PurgeStrategy.h" />
     <ClInclude Include="include\Poco\Random.h" />
     <ClInclude Include="include\Poco\RandomStream.h" />

+ 12 - 0
Foundation/Foundation_vs170.vcxproj.filters

@@ -642,6 +642,9 @@
     <ClCompile Include="src\NamedMutex_WIN32U.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\PIDFile.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\Pipe.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -669,6 +672,9 @@
     <ClCompile Include="src\Process_WIN32U.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunner.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\SharedMemory.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -1514,6 +1520,9 @@
     <ClInclude Include="include\Poco\NamedMutex_WIN32U.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="include\Poco\PIDFile.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="include\Poco\Pipe.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
@@ -1541,6 +1550,9 @@
     <ClInclude Include="include\Poco\Process_WIN32U.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="include\Poco\ProcessRunner.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="include\Poco\SharedMemory.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>

+ 1 - 1
Foundation/Makefile

@@ -19,7 +19,7 @@ objects = ArchiveStrategy Ascii ASCIIEncoding AsyncChannel ActiveThreadPool\
 	NestedDiagnosticContext Notification NotificationCenter \
 	NotificationQueue PriorityNotificationQueue TimedNotificationQueue \
 	NullStream NumberFormatter NumberParser NumericString AbstractObserver \
-	Path PatternFormatter Process PurgeStrategy RWLock Random RandomStream \
+	Path PatternFormatter PIDFile Process ProcessRunner PurgeStrategy RWLock Random RandomStream \
 	DirectoryIteratorStrategy RegularExpression RefCountedObject Runnable RotateStrategy \
 	SHA1Engine SHA2Engine Semaphore SharedLibrary SimpleFileChannel \
 	SignalHandler SplitterChannel SortedDirectoryIterator Stopwatch StreamChannel \

+ 101 - 0
Foundation/include/Poco/PIDFile.h

@@ -0,0 +1,101 @@
+//
+// PIDFile.h
+//
+// Library: Foundation
+// Package: Processes
+// Module:  PIDFile
+//
+// Definition of the PIDFile class.
+//
+// Copyright (c) 2023, Applied Informatics Software Engineering GmbH.
+// Aleph ONE Software Engineering d.o.o.,
+// and Contributors.
+//
+// SPDX-License-Identifier:    BSL-1.0
+//
+
+
+#ifndef Foundation_PIDFile_INCLUDED
+#define Foundation_PIDFile_INCLUDED
+
+
+#include "Poco/Foundation.h"
+#include <memory>
+
+
+namespace Poco {
+
+
+class Foundation_API PIDFile
+	/// A utility class, creating process ID file on
+	/// construction and deleting it on destruction.
+{
+public:
+	using Ptr = std::unique_ptr<PIDFile>;
+
+	static const int INVALID_PID = -1;
+
+	PIDFile();
+		/// Creates the PIDFile.
+
+	PIDFile(const std::string& fileName, bool write = true);
+		/// Creates the PIDFile.
+		/// If `fileName` is not empty, creates the PID file.
+		/// If `write` is true, the file is written.
+
+	~PIDFile();
+		/// Destroys the PIDFile.
+		/// If fileName is not empty, deletes the PID file.
+
+	const std::string& getName() const;
+		/// Returns the file name.
+
+	void setName(const std::string& fileName);
+		/// Sets the file name.
+
+	void create();
+		/// Creates the file and writes PID into it.
+
+	void destroy();
+		/// Deletes the PID file and invalidates the held PID.
+
+	int getPID() const;
+		/// Returns the PID.
+
+	bool exists() const;
+		/// Returns true if PID file exists and its content is
+		/// equal to the held PID.
+
+	static bool contains(const std::string& fileName, int pid);
+		/// Returns true if the `fileName` contains the given `pid`.
+
+	static std::string& getFileName(std::string& pidFile);
+		/// Returns the file name.
+
+private:
+
+	std::string _fileName;
+	int _pid = INVALID_PID;
+};
+
+
+//
+// inlines
+//
+
+inline const std::string& PIDFile::getName() const
+{
+	return _fileName;
+}
+
+
+inline int PIDFile::getPID() const
+{
+	return _pid;
+}
+
+
+} // namespace Poco
+
+
+#endif // Foundation_ProcessRunner_INCLUDED

+ 199 - 0
Foundation/include/Poco/ProcessRunner.h

@@ -0,0 +1,199 @@
+//
+// ProcessRunner.h
+//
+// Library: Foundation
+// Package: Processes
+// Module:  ProcessRunner
+//
+// Definition of the ProcessRunner class.
+//
+// Copyright (c) 2023, Applied Informatics Software Engineering GmbH.
+// Aleph ONE Software Engineering d.o.o.,
+// and Contributors.
+//
+// SPDX-License-Identifier:    BSL-1.0
+//
+
+
+#ifndef Foundation_ProcessRunner_INCLUDED
+#define Foundation_ProcessRunner_INCLUDED
+
+
+#include "Poco/Foundation.h"
+#include "Poco/Process.h"
+#include "Poco/ProcessOptions.h"
+#include "Poco/Runnable.h"
+#include "Poco/Thread.h"
+#include "Poco/Format.h"
+#include "Poco/Stopwatch.h"
+#include <atomic>
+#include <vector>
+
+
+namespace Poco {
+
+
+class Foundation_API ProcessRunner: public Poco::Runnable
+	/// ProcessRunner is a wrapper class for `Poco::ProcessHandle.
+	/// It starts and terminates a process with enabled or disabled (default)
+	/// `stdio` pipes, and optionally waits for process PID to be created before
+	/// returning control to the caller. The process is spawned from an
+	/// internal thread. Starting/stopping the process may block up to
+	/// a certain (configurable) period of time.
+	///
+	/// ProcessRunner can hold and control only one process at a time, which
+	/// can be started/stopped multiple times during the ProcessRunner lifetime.
+{
+public:
+	using Args = Poco::Process::Args;
+	using PID = Poco::ProcessHandle::PID;
+
+	static const int NO_OUT = Poco::PROCESS_CLOSE_STDOUT|Poco::PROCESS_CLOSE_STDERR;
+		/// Constant to prevent std out and err from being received from the process.
+
+	ProcessRunner(const std::string& cmd,
+		const Args& args,
+		const std::string& pidFile = "",
+		int options = NO_OUT,
+		int timeout = 10, /*seconds*/
+		bool startProcess = true,
+		const Args& pidArgFmt = pidArgFormat());
+		/// Creates the ProcessRunner.
+		///
+		/// If `pidFile` is not empty, the starting of the process waits
+		/// until the pid file has been updated with the new pid, and
+		/// the stopping of the process waits until the pid file is gone.
+		/// Waiting is terminated after timeout seconds.
+		///
+		/// If `pidFile` is empty and `pidArgFmt` is not empty, autodetect
+		/// of PID file from `args` is attempted; the default PID file
+		/// argument format corresponds to the one used by
+		/// `Poco::Util::Application`
+		///
+		/// The `options` are passed to the process, defaulting to
+		/// closed stdio output pipes.
+		///
+		/// The `timeout` in seconds determines how long the ProcessRunner
+		/// waits for the process to start; if PID file name is provided or
+		/// autodetected from arguments, ProcessRunner will wait until the file
+		/// exists and contains the process PID or timeout expires (in which
+		/// case a TimeoutException is thrown).
+		///
+		/// If `startProcess` is true, the process is started on object creation.
+
+	~ProcessRunner();
+		/// Destroys the ProcessRunner.
+
+	PID pid() const;
+		/// Returns the process PID.
+
+	const std::string& pidFile() const;
+		/// Returns the process PID filename.
+		/// Returns empty string when pid filename
+		/// is not specified at construction, either
+		/// explicitly, or implicitly through
+		/// command line argument.
+
+	bool running() const;
+		/// Returns true if process is running.
+
+	void start();
+		/// Starts the process and waits for it to be fully initialized.
+		/// Process initialization completion is indicated by a new pid in
+		/// the pid file (if specified at construction, otherwise there
+		/// is no wating for pid).
+		/// If pid file is not specified, there is no waiting.
+		///
+		/// Attempting to start a started process results in
+		/// Poco::InvalidAccessException being thrown.
+
+	void stop();
+		/// Stops the process.
+		///
+		/// Calling stop() on a stopped process is a no-op.
+
+	std::string cmdLine() const;
+		/// Returns process full command line.
+
+	int result() const;
+		/// Returns process return code.
+
+	int runCount() const;
+		/// Returns the number of times the process has been executed.
+
+
+private:
+	static const Poco::ProcessHandle::PID INVALID_PID = -1;
+	static const int RESULT_UNKNOWN = -1;
+
+	static Args pidArgFormat()
+	{
+#if defined(POCO_OS_FAMILY_WINDOWS)
+		return Args{"-p", "--pidfile=", "/p", "/pidfile="};
+#else
+		return Args{"-p", "--pidfile="};
+#endif
+	}
+
+
+	void run();
+		/// Starts the process and waits for it to be fully initialized.
+		/// Process initialization completion is indicated by new pid in
+		/// the pid file. If pid file is not specified, there is no waiting.
+
+	void checkTimeout(const Poco::Stopwatch& sw, const std::string& msg);
+		/// If timeout is exceeded, throws TimeoutException with `msg`
+		/// message.
+
+	Poco::Thread _t;
+	std::string _cmd;
+	Args _args;
+	std::atomic<PID> _pid;
+	std::string _pidFile;
+	int _options;
+	int _timeout;
+	std::atomic<Poco::ProcessHandle*> _pPH;
+	std::atomic<bool> _started;
+	std::atomic<int> _rc;
+	std::atomic<int> _runCount;
+};
+
+
+//
+// inlines
+//
+
+inline const std::string& ProcessRunner::pidFile() const
+{
+	return _pidFile;
+}
+
+
+inline bool ProcessRunner::running() const
+{
+	return _pid != INVALID_PID;
+}
+
+
+inline ProcessRunner::PID ProcessRunner::pid() const
+{
+	return _pid;
+}
+
+
+inline int ProcessRunner::result() const
+{
+	return _rc;
+}
+
+
+inline int ProcessRunner::runCount() const
+{
+	return _runCount;
+}
+
+
+} // namespace Poco
+
+
+#endif // Foundation_ProcessRunner_INCLUDED

+ 2 - 1
Foundation/include/Poco/Process_UNIX.h

@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <vector>
 #include <map>
+#include <atomic>
 
 
 namespace Poco {
@@ -42,7 +43,7 @@ public:
 	int tryWait() const;
 
 private:
-	pid_t _pid;
+	std::atomic<pid_t> _pid;
 };
 
 

+ 14 - 2
Foundation/include/Poco/Thread.h

@@ -74,11 +74,23 @@ public:
 		POLICY_DEFAULT = POLICY_DEFAULT_IMPL
 	};
 
-	Thread();
+	Thread(uint32_t sigMask = 0);
 		/// Creates a thread. Call start() to start it.
+		/// 
+		/// The optional sigMask parameter specifies which signals should be blocked.
+		/// To block a specific signal, set the corresponding bit in the sigMask.
+		/// Multiple bits can be set in the mask to block multiple signals if needed.
+		///
+		/// Available on POSIX platforms only
 
-	Thread(const std::string& name);
+	Thread(const std::string& name, uint32_t sigMask = 0);
 		/// Creates a named thread. Call start() to start it.
+		/// 
+		/// The optional sigMask parameter specifies which signals should be blocked.
+		/// To block a specific signal, set the corresponding bit in the sigMask.
+		/// Multiple bits can be set in the mask to block multiple signals if needed.
+		///
+		/// Available on POSIX platforms only
 
 	~Thread();
 		/// Destroys the thread.

+ 1 - 0
Foundation/include/Poco/Thread_POSIX.h

@@ -76,6 +76,7 @@ public:
 	static int getMaxOSPriorityImpl(int policy);
 	void setStackSizeImpl(int size);
 	int getStackSizeImpl() const;
+	void setSignalMaskImpl(uint32_t sigMask);
 	void startImpl(SharedPtr<Runnable> pTarget);
 	void joinImpl();
 	bool joinImpl(long milliseconds);

+ 122 - 0
Foundation/src/PIDFile.cpp

@@ -0,0 +1,122 @@
+//
+// PIDFile.cpp
+//
+// Library: Foundation
+// Package: Processes
+// Module:  PIDFile
+//
+// Copyright (c) 2023, Applied Informatics Software Engineering GmbH.
+// Aleph ONE Software Engineering d.o.o.,
+// and Contributors.
+//
+// SPDX-License-Identifier:    BSL-1.0
+//
+
+
+#include "Poco/PIDFile.h"
+#include "Poco/Path.h"
+#include "Poco/File.h"
+#include "Poco/Process.h"
+#include "Poco/FileStream.h"
+#include <fstream>
+
+
+using Poco::Path;
+using Poco::File;
+using Poco::Process;
+using Poco::FileInputStream;
+using Poco::FileOutputStream;
+
+
+namespace Poco {
+
+
+PIDFile::PIDFile()
+{
+}
+
+
+PIDFile::PIDFile(const std::string& fileName, bool write):
+	_fileName(fileName)
+{
+	if (write) create();
+}
+
+
+PIDFile::~PIDFile()
+{
+	destroy();
+}
+
+
+void PIDFile::setName(const std::string& fileName)
+{
+	destroy();
+	_fileName = fileName;
+	create();
+}
+
+
+void PIDFile::create()
+{
+	if (!_fileName.empty())
+	{
+		Path p(getFileName(_fileName));
+		if (!File(p.makeParent()).exists())
+			File(p).createDirectories();
+		_pid = static_cast<int>(Process::id());
+		FileOutputStream fos(_fileName);
+		fos << _pid; fos.close();
+	}
+}
+
+
+void PIDFile::destroy()
+{
+	if (!_fileName.empty())
+	{
+		File f(_fileName);
+		if (f.exists()) f.remove();
+		_fileName.clear();
+	}
+	_pid = INVALID_PID;
+}
+
+
+bool PIDFile::exists() const
+{
+	if (File(_fileName).exists())
+	{
+		FileInputStream fis(_fileName);
+		int fPID = 0;
+		if (fis.peek() != std::ifstream::traits_type::eof())
+			fis >> fPID;
+		return fPID == _pid;
+	}
+	return false;
+}
+
+
+bool PIDFile::contains(const std::string& fileName, int pid)
+{
+	if (File(fileName).exists())
+	{
+		FileInputStream fis(fileName);
+		int fPID = 0;
+		if (fis.peek() != std::ifstream::traits_type::eof())
+			fis >> fPID;
+		return fPID == pid;
+	}
+	return false;
+}
+
+
+std::string& PIDFile::getFileName(std::string& pidFile)
+{
+	Path p(pidFile);
+	pidFile = p.makeAbsolute().toString();
+	return pidFile;
+}
+
+
+} // namespace Poco

+ 217 - 0
Foundation/src/ProcessRunner.cpp

@@ -0,0 +1,217 @@
+//
+// ProcessRunner.cpp
+//
+// Library: Foundation
+// Package: Processes
+// Module:  ProcessRunner
+//
+// Copyright (c) 2023, Applied Informatics Software Engineering GmbH.
+// Aleph ONE Software Engineering d.o.o.,
+// and Contributors.
+//
+// SPDX-License-Identifier:    BSL-1.0
+//
+
+
+#include "Poco/ProcessRunner.h"
+#include "Poco/PIDFile.h"
+#include "Poco/FileStream.h"
+#include "Poco/AutoPtr.h"
+#include "Poco/File.h"
+#include "Poco/Path.h"
+#include "Poco/String.h"
+#include <fstream>
+
+
+using Poco::Thread;
+using Poco::Process;
+using Poco::ProcessHandle;
+using Poco::FileInputStream;
+using Poco::AutoPtr;
+using Poco::File;
+using Poco::Path;
+using Poco::Stopwatch;
+
+
+namespace Poco {
+
+
+ProcessRunner::ProcessRunner(const std::string& cmd,
+		const Args& args,
+		const std::string& pidFile,
+		int options,
+		int timeout,
+		bool startProcess,
+		const Args& pidArgFmt): _cmd(cmd),
+			_args(args),
+			_pid(INVALID_PID),
+			_pidFile(pidFile),
+			_options(options),
+			_timeout(timeout),
+			_pPH(nullptr),
+			_started(false),
+			_rc(RESULT_UNKNOWN),
+			_runCount(0)
+{
+	if (_pidFile.empty() && !_args.empty() && !pidArgFmt.empty())
+	{
+		for (const auto& fmt : pidArgFmt)
+		{
+			for (const auto& arg : _args)
+			{
+				std::string a = Poco::trim(arg);
+				std::size_t pos = a.find(fmt);
+				if (pos == 0)
+				{
+					_pidFile = a.substr(fmt.length());
+					PIDFile::getFileName(_pidFile);
+					break;
+				}
+			}
+		}
+	}
+	if (startProcess) start();
+}
+
+
+ProcessRunner::~ProcessRunner()
+{
+	try
+	{
+		stop();
+	}
+	catch (...)
+	{
+		poco_unexpected();
+	}
+}
+
+
+std::string ProcessRunner::cmdLine() const
+{
+	std::string cmdL = _cmd + ' ';
+	auto it = _args.begin();
+	auto end = _args.end();
+	for (; it != end;)
+	{
+		cmdL.append(*it);
+		if (++it == end) break;
+		cmdL.append(1, ' ');
+	}
+	return cmdL;
+}
+
+
+void ProcessRunner::run()
+{
+	ProcessHandle* pPH = nullptr;
+	try
+	{
+		_pPH = pPH = new ProcessHandle(Process::launch(_cmd, _args, _options));
+		_pid = pPH->id();
+		_rc = pPH->wait();
+	}
+	catch (...)
+	{
+	}
+
+	_pid = INVALID_PID;
+	_pPH = nullptr;
+	++_runCount;
+	delete pPH;
+}
+
+
+void ProcessRunner::stop()
+{
+	if (_started)
+	{
+		PID pid;
+		Stopwatch sw; sw.start();
+		if (_pPH.exchange(nullptr) && ((pid = _pid.exchange(INVALID_PID))) != INVALID_PID)
+		{
+			while (Process::isRunning(pid))
+			{
+				if (pid > 0)
+				{
+					Process::requestTermination(pid);
+					checkTimeout(sw, "Waiting for process termination");
+				}
+				else throw Poco::IllegalStateException("Invalid PID, can;t terminate process");
+			}
+			_t.join();
+		}
+
+		if (!_pidFile.empty())
+		{
+			if (!_pidFile.empty())
+			{
+				File pidFile(_pidFile);
+				_pidFile.clear();
+				std::string msg;
+				Poco::format(msg, "Waiting for PID file (pidFile: '%s')", _pidFile);
+				sw.restart();
+				while (pidFile.exists())
+					checkTimeout(sw, msg);
+			}
+		}
+	}
+	_started.store(false);
+}
+
+
+void ProcessRunner::checkTimeout(const Stopwatch& sw, const std::string& msg)
+{
+	if (sw.elapsedSeconds() > _timeout)
+	{
+		throw Poco::TimeoutException(
+			Poco::format("ProcessRunner::checkTimeout(): %s", msg));
+	}
+	Thread::sleep(10);
+}
+
+
+void ProcessRunner::start()
+{
+	if (!_started.exchange(true))
+	{
+		int prevRunCnt = runCount();
+
+		_t.start(*this);
+
+		std::string msg;
+		Poco::format(msg, "Waiting for process to start (pidFile: '%s')", _pidFile);
+		Stopwatch sw; sw.start();
+
+		// wait for the process to be either running or completed by monitoring run counts.
+		while (!running() && prevRunCnt >= runCount()) checkTimeout(sw, msg);
+
+		// we could wait for the process handle != INVALID_PID,
+		// but if pidFile name was given, we should wait for
+		// the process to write it
+		if (!_pidFile.empty())
+		{
+			sw.restart();
+			// wait until process is fully initialized
+			File pidFile(_pidFile);
+			while (!pidFile.exists())
+				checkTimeout(sw, "waiting for PID file");
+
+			// verify that the file content is actually the process PID
+			FileInputStream fis(_pidFile);
+			int fPID = 0;
+			if (fis.peek() != std::ifstream::traits_type::eof())
+				fis >> fPID;
+			while (fPID != pid())
+			{
+				fis.clear(); fis.seekg(0); fis >> fPID;
+				checkTimeout(sw, Poco::format("waiting for new PID (%s)", _pidFile));
+			}
+		}
+	}
+	else
+		throw Poco::InvalidAccessException("start() called on started ProcessRunner");
+}
+
+
+} // namespace Poco

+ 8 - 2
Foundation/src/Thread.cpp

@@ -88,21 +88,27 @@ private:
 } // namespace
 
 
-Thread::Thread():
+Thread::Thread(uint32_t sigMask):
 	_id(uniqueId()),
 	_pTLS(0),
 	_event(true)
 {
 	setNameImpl(makeName());
+#if defined(POCO_OS_FAMILY_UNIX)
+	setSignalMaskImpl(sigMask);
+#endif
 }
 
 
-Thread::Thread(const std::string& name):
+Thread::Thread(const std::string& name, uint32_t sigMask):
 	_id(uniqueId()),
 	_pTLS(0),
 	_event(true)
 {
 	setNameImpl(name);
+#if defined(POCO_OS_FAMILY_UNIX)
+	setSignalMaskImpl(sigMask);
+#endif
 }
 
 

+ 15 - 0
Foundation/src/Thread_POSIX.cpp

@@ -257,6 +257,21 @@ void ThreadImpl::setStackSizeImpl(int size)
 }
 
 
+void ThreadImpl::setSignalMaskImpl(uint32_t sigMask)
+{
+	sigset_t sset;
+	sigemptyset(&sset);
+
+	for (int sig = 0; sig < sizeof(uint32_t) * 8; ++sig) 
+	{
+		if ((sigMask & (1 << sig)) != 0)
+			sigaddset(&sset, sig);
+	}
+	
+	pthread_sigmask(SIG_BLOCK, &sset, 0);
+}
+
+
 void ThreadImpl::startImpl(SharedPtr<Runnable> pTarget)
 {
 	{

+ 1 - 1
Foundation/testsuite/Makefile-Driver

@@ -20,7 +20,7 @@ objects = ActiveMethodTest ActivityTest ActiveDispatcherTest \
 	NDCTest NotificationCenterTest NotificationQueueTest \
 	PriorityNotificationQueueTest TimedNotificationQueueTest \
 	NotificationsTestSuite NullStreamTest NumberFormatterTest \
-	NumberParserTest PathTest PatternFormatterTest PBKDF2EngineTest RWLockTest \
+	NumberParserTest PathTest PatternFormatterTest PBKDF2EngineTest ProcessRunnerTest RWLockTest \
 	RandomStreamTest RandomTest RegularExpressionTest SHA1EngineTest SHA2EngineTest \
 	SemaphoreTest ConditionTest SharedLibraryTest SharedLibraryTestSuite \
 	SimpleFileChannelTest StopwatchTest \

+ 54 - 36
Foundation/testsuite/TestApp_vs170.vcxproj

@@ -344,7 +344,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_shared|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <MinimalRebuild>false</MinimalRebuild>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
@@ -361,8 +361,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;PocoFoundationd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>bin\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -372,7 +373,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_shared|x64'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -388,8 +389,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;PocoFoundationd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin64\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>bin64\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -398,7 +400,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_shared|ARM64'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -414,8 +416,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;PocoFoundationd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>binA64\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>binA64\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -428,7 +431,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -445,8 +448,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;PocoFoundation.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>
@@ -463,7 +467,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -480,8 +484,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;PocoFoundation.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin64\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>
@@ -497,7 +502,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -514,8 +519,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;PocoFoundation.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>binA64\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>
@@ -531,7 +537,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -548,8 +554,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin\static_md\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>
@@ -566,7 +573,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -583,8 +590,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin64\static_md\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>
@@ -600,7 +608,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -617,8 +625,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>binA64\static_md\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>
@@ -630,7 +639,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_static_md|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <MinimalRebuild>false</MinimalRebuild>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
@@ -647,8 +656,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmdd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin\static_md\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>bin\static_md\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -658,7 +668,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_static_md|x64'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -674,8 +684,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmdd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin64\static_md\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>bin64\static_md\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -684,7 +695,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_static_md|ARM64'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -700,8 +711,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmdd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>binA64\static_md\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>binA64\static_md\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -710,7 +722,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_static_mt|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <MinimalRebuild>false</MinimalRebuild>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
@@ -727,8 +739,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmtd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin\static_mt\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>bin\static_mt\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -738,7 +751,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_static_mt|x64'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@@ -754,8 +767,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmtd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin64\static_mt\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>bin64\static_mt\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -764,7 +778,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='debug_static_mt|ARM64'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@@ -780,8 +794,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmtd.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>binA64\static_mt\TestAppd.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>binA64\static_mt\TestAppd.pdb</ProgramDatabaseFile>
       <SubSystem>Console</SubSystem>
@@ -794,7 +809,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
@@ -811,8 +826,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmt.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin\static_mt\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>
@@ -829,7 +845,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
@@ -846,8 +862,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmt.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>bin64\static_mt\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>
@@ -863,7 +880,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <OmitFramePointers>true</OmitFramePointers>
-      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
@@ -880,8 +897,9 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
+      <AdditionalDependencies>PocoFoundationmt.lib;winmm.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>binA64\static_mt\TestApp.exe</OutputFile>
-      <AdditionalLibraryDirectories>..\..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\..\libA64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ProgramDatabaseFile>
       </ProgramDatabaseFile>

+ 2 - 0
Foundation/testsuite/TestSuite_vs170.vcxproj

@@ -985,6 +985,7 @@
     <ClCompile Include="src\PBKDF2EngineTest.cpp" />
     <ClCompile Include="src\PriorityEventTest.cpp" />
     <ClCompile Include="src\PriorityNotificationQueueTest.cpp" />
+    <ClCompile Include="src\ProcessRunnerTest.cpp" />
     <ClCompile Include="src\ProcessesTestSuite.cpp" />
     <ClCompile Include="src\ProcessTest.cpp" />
     <ClCompile Include="src\RandomStreamTest.cpp" />
@@ -1127,6 +1128,7 @@
     <ClInclude Include="src\PBKDF2EngineTest.h" />
     <ClInclude Include="src\PriorityEventTest.h" />
     <ClInclude Include="src\PriorityNotificationQueueTest.h" />
+    <ClInclude Include="src\ProcessRunnerTest.h" />
     <ClInclude Include="src\ProcessesTestSuite.h" />
     <ClInclude Include="src\ProcessTest.h" />
     <ClInclude Include="src\RandomStreamTest.h" />

+ 6 - 0
Foundation/testsuite/TestSuite_vs170.vcxproj.filters

@@ -519,6 +519,9 @@
     <ClCompile Include="src\NamedMutexTest.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\ProcessRunnerTest.cpp">
+      <Filter>Processes\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="src\ProcessesTestSuite.cpp">
       <Filter>Processes\Source Files</Filter>
     </ClCompile>
@@ -938,6 +941,9 @@
     <ClInclude Include="src\NamedMutexTest.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="src\ProcessRunnerTest.h">
+      <Filter>Processes\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="src\ProcessesTestSuite.h">
       <Filter>Processes\Header Files</Filter>
     </ClInclude>

+ 49 - 28
Foundation/testsuite/src/ClassLoaderTest.cpp

@@ -15,6 +15,8 @@
 #include "Poco/Manifest.h"
 #include "Poco/Exception.h"
 #include "Poco/Path.h"
+#include "Poco/File.h"
+#include "Poco/Format.h"
 #include "TestPlugin.h"
 
 
@@ -24,6 +26,8 @@ using Poco::SharedLibrary;
 using Poco::AbstractMetaObject;
 using Poco::NotFoundException;
 using Poco::InvalidAccessException;
+using Poco::Path;
+using Poco::File;
 
 
 ClassLoaderTest::ClassLoaderTest(const std::string& name): CppUnit::TestCase(name)
@@ -38,17 +42,14 @@ ClassLoaderTest::~ClassLoaderTest()
 
 void ClassLoaderTest::testClassLoader1()
 {
-	std::string path = "TestLibrary";
-	path.append(SharedLibrary::suffix());
-	Poco::Path libraryPath = Poco::Path::current();
-	libraryPath.append(path);
+	std::string libraryPath = getFullName("TestLibrary");
 	ClassLoader<TestPlugin> cl;
 
 	assertTrue (cl.begin() == cl.end());
 	assertNullPtr (cl.findClass("PluginA"));
-	assertNullPtr (cl.findManifest(libraryPath.toString()));
+	assertNullPtr (cl.findManifest(libraryPath));
 
-	assertTrue (!cl.isLibraryLoaded(libraryPath.toString()));
+	assertTrue (!cl.isLibraryLoaded(libraryPath));
 
 	try
 	{
@@ -65,7 +66,7 @@ void ClassLoaderTest::testClassLoader1()
 
 	try
 	{
-		const ClassLoader<TestPlugin>::Manif& POCO_UNUSED manif = cl.manifestFor(libraryPath.toString());
+		const ClassLoader<TestPlugin>::Manif& POCO_UNUSED manif = cl.manifestFor(libraryPath);
 		fail("not found - must throw exception");
 	}
 	catch (NotFoundException&)
@@ -80,25 +81,22 @@ void ClassLoaderTest::testClassLoader1()
 
 void ClassLoaderTest::testClassLoader2()
 {
-	std::string path = "TestLibrary";
-	path.append(SharedLibrary::suffix());
-	Poco::Path libraryPath = Poco::Path::current();
-	libraryPath.append(path);
+	std::string libraryPath = getFullName("TestLibrary");
 	ClassLoader<TestPlugin> cl;
-	cl.loadLibrary(libraryPath.toString());
+	cl.loadLibrary(libraryPath);
 
 	assertTrue (cl.begin() != cl.end());
 	assertNotNullPtr (cl.findClass("PluginA"));
 	assertNotNullPtr (cl.findClass("PluginB"));
 	assertNotNullPtr (cl.findClass("PluginC"));
-	assertNotNullPtr (cl.findManifest(libraryPath.toString()));
+	assertNotNullPtr (cl.findManifest(libraryPath));
 
-	assertTrue (cl.isLibraryLoaded(libraryPath.toString()));
-	assertTrue (cl.manifestFor(libraryPath.toString()).size() == 3);
+	assertTrue (cl.isLibraryLoaded(libraryPath));
+	assertTrue (cl.manifestFor(libraryPath).size() == 3);
 
 	ClassLoader<TestPlugin>::Iterator it = cl.begin();
 	assertTrue (it != cl.end());
-	assertTrue (it->first == libraryPath.toString());
+	assertTrue (it->first == libraryPath);
 	assertTrue (it->second->size() == 3);
 	++it;
 	assertTrue (it == cl.end());
@@ -165,32 +163,55 @@ void ClassLoaderTest::testClassLoader2()
 	meta2.destroy(pPlugin);
 	assertTrue (!meta2.isAutoDelete(pPlugin));
 
-	cl.unloadLibrary(libraryPath.toString());
+	cl.unloadLibrary(libraryPath);
 }
 
 
 void ClassLoaderTest::testClassLoader3()
 {
-	std::string path = "TestLibrary";
-	path.append(SharedLibrary::suffix());
-	Poco::Path libraryPath = Poco::Path::current();
-	libraryPath.append(path);
+	std::string libraryPath = getFullName("TestLibrary");
 	ClassLoader<TestPlugin> cl;
-	cl.loadLibrary(libraryPath.toString());
-	cl.loadLibrary(libraryPath.toString());
-	cl.unloadLibrary(libraryPath.toString());
+	cl.loadLibrary(libraryPath);
+	cl.loadLibrary(libraryPath);
+	cl.unloadLibrary(libraryPath);
 
-	assertTrue (cl.manifestFor(libraryPath.toString()).size() == 3);
+	assertTrue (cl.manifestFor(libraryPath).size() == 3);
 
 	ClassLoader<TestPlugin>::Iterator it = cl.begin();
 	assertTrue (it != cl.end());
-	assertTrue (it->first == libraryPath.toString());
+	assertTrue (it->first == libraryPath);
 	assertTrue (it->second->size() == 3);
 	++it;
 	assertTrue (it == cl.end());
 
-	cl.unloadLibrary(libraryPath.toString());
-	assertNullPtr (cl.findManifest(libraryPath.toString()));
+	cl.unloadLibrary(libraryPath);
+	assertNullPtr (cl.findManifest(libraryPath));
+}
+
+
+std::string ClassLoaderTest::getFullName(const std::string& libName)
+{
+	std::string name = Path::expand("$POCO_BASE");
+	char c = Path::separator();
+	std::string OSNAME = Path::expand("$OSNAME");
+	std::string OSARCH = Path::expand("$OSARCH");
+	name.append(1, c)
+		.append(Poco::format("Foundation%ctestsuite%cbin%c", c, c, c))
+		.append(Poco::format("%s%c%s%c", OSNAME, c, OSARCH, c))
+		.append(libName).append(SharedLibrary::suffix());
+
+	// CMake
+	if (!File(name).exists())
+	{
+		name = Path::expand("$POCO_BASE");
+		name.append(Poco::format("%ccmake-build%cbin%c", c, c, c))
+			.append(libName).append(SharedLibrary::suffix());
+	}
+
+	if (!File(name).exists())
+		name = libName + SharedLibrary::suffix();
+
+	return name;
 }
 
 

+ 1 - 0
Foundation/testsuite/src/ClassLoaderTest.h

@@ -34,6 +34,7 @@ public:
 	static CppUnit::Test* suite();
 
 private:
+	static std::string getFullName(const std::string& libName);
 };
 
 

+ 348 - 0
Foundation/testsuite/src/ProcessRunnerTest.cpp

@@ -0,0 +1,348 @@
+//
+// ProcessRunnerTest.cpp
+//
+// Copyright (c) 2023, Applied Informatics Software Engineering GmbH.
+// Aleph ONE Software Engineering d.o.o.,
+// and Contributors.
+//
+// SPDX-License-Identifier:	BSL-1.0
+//
+
+
+#include "ProcessRunnerTest.h"
+#include "CppUnit/TestCaller.h"
+#include "CppUnit/TestSuite.h"
+#include "Poco/PIDFile.h"
+#include "Poco/Format.h"
+#include "Poco/Path.h"
+#include "Poco/File.h"
+#include "Poco/FileStream.h"
+
+
+using namespace Poco;
+
+
+ProcessRunnerTest::ProcessRunnerTest(const std::string& name):
+	CppUnit::TestCase(name)
+{
+}
+
+
+ProcessRunnerTest::~ProcessRunnerTest()
+{
+}
+
+
+void ProcessRunnerTest::testPIDFile()
+{
+	std::string pidFile = Path::tempHome() + "test.pid";
+
+	{
+		PIDFile f;
+		assertTrue (f.getName().empty());
+		assertTrue (f.getPID() == PIDFile::INVALID_PID);
+		assertFalse (File(pidFile).exists());
+
+		f.setName(pidFile);
+		assertTrue (f.getName() == pidFile);
+		assertTrue (f.getPID() != PIDFile::INVALID_PID);
+		assertTrue (File(pidFile).exists());
+	}
+	assertFalse (File(pidFile).exists());
+
+	{
+		PIDFile f(pidFile);
+		std::string pf = pidFile;
+
+		assertTrue (f.getName() == pf);
+		assertTrue (File(pf).exists());
+		assertTrue (f.getPID() != PIDFile::INVALID_PID);
+
+		assertTrue (f.exists());
+	}
+	assertFalse (File(pidFile).exists());
+
+	{
+		PIDFile f(pidFile);
+		assertTrue (f.getName() == pidFile);
+		assertTrue (File(pidFile).exists());
+		assertTrue (f.getPID() != PIDFile::INVALID_PID);
+
+		assertTrue (f.exists());
+	}
+	assertFalse (File(pidFile).exists());
+
+	{
+		PIDFile f(pidFile, false);
+		std::string pf = pidFile;
+
+		assertTrue (f.getName() == pf);
+		assertTrue (!File(pf).exists());
+		assertTrue (f.getPID() == PIDFile::INVALID_PID);
+
+		f.create();
+		assertTrue (f.exists());
+	}
+	assertFalse (File(pidFile).exists());
+}
+
+
+void ProcessRunnerTest::testProcessRunner()
+{
+	std::string name("TestApp");
+	std::string cmd;
+#if defined(_DEBUG) && (POCO_OS != POCO_OS_ANDROID)
+	name += "d";
+#endif
+
+#if defined(POCO_OS_FAMILY_UNIX)
+	cmd += name;
+#elif defined(_WIN32_WCE)
+	cmd = "\\";
+	cmd += name;
+	cmd += ".EXE";
+#else
+	cmd = name;
+#endif
+
+	// non-auto start, no PID
+	{
+		std::vector<std::string> args;
+		char c = Path::separator();
+		std::string pidFile = Poco::format("run%c%s.pid", c, name);
+		args.push_back(std::string("--pidfile=").append(pidFile));
+		ProcessRunner pr(cmd, args, "", ProcessRunner::NO_OUT, 10, false);
+		assertTrue (pr.cmdLine() == cmdLine(cmd, args));
+		assertFalse (pr.running());
+		pr.start();
+		
+		Stopwatch sw; sw.start();
+		while (!pr.running())
+			checkTimeout(sw, "Waiting for process to start", 1000, __LINE__);
+		
+		assertTrue (pr.running());
+		try
+		{
+			pr.start();
+			fail("It should not be possible to start a started process.");
+		}
+		catch(const Poco::InvalidAccessException&) {}
+		pr.stop();
+		sw.restart();
+		while (pr.running())
+			checkTimeout(sw, "Waiting for process to stop", 1000, __LINE__);
+		assertFalse (pr.running());
+		pr.start();
+		while (!pr.running())
+			checkTimeout(sw, "Waiting for process to start", 1000, __LINE__);
+		assertTrue (pr.running());
+		pr.stop();
+		pr.stop(); // second stop() should be silent no-op
+	}
+
+	// non-auto start with PID
+	{
+		std::vector<std::string> args;
+		char c = Path::separator();
+		std::string pidFile = Poco::format("run%c%s.pid", c, name);
+		args.push_back(std::string("--pidfile=").append(pidFile));
+		ProcessRunner pr(cmd, args, "", ProcessRunner::NO_OUT, 10, false);
+		assertTrue (pr.cmdLine() == cmdLine(cmd, args));
+		assertFalse (pr.running());
+		pr.start();
+		Stopwatch sw; sw.start();
+		while (!pr.running())
+			checkTimeout(sw, "Waiting for process to start", 1000, __LINE__);
+		assertTrue (pr.running());
+		try
+		{
+			pr.start();
+			fail("It should not be possible to start a started process.");
+		}
+		catch(const Poco::InvalidAccessException&) {}
+		pr.stop();
+		sw.restart();
+		while (pr.running())
+			checkTimeout(sw, "Waiting for process to stop", 1000, __LINE__);
+		assertFalse (pr.running());
+		pr.start();
+		while (!pr.running())
+			checkTimeout(sw, "Waiting for process to start", 1000, __LINE__);
+		assertTrue (pr.running());
+		pr.stop();
+		pr.stop(); // second stop() should be silent no-op
+	}
+
+	// autodetect PID file from the long command line argument
+	{
+		std::vector<std::string> args;
+		char c = Path::separator();
+		std::string pidFile = Poco::format("run%c%s.pid", c, name);
+		args.push_back(std::string("--pidfile=").append(pidFile));
+		{
+			ProcessRunner pr(cmd, args);
+			assertTrue (pr.cmdLine() == cmdLine(cmd, args));
+			assertTrue (pr.pidFile() == PIDFile::getFileName(pidFile));
+			assertTrue (File(pidFile).exists());
+			assertTrue (PIDFile::contains(pidFile, pr.pid()));
+		}
+		assertTrue (!File(pidFile).exists());
+	}
+
+	// autodetect PID file from the short command line argument
+	{
+		std::vector<std::string> args;
+		char c = Path::separator();
+		std::string pidFile = Poco::format("run%c%s.pid", c, name);
+		args.push_back(std::string("-p=").append(pidFile));
+		{
+			ProcessRunner pr(cmd, args, PIDFile::getFileName(pidFile));
+			assertTrue (pr.cmdLine() == cmdLine(cmd, args));
+			assertTrue (pr.pidFile() == pidFile);
+			assertTrue (File(pidFile).exists());
+			assertTrue (PIDFile::contains(pidFile, pr.pid()));
+		}
+		assertTrue (!File(pidFile).exists());
+	}
+
+	// ProcessRunner should NOT autodetect PID from command line args
+	// if argument formats list is empty
+	{
+		std::vector<std::string> args;
+		char c = Path::separator();
+		std::string pidFile = Poco::format("run%c%s.pid", c, name);
+		args.push_back(std::string("--pidfile=").append(pidFile));
+		{
+			ProcessRunner pr(cmd, args, "", ProcessRunner::NO_OUT, 10, true, {});
+			assertTrue (pr.cmdLine() == cmdLine(cmd, args));
+			assertTrue (pr.pidFile().empty()); // ProcessRunner has no PID file
+
+			PIDFile::getFileName(pidFile);
+			Stopwatch sw; sw.start();
+			while (!File(pidFile).exists())
+				checkTimeout(sw, "Waiting for PID file", 1000, __LINE__);
+
+			// PID file exists and is valid
+			assertTrue (File(pidFile).exists());
+			assertTrue (PIDFile::contains(pidFile, pr.pid()));
+		}
+		assertTrue (!File(pidFile).exists());
+	}
+
+	{
+		std::vector<std::string> args;
+		char c = Path::separator();
+		std::string pidFile = Poco::format("run%c%s.pid", c, name);
+		args.push_back(std::string("-p=").append(pidFile));
+		{
+			ProcessRunner pr(cmd, args, "", ProcessRunner::NO_OUT, 10, true, {});
+			assertTrue (pr.cmdLine() == cmdLine(cmd, args));
+			assertTrue (pr.pidFile().empty()); // ProcessRunner has no PID file
+
+			PIDFile::getFileName(pidFile);
+			Stopwatch sw; sw.start();
+			while (!File(pidFile).exists())
+				checkTimeout(sw, "Waiting for PID file", 1000, __LINE__);
+
+			// PID file exists and is valid
+			assertTrue (File(pidFile).exists());
+			assertTrue (PIDFile::contains(pidFile, pr.pid()));
+		}
+		assertTrue (!File(pidFile).exists());
+	}
+
+	// no PID file created at all
+	{
+		std::vector<std::string> args;
+		char c = Path::separator();
+		std::string pidFile = Poco::format("run%c%s.pid", c, name);
+		{
+			ProcessRunner pr(cmd, args);
+			assertTrue (pr.cmdLine() == cmdLine(cmd, args));
+			assertTrue (pr.pidFile().empty());
+
+			Thread::sleep(500);
+
+			assertTrue (!File(pidFile).exists());
+			assertTrue (!PIDFile::contains(pidFile, pr.pid()));
+		}
+		assertTrue (!File(pidFile).exists());
+	}
+#if defined(POCO_OS_FAMILY_UNIX)
+	// start process launching multiple threads
+	{
+		std::vector<std::string> args;
+		char c = Path::separator();
+		std::string pidFile = Poco::format("run%c%s.pid", c, name);
+		args.push_back(std::string("--pidfile=").append(pidFile));
+		args.push_back(std::string("--launch-thread"));
+		ProcessRunner pr(cmd, args, "", ProcessRunner::NO_OUT, 10, false);
+		assertTrue (pr.cmdLine() == cmdLine(cmd, args));
+		assertFalse (pr.running());
+		pr.start();
+		Stopwatch sw; sw.start();
+		while (!pr.running())
+			checkTimeout(sw, "Waiting for process to start", 1000, __LINE__);
+		assertTrue (pr.running());
+		try
+		{
+			pr.start();
+			fail("It should not be possible to start a started process.");
+		}
+		catch(const Poco::InvalidAccessException&) {}
+		pr.stop();
+		sw.restart();
+		while (pr.running())
+			checkTimeout(sw, "Waiting for process to stop", 1000, __LINE__);
+		assertFalse (pr.running());
+		assertEqual (pr.result(), 0);
+	}
+#endif
+}
+
+
+std::string ProcessRunnerTest::cmdLine(const std::string& cmd, const ProcessRunner::Args& args)
+{
+	std::string cmdL = cmd + ' ';
+	auto it = args.begin();
+	auto end = args.end();
+	for (; it != end;)
+	{
+		cmdL.append(*it);
+		if (++it == end) break;
+		cmdL.append(1, ' ');
+	}
+	return cmdL;
+}
+
+
+void ProcessRunnerTest::checkTimeout(const Stopwatch& sw, const std::string& msg, int timeoutMS, int line)
+{
+	if (sw.elapsedSeconds()*1000 > timeoutMS)
+	{
+		throw Poco::TimeoutException(
+			Poco::format("ProcessRunner::checkTimeout(): %s, line: %d", msg, line));
+	}
+	Thread::sleep(10);
+}
+
+
+void ProcessRunnerTest::setUp()
+{
+}
+
+
+void ProcessRunnerTest::tearDown()
+{
+}
+
+
+CppUnit::Test* ProcessRunnerTest::suite()
+{
+	CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ProcessRunnerTest");
+
+	CppUnit_addTest(pSuite, ProcessRunnerTest, testPIDFile);
+	CppUnit_addTest(pSuite, ProcessRunnerTest, testProcessRunner);
+
+	return pSuite;
+}

+ 44 - 0
Foundation/testsuite/src/ProcessRunnerTest.h

@@ -0,0 +1,44 @@
+//
+// ProcessRunnerTest.h
+//
+// Definition of the ProcessRunnerTest class.
+//
+// Copyright (c) 2023, Applied Informatics Software Engineering GmbH.
+// Aleph ONE Software Engineering d.o.o.,
+// and Contributors.
+//
+// SPDX-License-Identifier:	BSL-1.0
+//
+
+
+
+#ifndef ProcessRunnerTest_INCLUDED
+#define ProcessRunnerTest_INCLUDED
+
+
+#include "CppUnit/TestCase.h"
+#include "Poco/ProcessRunner.h"
+#include "Poco/Stopwatch.h"
+
+
+class ProcessRunnerTest: public CppUnit::TestCase
+{
+public:
+	ProcessRunnerTest(const std::string& name);
+	~ProcessRunnerTest();
+	
+	void testPIDFile();
+	void testProcessRunner();
+
+	void setUp();
+	void tearDown();
+
+	static CppUnit::Test* suite();
+
+private:
+	std::string cmdLine(const std::string& cmd, const Poco::ProcessRunner::Args& args);
+	void checkTimeout(const Poco::Stopwatch& sw, const std::string& msg, int timeoutMS, int line);
+};
+
+
+#endif // ProcessRunnerTest_INCLUDED

+ 3 - 0
Foundation/testsuite/src/ProcessTest.cpp

@@ -14,12 +14,15 @@
 #include "Poco/Process.h"
 #include "Poco/Pipe.h"
 #include "Poco/PipeStream.h"
+#include "Poco/Path.h"
+#include "Poco/Format.h"
 
 
 using namespace std::string_literals;
 using Poco::Process;
 using Poco::ProcessHandle;
 using Poco::Pipe;
+using Poco::Path;
 using Poco::PipeInputStream;
 using Poco::PipeOutputStream;
 

+ 1 - 0
Foundation/testsuite/src/ProcessTest.h

@@ -39,6 +39,7 @@ public:
 	static CppUnit::Test* suite();
 
 private:
+	static std::string getFullName(const std::string& name);
 };
 
 

+ 2 - 0
Foundation/testsuite/src/ProcessesTestSuite.cpp

@@ -13,6 +13,7 @@
 #include "NamedMutexTest.h"
 #include "NamedEventTest.h"
 #include "SharedMemoryTest.h"
+#include "ProcessRunnerTest.h"
 
 
 CppUnit::Test* ProcessesTestSuite::suite()
@@ -23,6 +24,7 @@ CppUnit::Test* ProcessesTestSuite::suite()
 	pSuite->addTest(NamedMutexTest::suite());
 	pSuite->addTest(NamedEventTest::suite());
 	pSuite->addTest(SharedMemoryTest::suite());
+	pSuite->addTest(ProcessRunnerTest::suite());
 
 	return pSuite;
 }

+ 43 - 21
Foundation/testsuite/src/SharedLibraryTest.cpp

@@ -14,12 +14,16 @@
 #include "Poco/SharedLibrary.h"
 #include "Poco/Exception.h"
 #include "Poco/Path.h"
+#include "Poco/File.h"
+#include "Poco/Format.h"
 
 
 using Poco::SharedLibrary;
 using Poco::NotFoundException;
 using Poco::LibraryLoadException;
 using Poco::LibraryAlreadyLoadedException;
+using Poco::Path;
+using Poco::File;
 
 
 typedef int (*GimmeFiveFunc)();
@@ -37,14 +41,11 @@ SharedLibraryTest::~SharedLibraryTest()
 
 void SharedLibraryTest::testSharedLibrary1()
 {
-	std::string path = "TestLibrary";
-	path.append(SharedLibrary::suffix());
-	Poco::Path libraryPath = Poco::Path::current();
-	libraryPath.append(path);
+	std::string libraryPath = getFullName("TestLibrary");
 	SharedLibrary sl;
 	assertTrue (!sl.isLoaded());
-	sl.load(libraryPath.toString());
-	assertTrue (sl.getPath() == libraryPath.toString());
+	sl.load(libraryPath);
+	assertTrue (sl.getPath() == libraryPath);
 	assertTrue (sl.isLoaded());
 	assertTrue (sl.hasSymbol("pocoBuildManifest"));
 	assertTrue (sl.hasSymbol("pocoInitializeLibrary"));
@@ -73,12 +74,9 @@ void SharedLibraryTest::testSharedLibrary1()
 
 void SharedLibraryTest::testSharedLibrary2()
 {
-	std::string path = "TestLibrary";
-	path.append(SharedLibrary::suffix());
-	Poco::Path libraryPath = Poco::Path::current();
-	libraryPath.append(path);
-	SharedLibrary sl(libraryPath.toString());
-	assertTrue (sl.getPath() == libraryPath.toString());
+	std::string libraryPath = getFullName("TestLibrary");
+	SharedLibrary sl(libraryPath);
+	assertTrue (sl.getPath() == libraryPath);
 	assertTrue (sl.isLoaded());
 
 	GimmeFiveFunc gimmeFive = (GimmeFiveFunc) sl.getSymbol("gimmeFive");
@@ -91,12 +89,12 @@ void SharedLibraryTest::testSharedLibrary2()
 
 void SharedLibraryTest::testSharedLibrary3()
 {
-	std::string path = "NonexistentLibrary";
-	path.append(SharedLibrary::suffix());
+	std::string libraryPath = "NonexistentLibrary";
+	libraryPath.append(libraryPath);
 	SharedLibrary sl;
 	try
 	{
-		sl.load(path);
+		sl.load(libraryPath);
 		failmsg("no such library - must throw exception");
 	}
 	catch (LibraryLoadException&)
@@ -108,16 +106,13 @@ void SharedLibraryTest::testSharedLibrary3()
 	}
 	assertTrue (!sl.isLoaded());
 
-	path = "TestLibrary";
-	path.append(SharedLibrary::suffix());
-	Poco::Path libraryPath = Poco::Path::current();
-	libraryPath.append(path);
-	sl.load(libraryPath.toString());
+	libraryPath = getFullName("TestLibrary");
+	sl.load(libraryPath);
 	assertTrue (sl.isLoaded());
 
 	try
 	{
-		sl.load(libraryPath.toString());
+		sl.load(libraryPath);
 		failmsg("library already loaded - must throw exception");
 	}
 	catch (LibraryAlreadyLoadedException&)
@@ -134,6 +129,33 @@ void SharedLibraryTest::testSharedLibrary3()
 }
 
 
+std::string SharedLibraryTest::getFullName(const std::string& libName)
+{
+	// make
+	std::string name = Path::expand("$POCO_BASE");
+	char c = Path::separator();
+	std::string OSNAME = Path::expand("$OSNAME");
+	std::string OSARCH = Path::expand("$OSARCH");
+	name.append(1, c)
+		.append(Poco::format("Foundation%ctestsuite%cbin%c", c, c, c))
+		.append(Poco::format("%s%c%s%c", OSNAME, c, OSARCH, c))
+		.append(libName).append(SharedLibrary::suffix());
+
+	// CMake
+	if (!File(name).exists())
+	{
+		name = Path::expand("$POCO_BASE");
+		name.append(Poco::format("%ccmake-build%cbin%c", c, c, c))
+			.append(libName).append(SharedLibrary::suffix());
+	}
+
+	if (!File(name).exists())
+		name = libName + SharedLibrary::suffix();
+
+	return name;
+}
+
+
 void SharedLibraryTest::setUp()
 {
 }

+ 1 - 0
Foundation/testsuite/src/SharedLibraryTest.h

@@ -34,6 +34,7 @@ public:
 	static CppUnit::Test* suite();
 
 private:
+	static std::string getFullName(const std::string& libName);
 };
 
 

+ 165 - 0
Foundation/testsuite/src/TestApp.cpp

@@ -12,12 +12,151 @@
 #define _CRT_SECURE_NO_DEPRECATE
 #endif
 
+#include "Poco/PIDFile.h"
 
 #include <string>
 #include <iostream>
 #include <cstdlib>
 #include <signal.h>
 
+#if defined(POCO_OS_FAMILY_UNIX)
+#include "Poco/Thread.h"
+#include "Poco/Runnable.h"
+#elif defined(POCO_OS_FAMILY_WINDOWS)
+#include "Poco/Process.h"
+#include "Poco/Event.h"
+#include "Poco/NamedEvent.h"
+#endif
+
+using namespace Poco;
+
+#if defined(POCO_OS_FAMILY_UNIX)
+class MyRunnable: public Runnable
+{
+public:
+	MyRunnable(): _ran(false)
+	{
+	}
+
+	void run()
+	{
+		Thread* pThread = Thread::current();
+		if (pThread)
+		{
+			_threadName = pThread->name();
+		}
+		_ran = true;
+	}
+
+	bool ran() const
+	{
+		return _ran;
+	}
+
+private:
+	bool _ran;
+	std::string _threadName;
+};
+#endif
+
+#if POCO_OS != POCO_OS_ANDROID
+class MyApp
+{
+public:
+	MyApp() {}
+	~MyApp() {}
+
+	std::unique_ptr<PIDFile> _pPIDFile;
+#if defined(POCO_OS_FAMILY_WINDOWS)
+	static Poco::Event _terminated;
+	static Poco::NamedEvent _terminate;
+
+
+	static void terminate()
+	{
+		_terminate.set();
+	}
+
+
+	static BOOL WINAPI ConsoleCtrlHandler(DWORD ctrlType)
+	{
+		switch (ctrlType)
+		{
+		case CTRL_C_EVENT:
+		case CTRL_CLOSE_EVENT:
+		case CTRL_BREAK_EVENT:
+			terminate();
+			return _terminated.tryWait(10000) ? TRUE : FALSE;
+		default:
+			return FALSE;
+		}
+	}
+
+	void waitForTerminationRequest()
+	{
+		SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
+		_terminate.wait();
+		_terminated.set();
+	}
+#elif defined(POCO_OS_FAMILY_UNIX)
+	void waitForTerminationRequest()
+	{
+		sigset_t sset;
+		sigemptyset(&sset);
+		if (!std::getenv("POCO_ENABLE_DEBUGGER"))
+		{
+			sigaddset(&sset, SIGINT);
+		}
+		sigaddset(&sset, SIGQUIT);
+		sigaddset(&sset, SIGTERM);
+		sigprocmask(SIG_BLOCK, &sset, NULL);
+		int sig;
+		sigwait(&sset, &sig);
+	}
+
+	int runThreads(std::string pidPath)
+	{
+		_pPIDFile.reset(new PIDFile(pidPath, true));
+
+		uint32_t sigMask = 4; // Block SIGINT
+
+		Thread thread1(sigMask);
+		Thread thread2(sigMask);
+		Thread thread3(sigMask);
+		Thread thread4(sigMask);
+
+		MyRunnable r1;
+		MyRunnable r2;
+		MyRunnable r3;
+		MyRunnable r4;
+
+		thread1.start(r1);
+		thread2.start(r2);
+		thread3.start(r3);
+		thread4.start(r4);
+
+		waitForTerminationRequest();
+
+		thread1.join();
+		thread2.join();
+		thread3.join();
+		thread4.join();
+		return 0;
+	}
+#endif
+
+	int run(std::string pidPath)
+	{
+		_pPIDFile.reset(new PIDFile(pidPath, true));
+		waitForTerminationRequest();
+		return 0;
+	}
+};
+#endif
+#if defined(POCO_OS_FAMILY_WINDOWS)
+Poco::Event MyApp::_terminated;
+Poco::NamedEvent      MyApp::_terminate(Poco::ProcessImpl::terminationEventName(Poco::Process::id()));
+#endif
 
 int main(int argc, char** argv)
 {
@@ -57,6 +196,32 @@ int main(int argc, char** argv)
 				std::cout << argv[i] << std::endl;
 			}
 		}
+#if defined(POCO_OS_FAMILY_UNIX)
+		else if (argc > 2 && arg.find("--pidfile") != std::string::npos && std::string(argv[2]) == "--launch-thread") 
+		{
+			size_t equals_pos = arg.find('=');
+			if (equals_pos != std::string::npos)
+			{
+				std::string pidPath = arg.substr(equals_pos + 1);
+				MyApp myApp;
+				int result = myApp.runThreads(pidPath);
+				return result;
+			}
+		}
+#endif
+#if POCO_OS != POCO_OS_ANDROID
+		else if (arg.find("--pidfile") != std::string::npos || arg.find("-p") != std::string::npos)
+		{
+			size_t equals_pos = arg.find('=');
+			if (equals_pos != std::string::npos)
+			{
+				std::string pidPath = arg.substr(equals_pos + 1);
+				MyApp myApp;
+				int result = myApp.run(pidPath);
+				return result;
+			}
+		}
+#endif
 	}
 	return argc - 1;
 }

+ 1 - 1
build/script/runtests.sh

@@ -87,7 +87,7 @@ do
 				echo ""
 
 				runs=$((runs + 1))
-				if ! sh -c "cd $POCO_BUILD/$comp/testsuite/$BINDIR && PATH=.:$PATH && LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH $TESTRUNNER $IGNORE $TESTRUNNERARGS";
+				if ! sh -c "export POCO_BASE='$POCO_BASE'; export OSNAME='$OSNAME'; export OSARCH='$OSARCH'; cd $POCO_BUILD/$comp/testsuite/$BINDIR && PATH=.:$PATH && LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH $TESTRUNNER $IGNORE $TESTRUNNERARGS";
 				then
 					failures=$((failures + 1))
 					failedTests="$failedTests $comp"

+ 2 - 0
poco_env.bash

@@ -93,6 +93,8 @@ esac
 # uncomment for sanitizer builds
 #LSAN_OPTIONS=verbosity=1:log_threads=1
 #export LSAN_OPTIONS
+#TSAN_OPTIONS="suppressions=$POCO_BASE/tsan.suppress,second_deadlock_stack=1"
+#export TSAN_OPTIONS
 
 echo "\$OSNAME    = $OSNAME"
 echo "\$OSARCH    = $OSARCH"

+ 1 - 1
tsan.suppress

@@ -3,7 +3,7 @@
 # https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions
 #
 # To apply:
-# export TSAN_OPTIONS="suppressions=$POCO_BASE/tsan.supress,second_deadlock_stack=1"
+# export TSAN_OPTIONS="suppressions=$POCO_BASE/tsan.suppress,second_deadlock_stack=1"
 
 ##############
 # Suppressions: