jaulib v1.3.0
Jau Support Library (C++, Java, ..)
test_os_dynlinker.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24#include <cassert>
25#include <cinttypes>
26#include <cstring>
27#include <memory>
28#include <thread>
29#include <pthread.h>
30
32
33#include <jau/file_util.hpp>
34#include <jau/environment.hpp>
35#include <jau/os/dyn_linker.hpp>
36#include <jau/os/native_lib.hpp>
37
38static bool existsPath(const std::string& libPath) noexcept {
39 jau::fs::file_stats path_stats(libPath);
40 return path_stats.exists();
41}
42static bool existsLibBasename(const std::string& libBasename, const std::string& relDir, std::string& libPath) noexcept {
43 const std::string libName = jau::os::DynamicLinker::getCanonicalName(libBasename);
44 libPath = jau::fs::dirname(executable_path) + "/" + relDir + "/"+libName;
45 return existsPath(libPath);
46}
47
48// Test 00 Move testlib.* -> orig/, copy orig/testlib.so.1.2.3 to copy/testlib2.so
49TEST_CASE( "Test00", "[dll][os]" ) {
50 // Paranoia constraints!
51 //
52 // First move testlib.* into a new sub-folder to NOT have system linker:
53 // - find it at cwd
54 //
55 // Second copy testlib.so into a new file to NOT have system linker:
56 // - reuse the already linked native library
57 // - the path location of the already linked native library
58 //
59 // Result:
60 // - test_dir/orig/libtest.so* (all files w/ symlinks)
61 // - test_dir/copy/libtest2.so (single file)
62 //
63 const std::string libBasename = "testlib";
64 const std::string libName = jau::os::DynamicLinker::getCanonicalName(libBasename);
65 const std::string libPathBuild = jau::fs::absolute( jau::fs::dirname(executable_path) ) + "/"+libName;
66 const std::string libPathOrig = jau::fs::absolute( jau::fs::dirname(executable_path) ) + "/orig/"+libName;
67 const std::string symbolName = "jaulib_id_entryfunc";
68 if( !existsPath(libPathBuild) ) {
69 if( !existsPath(libPathOrig) ) {
70 std::cout << "Warning: library '" << libBasename << "' doesn't exist at: build '" << libPathBuild << "', nor at orig '" << libPathOrig << "'" << std::endl;
71 return;
72 }
73 // OK, already moved
74 } else {
75 // move test_dir/testlib.* to test_dir/orig/testlib.* (overwrite)
76 jau::fs::file_stats path_stats(libPathBuild);
77 const std::string libPath = path_stats.final_target()->path();
78 const std::string libDir = jau::fs::dirname(libPath);
79 const std::string libDirOrig = libDir + "/orig";
80
81 // recreate test_dir/orig
82 std::cout << "remove: " << libDirOrig << std::endl;
84 REQUIRE( true == jau::fs::mkdir(libDirOrig, jau::fs::fmode_t::def_dir_prot, true) );
85
86 // move all libName* from test_dir to test_dir/orig at depth 1 (flat)
87 {
91 std::cout << "move: libs in '" << libDir << "' to '" << libDirOrig << "'" << std::endl;
92 const jau::fs::path_visitor pv = [&](jau::fs::traverse_event tevt, const jau::fs::file_stats& element_stats, size_t depth) -> bool {
94 std::cout << "- move: ignore entry depth[" << depth << "]" << element_stats.item().to_string() << std::endl;
95 return false;
96 }
97 if( jau::fs::traverse_event::none != ( tevt & ( jau::fs::traverse_event::file | jau::fs::traverse_event::symlink ) ) ) { // at least one of: file + link
98 std::string bname = element_stats.item().basename();
99 if( 0 == bname.find(libName) ) { // starts-with
100 const std::string p = libDirOrig + "/" + bname;
101 std::cout << "- move: depth[" << depth << "]: '" << element_stats.path() << "' to '" << p << "'" << std::endl;
102 jau::fs::rename(element_stats.path(), p);
103 }
104 }
105 return true;
106 };
107 REQUIRE( true == jau::fs::visit(libDir, topts, pv) );
108 jau::fs::file_stats path_stats2(libPathOrig);
109 std::cout << "post move: " << path_stats2.to_string() << std::endl;
110 REQUIRE( true == existsPath(libPathOrig) );
111 }
112 }
113 // copy test_dir/orig/testlib.so.1.2.3 to test_dir/copy/testlib2.so
114 {
118 jau::fs::file_stats path_stats(libPathOrig);
119 const std::string libPathOrigFile = path_stats.final_target()->path();
120 const std::string libDirOrig = jau::fs::dirname(libPathOrigFile);
121
122 const std::string libBasenameCopy = "testlib2";
123 const std::string libNameCopy = jau::os::DynamicLinker::getCanonicalName(libBasenameCopy);
124 const std::string libDirCopy = jau::fs::absolute( jau::fs::dirname(executable_path) ) + "/copy";
125
126 // recreate test_dir/copy
127 if( existsPath( libDirCopy ) ) {
128 std::cout << "remove: " << libDirCopy << std::endl;
130 }
131 REQUIRE( true == jau::fs::mkdir(libDirCopy, jau::fs::fmode_t::def_dir_prot, true) );
132
133 const std::string libPathCopy = libDirCopy + "/" + libNameCopy;
134 REQUIRE( true == jau::fs::copy(libPathOrigFile, libPathCopy, copts) );
135 REQUIRE( true == existsPath(libPathCopy) );
136 std::string libPath2;
137 REQUIRE( true == existsLibBasename(libBasenameCopy, "copy", libPath2) );
138 }
139}
140
141void test01DynamikLinkerAbs(const std::string& libBasename, const std::string& relDir) {
142 const std::string symbolName = "jaulib_id_entryfunc";
143 const std::string libName = jau::os::DynamicLinker::getCanonicalName(libBasename);
144 std::cout << "- libBasename: " << libBasename << std::endl;
145 std::cout << "- libName: " << libName << std::endl;
146
147 std::string libPath;
148 {
149 std::cout << "- cwd: " << jau::fs::get_cwd() << std::endl;
150 std::cout << "- exe " << executable_path << std::endl << std::endl;
151 if( !existsLibBasename(libBasename, relDir, libPath) ) {
152 std::cout << "Warning: library '" << libName << "' doesn't exist at: '" << libPath << "'" << std::endl;
153 return;
154 }
155 }
158 std::cout << "- Path: " << libPath << std::endl;
159 std::cout << "- LibHandle: " << jau::to_hexstring(libHandle) << std::endl;
160 REQUIRE( nullptr != libHandle );
161
162 jau::os::DynamicLinker::symhandle_t symHandle = dl.lookupSymbol(libHandle, symbolName);
163 std::cout << "- Symbol '" << symbolName << "': Handle = " << jau::to_hexstring(symHandle) << std::endl;
164 REQUIRE( nullptr != symHandle );
165
166 const char* nativePath = dl.lookupLibraryPathname(libHandle, symbolName);
167 if( nullptr == nativePath ) {
168 std::cout << "- Native Path: null" << std::endl;
169 } else {
170 std::cout << "- Native Path: '" << std::string(nativePath) << "'" << std::endl;
171 }
172 REQUIRE( nullptr != nativePath );
173
174 {
175 std::string bname1 = jau::os::DynamicLinker::getBaseName(libPath);
176 std::cout << "- Basename (path): " << bname1 << std::endl;
177 REQUIRE( libBasename == bname1 );
178 if( nullptr != nativePath ) {
179 std::string bname2 = jau::os::DynamicLinker::getBaseName(std::string(nativePath));
180 std::cout << "- Basename (native-path): " << bname2 << std::endl;
181 REQUIRE( libBasename == bname2 );
182 }
183 }
184}
185
186// Test 01 Local Open dlsym etc
187TEST_CASE( "Test01", "[dll][os]" ) {
188 {
189 std::string lib_path_var_name = jau::os::DynamicLinker::getEnvLibPathVarName();
190 std::string lib_path_var = jau::environment::getProperty( lib_path_var_name );
191 std::vector<std::string> lib_paths = jau::os::DynamicLinker::getSystemEnvLibraryPaths();
192 std::cout << "- lib_path_var_name: " << lib_path_var_name << std::endl;
193 std::cout << "- lib_path_var : " << lib_path_var << std::endl;
194 std::cout << "- lib_paths: count : " << lib_paths.size() << std::endl << " - path: ";
195 std::cout << jau::to_string(lib_paths, "\n - path: ") << std::endl;
196 }
197 test01DynamikLinkerAbs("testlib", "orig");
198 test01DynamikLinkerAbs("testlib2", "copy");
199}
200
201void test10NativeLibrary(const std::string& libBasename, const std::string& libDirRel) {
202 const std::string symbolName = "jaulib_id_entryfunc";
203 const std::string libName = jau::os::DynamicLinker::getCanonicalName(libBasename);
204 std::cout << "- libBasename: " << libBasename << std::endl;
205 std::cout << "- libName: " << libName << std::endl;
206 const std::string exe_path_abs = jau::fs::absolute(executable_path);
207 const std::string exe_dir = jau::fs::dirname(exe_path_abs);
208 const std::string cwd = jau::fs::get_cwd();
209 const std::string libPathRel = libDirRel + "/" + libName;
210 std::string libDirAbs, libPathAbs;
211 {
212 std::cout << "- cwd: " << cwd << std::endl;
213 std::cout << "- exe-rel " << executable_path << std::endl;
214 std::cout << "- exe-abs " << exe_path_abs << std::endl << std::endl;
215 {
216 jau::fs::file_stats fs(exe_dir + "/" + libDirRel);
217 REQUIRE( true == fs.exists() );
218 libDirAbs = fs.final_target()->path();
219 }
220 libPathAbs = libDirAbs + "/" + libName;
221
222 if( !existsPath(libPathAbs) ) {
223 std::cout << "Warning: library '" << libName << "' doesn't exist at: '" << libPathAbs << "'" << std::endl;
224 return;
225 }
226 }
227 std::string lib_path_var_name = jau::os::DynamicLinker::getEnvLibPathVarName();
228 std::string lib_path_var0 = jau::environment::getProperty( lib_path_var_name );
229 {
230 std::cout << "Sys-Path: '" << lib_path_var_name << "': Original" << std::endl;
231 std::vector<std::string> lib_paths1 = jau::os::DynamicLinker::getSystemEnvLibraryPaths();
232 std::cout << "- lib_path_var (1) : " << lib_path_var0 << std::endl;
233 std::cout << "- lib_paths: count : " << lib_paths1.size() << std::endl << " - path: '";
234 std::cout << jau::to_string(lib_paths1, "'\n - path: '") << "'" << std::endl;
235 }
236
237 // 1: Test with libPathAbs, no search
238 {
239 const std::string cwd2 = jau::fs::get_cwd();
240 const bool searchSystemPath = false;
241 const bool searchSystemPathFirst = false;
242 const bool global = false;
243 std::cout << "Check-1: Absolute Path: '" << libPathAbs << "', cwd2 '" << cwd2 << "'" << std::endl;
244 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libPathAbs, searchSystemPath, searchSystemPathFirst, global, symbolName);
245 std::cout << "Check-1: " << nl.toString() << std::endl;
246 REQUIRE( true == nl.isValid() );
247 REQUIRE( true == nl.isOpen() );
248 nl.close();
249 REQUIRE( false == nl.isOpen() );
250 REQUIRE( true == nl.isValid() );
251 }
252
253 // 2: Test with libPathRel, cd into test_exe path and search from cwd (no sys)
254 {
255 REQUIRE( true == jau::fs::chdir(exe_dir) );
256 {
257 const std::string cwd2 = jau::fs::get_cwd();
258 const bool searchSystemPath = false;
259 const bool searchSystemPathFirst = false;
260 const bool global = false;
261 std::cout << "Check-2: Relative Path to cwd: libPathRel '" << libPathRel << "', cwd2 '" << cwd2 << "'" << std::endl;
262 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libPathRel, searchSystemPath, searchSystemPathFirst, global, symbolName);
263 std::cout << "Check-2: " << nl.toString() << std::endl;
264 REQUIRE( true == nl.isValid() );
265 REQUIRE( true == nl.isOpen() );
266 nl.close();
267 REQUIRE( false == nl.isOpen() );
268 REQUIRE( true == nl.isValid() );
269 }
270 REQUIRE( true == jau::fs::chdir(cwd) );
271 }
272
273 // Add libDirAbs to orig sys-path
274 {
275 std::cout << "Sys-Path: '" << lib_path_var_name << "': Variant 1: With libDirAbs" << std::endl;
276 std::string lib_path_var2;
277 if( lib_path_var0.size() > 0 ) {
278 lib_path_var2 = lib_path_var0+jau::os::path_separator()+libDirAbs;
279 } else {
280 lib_path_var2 = libDirAbs;
281 }
282 ::setenv(lib_path_var_name.c_str(), lib_path_var2.c_str(), 1 /* overwrite */);
283 std::cout << "- lib_path_var set 2: " << lib_path_var2 << std::endl;
284
285 std::string lib_path_var = jau::environment::getProperty( lib_path_var_name );
286 std::vector<std::string> lib_paths = jau::os::DynamicLinker::getSystemEnvLibraryPaths();
287 std::cout << "- lib_path_var get 2: " << lib_path_var << std::endl;
288 std::cout << "- lib_paths: count : " << lib_paths.size() << std::endl << " - path: '";
289 std::cout << jau::to_string(lib_paths, "'\n - path: '") << "'" << std::endl;
290 }
291
292 // 10+11: Test with libBasename + libName, search from sys-path (incl. libDirAbs)
293 {
294 const bool searchSystemPath = true;
295 const bool searchSystemPathFirst = true;
296 const bool global = false;
297 {
298 const std::string cwd2 = jau::fs::get_cwd();
299 std::cout << "Check-10: libBasename in sys: '" << libBasename << "', cwd2 '" << cwd2 << "'" << std::endl;
300 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libBasename, searchSystemPath, searchSystemPathFirst, global, symbolName);
301 std::cout << "Check-10: " << nl.toString() << std::endl;
302 REQUIRE( true == nl.isValid() );
303 REQUIRE( true == nl.isOpen() );
304 nl.close();
305 REQUIRE( false == nl.isOpen() );
306 REQUIRE( true == nl.isValid() );
307 }
308 {
309 const std::string cwd2 = jau::fs::get_cwd();
310 std::cout << "Check-11: libName in sys: '" << libName << "', cwd2 '" << cwd2 << "'" << std::endl;
311 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libName, searchSystemPath, searchSystemPathFirst, global, symbolName);
312 std::cout << "Check-11: " << nl.toString() << std::endl;
313 REQUIRE( true == nl.isValid() );
314 REQUIRE( true == nl.isOpen() );
315 nl.close();
316 REQUIRE( false == nl.isOpen() );
317 REQUIRE( true == nl.isValid() );
318 }
319 }
320
321 // Add test_exe path to orig sys-path
322 {
323 std::cout << "Sys-Path: '" << lib_path_var_name << "': Variant 2: With test_exe path" << std::endl;
324 std::string lib_path_var2;
325 if( lib_path_var0.size() > 0 ) {
326 lib_path_var2 = lib_path_var0+jau::os::path_separator()+exe_dir;
327 } else {
328 lib_path_var2 = exe_dir;
329 }
330 ::setenv(lib_path_var_name.c_str(), lib_path_var2.c_str(), 1 /* overwrite */);
331 std::cout << "- lib_path_var set 3: " << lib_path_var2 << std::endl;
332
333 std::string lib_path_var = jau::environment::getProperty( lib_path_var_name );
334 std::vector<std::string> lib_paths = jau::os::DynamicLinker::getSystemEnvLibraryPaths();
335 std::cout << "- lib_path_var get 3: " << lib_path_var << std::endl;
336 std::cout << "- lib_paths: count : " << lib_paths.size() << std::endl << " - path: '";
337 std::cout << jau::to_string(lib_paths, "'\n - path: '") << "'" << std::endl;
338 }
339
340#if 1
341 // 12: Test with libPathRel, search from sys-path (incl. text_exe path)
342 {
343 const bool searchSystemPath = true;
344 const bool searchSystemPathFirst = true;
345 const bool global = true;
346 std::cout << "Check-12: Relative Path to cwd: " << libPathRel << std::endl;
347 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libPathRel, searchSystemPath, searchSystemPathFirst, global, symbolName);
348 std::cout << "Check-12: " << nl.toString() << std::endl;
349 REQUIRE( true == nl.isValid() );
350 REQUIRE( true == nl.isOpen() );
351 nl.close();
352 REQUIRE( false == nl.isOpen() );
353 REQUIRE( true == nl.isValid() );
354 }
355#endif
356}
357
358// Test 10 NativeLibrary Find / Open dlsym etc using orig filenames
359TEST_CASE( "Test10", "[orig][NativeLibrary][dll][os]" ) {
360 test10NativeLibrary("testlib", "orig");
361}
362
363// Test 10 NativeLibrary Find / Open dlsym etc using a copy w/ changed filename
364TEST_CASE( "Test11", "[copy][NativeLibrary][dll][os]" ) {
365 test10NativeLibrary("testlib2", "copy");
366}
std::string executable_path
The main argv[0] test executable path.
static std::string getProperty(const std::string &name) noexcept
Returns the value of the environment's variable 'name'.
Definition: environment.cpp:60
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
Definition: file_util.hpp:406
constexpr bool exists() const noexcept
Returns true if entity does not exist, exclusive bit.
Definition: file_util.hpp:648
const file_stats * final_target(size_t *link_count=nullptr) const noexcept
Returns the final target element, either a pointer to this instance if not a symbolic-link or the fin...
Definition: file_util.cpp:819
std::string to_string() const noexcept
Returns a comprehensive string representation of this element.
Definition: file_util.cpp:859
std::string path() const noexcept
Returns the unix path representation.
Definition: file_util.hpp:530
Class template jau::function is a general-purpose static-polymorphic function wrapper.
Low level secure dynamic linker access.
Definition: dyn_linker.hpp:45
static std::string getBaseName(const std::string &filename, const bool isBasename=false, const bool caseInsensitive=jau::os::is_windows()) noexcept
Returns the library basename, i.e.
Definition: os_support.cpp:262
static DynamicLinker & get() noexcept
Returns static singleton instance of DynamicLinker.
Definition: dyn_linker.hpp:235
symhandle_t lookupSymbol(libhandle_t handle, const std::string &symbolName) noexcept
Definition: dyn_linker.hpp:320
void * symhandle_t
symbol handle within a library
Definition: dyn_linker.hpp:50
static constexpr_cxx20 std::string getEnvLibPathVarName() noexcept
Returns the environment library path variable name, e.g.
Definition: dyn_linker.hpp:143
void * libhandle_t
library handle
Definition: dyn_linker.hpp:48
const char * lookupLibraryPathname(libhandle_t handle, const std::string &symbolName) noexcept
Definition: dyn_linker.hpp:293
static constexpr_cxx20 std::string getCanonicalName(const std::string &basename, const bool checkIsCanonical=true) noexcept
Returns canonical library name for this system from given library-basename, e.g.
Definition: dyn_linker.hpp:186
libhandle_t openLibraryLocal(const std::string &pathname) noexcept
Opens the named library, restricting access to this process.
Definition: dyn_linker.hpp:275
static std::vector< std::string > getSystemEnvLibraryPaths()
Returns a list of system paths, from the getSystemEnvLibraryPathVarname() variable.
Definition: dyn_linker.hpp:155
Runtime libary dynamic library (RTLD) access.
Definition: native_lib.hpp:44
bool isValid() const noexcept
Returns true if this instance is valid, i.e.
Definition: native_lib.hpp:95
void close() noexcept
Closes this native library.
Definition: native_lib.hpp:115
std::string toString() const noexcept
Definition: native_lib.hpp:125
bool isOpen() const noexcept
Returns true if this instance isValid() and not close()'ed, otherwise false.
Definition: native_lib.hpp:98
static NativeLibrary open(const std::string &libName, const bool searchSystemPath, const bool searchSystemPathFirst, const bool global) noexcept
Opens the given native library, assuming it has the same base name on all platforms.
Definition: native_lib.hpp:152
std::string to_string(const endian_t v) noexcept
Return std::string representation of the given endian.
bool mkdir(const std::string &path, const fmode_t mode=jau::fs::fmode_t::def_dir_prot, const bool verbose=false) noexcept
Create directory.
Definition: file_util.cpp:909
bool copy(const std::string &source_path, const std::string &dest_path, const copy_options copts=copy_options::none) noexcept
Copy the given source_path to dest_path using copy_options.
Definition: file_util.cpp:1720
std::string get_cwd() noexcept
Return the current working directory or empty on failure.
Definition: file_util.cpp:82
bool visit(const std::string &path, const traverse_options topts, const path_visitor &visitor, std::vector< int > *dirfds=nullptr) noexcept
Visit element(s) of a given path, see traverse_options for detailed settings.
Definition: file_util.cpp:1169
std::string absolute(const std::string_view &relpath) noexcept
Returns the absolute path of given relpath if existing, otherwise an empty string.
Definition: file_util.cpp:102
std::string dirname(const std::string_view &path) noexcept
Return stripped last component from given path separated by /, excluding the trailing separator /.
Definition: file_util.cpp:129
copy_options
Filesystem copy options used to copy() path elements.
Definition: file_util.hpp:1035
traverse_options
Filesystem traverse options used to visit() path elements.
Definition: file_util.hpp:879
bool rename(const std::string &oldpath, const std::string &newpath) noexcept
Rename oldpath to newpath using POSIX rename(), with the following combinations.
Definition: file_util.cpp:1916
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
Definition: file_util.cpp:1173
constexpr bool is_set(const fmode_t mask, const fmode_t bits) noexcept
Definition: file_util.hpp:342
bool chdir(const std::string &path) noexcept
Change working directory.
Definition: file_util.cpp:98
traverse_event
Filesystem traverse event used to call path_visitor for path elements from visit().
Definition: file_util.hpp:763
@ def_dir_prot
Default directory protection bit: Safe default: POSIX S_IRWXU | S_IRGRP | S_IXGRP or rwx_usr | read_g...
@ verbose
Enable verbosity mode, show error messages on stderr.
@ overwrite
Overwrite existing destination files.
@ preserve_all
Preserve uid and gid if allowed and access- and modification-timestamps, i.e.
@ verbose
Enable verbosity mode, potentially used by a path_visitor implementation like remove().
@ dir_check_entry
Call path_visitor at directory entry, allowing path_visitor to skip traversal of this directory if re...
@ recursive
Traverse through directories, i.e.
@ none
No value, neither file, symlink nor dir_entry or dir_exit.
@ file
Visiting a file, may be in conjunction with symlink, i.e.
@ dir_check_entry
Visiting a directory on entry, see traverse_options::dir_check_entry.
@ symlink
Visiting a symbolic-link, either to a file or a non-existing entity.
constexpr_cxx20 std::string path_separator() noexcept
Returns the OS's path separator as a string, e.g.
Definition: os_support.hpp:335
std::string to_hexstring(value_type const &v) noexcept
Produce a lower-case hexadecimal string representation of the given pointer.
static bool existsLibBasename(const std::string &libBasename, const std::string &relDir, std::string &libPath) noexcept
void test10NativeLibrary(const std::string &libBasename, const std::string &libDirRel)
TEST_CASE("Test00", "[dll][os]")
void test01DynamikLinkerAbs(const std::string &libBasename, const std::string &relDir)
static bool existsPath(const std::string &libPath) noexcept