diff --git a/.vscode/settings.json b/.vscode/settings.json index 6aa78f6c..e30f6b86 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -59,6 +59,9 @@ "C_Cpp.clang_format_style": "file", "C_Cpp.default.cStandard": "c17", "C_Cpp.default.cppStandard": "c++17", + "C_Cpp.default.defines": [ + // "STLSOFT_DOCUMENTATION_SKIP_SECTION", + ], "C_Cpp.enhancedColorization": "enabled", "C_Cpp.errorSquiggles": "enabled", "C_Cpp.formatting": "clangFormat", diff --git a/CHANGES.md b/CHANGES.md index a4b17da0..77f1ad08 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ## 1.11.1-rc9 - 18th October 2026 * additions: + * Added `unixstl::system_traits<>::get_current_process_path()` and `winstl::system_traits<>::get_current_process_path()` (multibyte and wide), obtaining the current process image path with an optional basename path-separator out-parameter; + * Added `T_resizeableBuffer` overloads of `get_current_process_path()` on **unixstl** / **winstl** `system_traits` (query → `stlsoft::resizeable_buffer_resize()` → fill; `ppFile` only after the final fill; resize failure sets `ENOMEM` / `ERROR_OUTOFMEMORY` and leaves the buffer unchanged); * **unixstl::filesystem_traits** — implemented `find_last_path_name_separator()` / `find_next_path_name_separator()` and `is_path_rooted()` / `is_path_absolute()` / `is_path_UNC()` (NUL-terminated and counted), including Windows drive-letter and UNC classification; * **unixstl::filesystem_traits** — path-resolve, current-directory, and directory-enumeration members remain declared, pending one conversion policy for that group; * **unixstl_C_get_home_directory_w()** (**unixstl/system/directory_functions.h**) — reads `HOME` via `getenv()` and converts with `mbstowcs()` (locale multibyte encoding); a short buffer keeps a NUL in the last slot and returns `cchBuffer - 1`, matching the narrow copy; @@ -11,21 +13,23 @@ * **unixstl::system_traits** — implemented `get_environment_variable()` (raw and resizeable-buffer) by converting the name with `wcstombs()`, reading it with `getenv()`, and converting the value with `mbstowcs()`; implemented the resizeable-buffer overload of `get_home_directory()`; * **unixstl::system_traits** — `load_library()` and `free_library()` stay declared together pending one conversion policy (`free_library()` no longer calls `dlclose()` alone); `expand_environment_strings()` remains declared; * testing: + * Component tests **test/component/unixstl/system/process_paths** (multibyte; multi-OS raw path oracle; resizeable-buffer overloads for `auto_buffer` / `vector`, `ppFile`, and resize-failure) and **test/component/winstl/system/process_paths** (multibyte and wide; `GetModuleFileName` oracle; same resizeable-buffer coverage); * Unit tests **test.unit.unixstl.filesystem.filesystem_traits** cover wide separator finding and path classification, and agreement with the narrow specialisation; * Unit tests **test.unit.unixstl.system.system_traits** cover wide `path_str_compare()` and `path_str_n_compare()`; * Component tests **test.component.unixstl.system.system_traits** cover wide `get_home_directory()` and `get_environment_variable()` (probe, fixed buffer, and `std::vector` resizeable overload) via `platformstl::environment_variable_scope`; * Resizeable-buffer cases shared with the resizeable-buffer line (`ensure_dir_end()` / `remove_dir_end()`, and the narrow resizeable `get_home_directory()` / `get_environment_variable()` overloads) remain empty pending that merge; + * **test.unit.versions** — aligned with **1.11.1-rc9** (**_STLSOFT_VER_1_11_1_RC9**); * docs: * Normalised Doxygen `\pre` comments to bare boolean expressions (no outer parentheses; no trailing `;` / `.`); * Prefer `nullptr` over `NULL` / prose in `\pre` text; optional-buffer forms use zero-length check first (`0 == n || nullptr != s`); * Converted expressible prose preconditions (e.g. `!empty()`, `n <= size()`, `nullptr != p`) where applicable; retained soft COM / prior-init / exception-gated contracts as prose; * version: * **test.unit.versions** — aligned with **1.11.1-rc9** (**_STLSOFT_VER_1_11_1_RC9**); - * **unixstl** — **1.8.6 beta 8** (**_UNIXSTL_VER_1_8_6_B08**), with **STLSoft 1.11.1-rc9**; - * **winstl** — **1.13.0 beta 5** (**_WINSTL_VER_1_13_0_B05**), with **STLSoft 1.11.1-rc9**; + * **comstl** — **1.10.4** (**_COMSTL_VER_1_10_4**), with **STLSoft 1.11.1-rc9**; * **inetstl** — **1.5.0 alpha 4** (**_INETSTL_VER_1_5_0_A04**), with **STLSoft 1.11.1-rc9**; * **platformstl** — **1.10.0 beta 3** (**_PLATFORMSTL_VER_1_10_0_B03**), with **STLSoft 1.11.1-rc9**; - * **comstl** — **1.10.4** (**_COMSTL_VER_1_10_4**), with **STLSoft 1.11.1-rc9**; + * **unixstl** — **1.8.6 beta 8** (**_UNIXSTL_VER_1_8_6_B08**), with **STLSoft 1.11.1-rc9**; + * **winstl** — **1.13.0 beta 5** (**_WINSTL_VER_1_13_0_B05**), with **STLSoft 1.11.1-rc9**; ## 1.11.1-rc8 - 10th October 2026 diff --git a/NEWS.md b/NEWS.md index 8660f2fa..f4fa50a6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,7 +3,7 @@ | Date | News Item | Details | | ------------------- | ----------------------------------- | ------- | -| 18th October 2026 | Release of [STLSoft 1.11.1-rc9](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc9) | **unixstl** `wchar_t` path classification and separators; `path_str_*`; wide `get_home_directory()` and `get_environment_variable()`; `\pre` normalised to bare exprs with `nullptr`; companion version bumps | +| 18th October 2026 | Release of [STLSoft 1.11.1-rc9](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc9) | `get_current_process_path()` (+ resizeable-buffer overloads) on **unixstl** / **winstl** `system_traits` with component tests; zero-first optional buffers; **unixstl** `wchar_t` path classification and separators; `path_str_*`; wide `get_home_directory()` and `get_environment_variable()`; `\pre` normalised to bare exprs with `nullptr`; subproject version bumps | | 10th October 2026 | Release of [STLSoft 1.11.1-rc8](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc8) | environment_variable_strtoll()`; `unixstl::filesystem_traits<>::rewind_dir()`; `class_type` and `getenv_s()` empty-variable fixes; component tests; `cmake --build` helper scripts; **run_all_component_tests.sh** / **run_all_automated_tests.sh**; **generate_doxygen.sh**; Linguist classification | | 30th September 2026 | Release of [STLSoft 1.11.1-rc7](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc7) | counted NULs; `wchar_t` `string_begins_with`; bit intrinsics; `write_binary()`; `file_lines` mmap (~10–20× vs `fgetc`); compare and allocator units; MinGW static runtime; short test directories; nested **test/performance** | | 21st September 2026 | Release of [STLSoft 1.11.1-rc6](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc6) | ACE/ATL/MFC CI; Clang/macOS `stricmp` aliases | diff --git a/TODO.md b/TODO.md index d997851e..ebf2c06f 100644 --- a/TODO.md +++ b/TODO.md @@ -17,6 +17,7 @@ - [1.11.9](#1119) - [1.11.10](#11110) - [1.11.11](#11111) + - [Cross-publish to STLSoft-1.11](#cross-publish-to-stlsoft-111) - [1.12](#112) - [Requires clarification by project maintainers](#requires-clarification-by-project-maintainers) - [Abandoned](#abandoned) @@ -34,6 +35,14 @@ * [ ] Remove **WTL**; * [ ] Resolve finally the structure and semantics of the top-level execution scripts (**run_all_unit_tests.sh**, **run_all_scratch_tests.sh**, **run_all_examples.sh**, **execute_performance_tests.sh**, and Windows `.cmd` counterparts) on all platforms — discovery globs, execute-bit / artifact restore, zero-match failure, verbosity, and CI wiring; * [ ] bring in some essential unit-tests from interal repo; +* [ ] Being the CONCEPTS documentation: + * [ ] auto-buffer; + * [ ] resizeable-buffer; + * [ ] string-access-shims; + * [ ] Null Object (variable); + * [ ] Externals (esp. not introducing sub-project's headers proper); + * [ ] Conformance: Structural, Semantic, Intersecting; + * [ ] ... ### 1.11.2 @@ -98,6 +107,13 @@ Dedicated increment: **test analysis and coverage only** — no other product ch * [ ] Doxygen build (**Doxyfile**, **generate_doxygen.sh**) and deeper API documentation — see also [**KNOWN_ISSUES.md**](./KNOWN_ISSUES.md); +### Cross-publish to STLSoft-1.11 + +When tip **1.11** is finally complete, cross-publish into the versioned line **[synesissoftware/STLSoft-1.11](https://github.com/synesissoftware/STLSoft-1.11)** (not done for the interim VOLE / historical-consumer recognition work — that line was left unpushed on purpose). + +* [ ] Cross-publish the completed tip **1.11** tree into **STLSoft-1.11**, including the unknown-newer Visual C++ warn-not-error forward-warning from the **`prospective-msvc`** branch (and any related recognition / semi-freeze messaging needed for historical consumers); + + ## 1.12 From **1.12** onwards, every new component or feature must ship with an **example**, **unit tests**, and a **blog entry** on the new Website (website work is contemporaneous with 1.12). See **oss-library-modernisation** (STLSoft 1.12+ feature completeness). diff --git a/include/stlsoft/stlsoft.h b/include/stlsoft/stlsoft.h index da78b79a..eaffb4b3 100644 --- a/include/stlsoft/stlsoft.h +++ b/include/stlsoft/stlsoft.h @@ -55,8 +55,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define STLSOFT_VER_STLSOFT_H_STLSOFT_MAJOR 3 # define STLSOFT_VER_STLSOFT_H_STLSOFT_MINOR 57 -# define STLSOFT_VER_STLSOFT_H_STLSOFT_REVISION 20 -# define STLSOFT_VER_STLSOFT_H_STLSOFT_EDIT 609 +# define STLSOFT_VER_STLSOFT_H_STLSOFT_REVISION 21 +# define STLSOFT_VER_STLSOFT_H_STLSOFT_EDIT 610 #else /* ? STLSOFT_DOCUMENTATION_SKIP_SECTION */ /* # include "./internal/doxygen_defs.h" */ #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ diff --git a/include/unixstl/process/functions.h b/include/unixstl/process/functions.h index ddf65e78..530b2f49 100644 --- a/include/unixstl/process/functions.h +++ b/include/unixstl/process/functions.h @@ -4,7 +4,7 @@ * Purpose: Process functions. * * Created: 8th October 2026 - * Updated: 8th October 2026 + * Updated: 15th October 2026 * * Home: http://stlsoft.org/ * @@ -51,9 +51,9 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define UNIXSTL_VER_UNIXSTL_PROCESS_H_FUNCTIONS_MAJOR 1 -# define UNIXSTL_VER_UNIXSTL_PROCESS_H_FUNCTIONS_MINOR 0 -# define UNIXSTL_VER_UNIXSTL_PROCESS_H_FUNCTIONS_REVISION 1 -# define UNIXSTL_VER_UNIXSTL_PROCESS_H_FUNCTIONS_EDIT 1 +# define UNIXSTL_VER_UNIXSTL_PROCESS_H_FUNCTIONS_MINOR 1 +# define UNIXSTL_VER_UNIXSTL_PROCESS_H_FUNCTIONS_REVISION 3 +# define UNIXSTL_VER_UNIXSTL_PROCESS_H_FUNCTIONS_EDIT 4 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -68,17 +68,78 @@ # pragma message(__FILE__) #endif /* STLSOFT_TRACE_INCLUDE */ +#ifndef STLSOFT_INCL_STLSOFT_MEMORY_H_AUTO_BUFFER +# include +#endif /* !STLSOFT_INCL_STLSOFT_MEMORY_H_AUTO_BUFFER */ + +#ifndef STLSOFT_INCL_H_ERRNO +# define STLSOFT_INCL_H_ERRNO +# include +#endif /* !STLSOFT_INCL_H_ERRNO */ +#ifndef STLSOFT_INCL_H_LIMITS +# define STLSOFT_INCL_H_LIMITS +# include +#endif /* !STLSOFT_INCL_H_LIMITS */ +#ifndef STLSOFT_INCL_H_STDLIB +# define STLSOFT_INCL_H_STDLIB +# include +#endif /* !STLSOFT_INCL_H_STDLIB */ +#ifndef STLSOFT_INCL_H_STRING +# define STLSOFT_INCL_H_STRING +# include +#endif /* !STLSOFT_INCL_H_STRING */ + #if 0 -#elif defined(_WIN32) &&\ - ( 0 ||\ - defined(STLSOFT_COMPILER_IS_MSVC) ||\ - defined(STLSOFT_COMPILER_IS_INTEL) ||\ - 0) +#elif defined(_WIN32) + +# ifndef WINSTL_INCL_WINSTL_API_external_h_DynamicLinkLibrary +# include +# endif /* !WINSTL_INCL_WINSTL_API_external_h_DynamicLinkLibrary */ # ifndef STLSOFT_INCL_H_PROCESS # define STLSOFT_INCL_H_PROCESS # include # endif /* !STLSOFT_INCL_H_PROCESS */ +#elif defined(__APPLE__) + +# ifndef STLSOFT_INCL_H_LIBPROC +# define STLSOFT_INCL_H_LIBPROC +# include +# endif /* !STLSOFT_INCL_H_LIBPROC */ +# ifndef STLSOFT_INCL_MACH_O_H_DYLD +# define STLSOFT_INCL_MACH_O_H_DYLD +# include +# endif /* !STLSOFT_INCL_MACH_O_H_DYLD */ +# ifndef STLSOFT_INCL_H_UNISTD +# define STLSOFT_INCL_H_UNISTD +# include +# endif /* !STLSOFT_INCL_H_UNISTD */ +#elif defined(__linux__) + +# ifndef STLSOFT_INCL_H_UNISTD +# define STLSOFT_INCL_H_UNISTD +# include +# endif /* !STLSOFT_INCL_H_UNISTD */ +#elif 0 ||\ + defined(__FreeBSD__) ||\ + defined(__NetBSD__) ||\ + defined(__OpenBSD__) ||\ + 0 + +# ifndef STLSOFT_INCL_SYS_H_SYSCTL +# define STLSOFT_INCL_SYS_H_SYSCTL +# include +# endif /* !STLSOFT_INCL_SYS_H_SYSCTL */ +# ifndef STLSOFT_INCL_SYS_H_TYPES +# define STLSOFT_INCL_SYS_H_TYPES +# include +# endif /* !STLSOFT_INCL_SYS_H_TYPES */ +#elif defined(__sun) + +# ifndef STLSOFT_INCL_H_UNISTD +# define STLSOFT_INCL_H_UNISTD +# include +# endif /* !STLSOFT_INCL_H_UNISTD */ #else # ifndef STLSOFT_INCL_SYS_H_TYPES @@ -92,6 +153,19 @@ #endif +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +#ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION +# if !defined(PATH_MAX) +# define UNIXSTL_C_GET_CURRENT_PROCESS_PATH_INTERNAL_SIZE_ 4096 +# else +# define UNIXSTL_C_GET_CURRENT_PROCESS_PATH_INTERNAL_SIZE_ PATH_MAX +# endif +#endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ + + /* ///////////////////////////////////////////////////////////////////////// * namespace */ @@ -128,11 +202,7 @@ int unixstl_C_get_current_process_id(void) { #if 0 -#elif defined(_WIN32) && \ - ( 0 ||\ - defined(STLSOFT_COMPILER_IS_MSVC) ||\ - defined(STLSOFT_COMPILER_IS_INTEL) ||\ - 0) +#elif defined(_WIN32) return STLSOFT_NS_GLOBAL(_getpid)(); #else @@ -143,6 +213,262 @@ unixstl_C_get_current_process_id(void) #endif } +/** \brief Obtains the fully qualified path of the current process image + * (multibyte). + * + * \ingroup group__library__System + * + * \param buff Destination buffer. May be \c NULL if and only if \c cch is + * 0; + * \param cch Number of characters available in \c buff, including space for + * a terminating NUL; + * + * \return If \c buff is sufficient for the full path and a terminating NUL + * character then this returns the length of the path (excluding the NUL + * character). Otherwise, as much of the result as can be written is done + * so and a character count of at least the full length + 1 (for + * the NUL character) is returned. The returned demand may exceed that + * minimum. Returns 0 on failure; + * + * \pre 0 == cch || NULL != buff + * + * \note The normative client pattern is to call with \c cch of 0 to obtain + * a sufficient size, allocate, then call again. + */ +STLSOFT_INLINE +us_size_t +unixstl_C_get_current_process_path_m( + us_char_a_t* buff +, us_size_t cch +) +{ + STLSOFT_C_AUTO_BUFFER_DECLARE(us_char_a_t, UNIXSTL_C_GET_CURRENT_PROCESS_PATH_INTERNAL_SIZE_, ab); + us_size_t len = 0; + us_size_t result = 0; + + UNIXSTL_ASSERT(0 == cch || NULL != buff); + + STLSOFT_C_AUTO_BUFFER_INITIALISE_FROM_INTERNAL(ab); + +#if 0 +#elif defined(_WIN32) + + /* NOTE: this only for UNIXem-ulation, so we don't care about its + * performance + */ + + /* Windows / UNIXem: GetModuleFileNameA */ + for (;;) + { + DWORD const n = WINSTL_API_EXTERNAL_DynamicLinkLibrary_GetModuleFileNameA( + NULL + , ab.ptr + , STLSOFT_STATIC_CAST(DWORD, ab.size) + ); + + if (0 == n) + { + goto done; + } + + if (n < ab.size) + { + len = STLSOFT_STATIC_CAST(us_size_t, n); + break; + } + + if (0 != STLSOFT_C_AUTO_BUFFER_RESIZE(ab, ab.size * 2)) + { + errno = ENOMEM; + + goto done; + } + } +#elif defined(__APPLE__) + + /* macOS: prefer proc_pidpath; fallback to _NSGetExecutablePath (+ realpath). */ + { + int const ret = STLSOFT_NS_GLOBAL(proc_pidpath)( + STLSOFT_NS_GLOBAL(getpid)() + , ab.ptr + , STLSOFT_STATIC_CAST(uint32_t, ab.size) + ); + + if (ret <= 0) + { + uint32_t size = STLSOFT_STATIC_CAST(uint32_t, ab.size); + + if (0 != STLSOFT_NS_GLOBAL(_NSGetExecutablePath)(ab.ptr, &size)) + { + if (0 != STLSOFT_C_AUTO_BUFFER_RESIZE(ab, size)) + { + errno = ENOMEM; + + goto done; + } + + size = STLSOFT_STATIC_CAST(uint32_t, ab.size); + + if (0 != STLSOFT_NS_GLOBAL(_NSGetExecutablePath)(ab.ptr, &size)) + { + goto done; + } + } + + { + char* const resolved = STLSOFT_NS_GLOBAL(realpath)(ab.ptr, NULL); + + if (NULL != resolved) + { + us_size_t const cchResolved = STLSOFT_NS_GLOBAL(strlen)(resolved); + + if (cchResolved + 1 > ab.size) + { + if (0 != STLSOFT_C_AUTO_BUFFER_RESIZE(ab, cchResolved + 1)) + { + STLSOFT_NS_GLOBAL(free)(resolved); + + errno = ENOMEM; + + goto done; + } + } + + STLSOFT_NS_GLOBAL(memcpy)(ab.ptr, resolved, cchResolved + 1); + STLSOFT_NS_GLOBAL(free)(resolved); + } + } + } + + len = STLSOFT_NS_GLOBAL(strlen)(ab.ptr); + } +#elif defined(__linux__) + + /* Linux: readlink("/proc/self/exe") */ + for (;;) + { + ssize_t const n = STLSOFT_NS_GLOBAL(readlink)("/proc/self/exe", ab.ptr, ab.size); + + if (n < 0) + { + goto done; + } + + if (STLSOFT_STATIC_CAST(us_size_t, n) < ab.size) + { + ab.ptr[n] = '\0'; + len = STLSOFT_STATIC_CAST(us_size_t, n); + break; + } + + if (0 != STLSOFT_C_AUTO_BUFFER_RESIZE(ab, ab.size * 2)) + { + errno = ENOMEM; + + goto done; + } + } +#elif 0 ||\ + defined(__FreeBSD__) ||\ + defined(__NetBSD__) ||\ + defined(__OpenBSD__) ||\ + 0 + + /* BSD: sysctl(KERN_PROC_PATHNAME) */ + { + int mib[4]; + size_t cb = ab.size; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + + if (0 != STLSOFT_NS_GLOBAL(sysctl)(mib, 4, ab.ptr, &cb, NULL, 0)) + { + if (ENOMEM == errno) + { + if (0 != STLSOFT_C_AUTO_BUFFER_RESIZE(ab, cb)) + { + errno = ENOMEM; + + goto done; + } + + cb = ab.size; + + if (0 != STLSOFT_NS_GLOBAL(sysctl)(mib, 4, ab.ptr, &cb, NULL, 0)) + { + goto done; + } + } + else + { + goto done; + } + } + + len = STLSOFT_NS_GLOBAL(strlen)(ab.ptr); + } +#elif defined(__sun) + + /* Solaris: getexecname() + realpath() */ + { + char const* const exe = STLSOFT_NS_GLOBAL(getexecname)(); + + if (NULL == exe) + { + goto done; + } + + if (NULL == STLSOFT_NS_GLOBAL(realpath)(exe, ab.ptr)) + { + goto done; + } + + len = STLSOFT_NS_GLOBAL(strlen)(ab.ptr); + } +#else + + errno = ENOSYS; + + goto done; +#endif + + if (0 == cch) + { + result = len + 1; + } + else if (cch <= len) + { + us_size_t const nCopy = cch - 1; + + if (0 != nCopy) + { + STLSOFT_NS_GLOBAL(memcpy)(buff, ab.ptr, nCopy * sizeof(us_char_a_t)); + } + + buff[nCopy] = '\0'; + result = len + 1; + } + else + { + if (0 != len) + { + STLSOFT_NS_GLOBAL(memcpy)(buff, ab.ptr, len * sizeof(us_char_a_t)); + } + + buff[len] = '\0'; + result = len; + } + +done: + + STLSOFT_C_AUTO_BUFFER_FREE(ab); + + return result; +} + /* ///////////////////////////////////////////////////////////////////////// * namespace diff --git a/include/unixstl/system/system_traits.hpp b/include/unixstl/system/system_traits.hpp index b737497f..e5ac6dc9 100644 --- a/include/unixstl/system/system_traits.hpp +++ b/include/unixstl/system/system_traits.hpp @@ -56,7 +56,7 @@ # define UNIXSTL_VER_UNIXSTL_SYSTEM_HPP_SYSTEM_TRAITS_MAJOR 7 # define UNIXSTL_VER_UNIXSTL_SYSTEM_HPP_SYSTEM_TRAITS_MINOR 0 # define UNIXSTL_VER_UNIXSTL_SYSTEM_HPP_SYSTEM_TRAITS_REVISION 1 -# define UNIXSTL_VER_UNIXSTL_SYSTEM_HPP_SYSTEM_TRAITS_EDIT 144 +# define UNIXSTL_VER_UNIXSTL_SYSTEM_HPP_SYSTEM_TRAITS_EDIT 146 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -71,6 +71,12 @@ # pragma message(__FILE__) #endif /* STLSOFT_TRACE_INCLUDE */ +#ifndef UNIXSTL_INCL_UNIXSTL_FILESYSTEM_H_PATH_PARSE_FUNCTIONS +# include +#endif /* !UNIXSTL_INCL_UNIXSTL_FILESYSTEM_H_PATH_PARSE_FUNCTIONS */ +#ifndef UNIXSTL_INCL_UNIXSTL_PROCESS_H_FUNCTIONS +# include +#endif /* !UNIXSTL_INCL_UNIXSTL_PROCESS_H_FUNCTIONS */ #ifndef UNIXSTL_INCL_UNIXSTL_SYSTEM_H_DIRECTORY_FUNCTIONS # include #endif /* !UNIXSTL_INCL_UNIXSTL_SYSTEM_H_DIRECTORY_FUNCTIONS */ @@ -258,6 +264,74 @@ struct system_traits ); /// @} +/// \name Process Paths +/// @{ +public: + /// Attempt to obtain the current process's path. + /// + /// \param cch The number of elements available in \c buff available for + /// writing; + /// \param buff Pointer to the buffer into which \c cch result can be + /// written. May not be \c nullptr except when \c cch is 0; + /// \param ppFile Optional pointer to receive a mutating pointer. When + /// non-nullptr, *ppFile is always set to + /// nullptr; then, if and only if the full path was + /// written into \c buff, *ppFile is set to the last + /// path-name separator in \c buff, if any; + /// + /// \return If \c buff is sufficient for the full path and a terminating + /// NUL character then this returns the length of the path (excluding + /// the NUL character). Otherwise, as much of the result as can be + /// written is done so and a character count of at least the + /// full length + 1 (for the NUL character) is returned, in order that + /// client code can allocate all required space. The returned demand + /// may exceed that minimum; + /// \retval 0 The function failed, and more information can be obtained + /// from get_last_error(); + /// + /// \note The normative client pattern is to call with \c cch of 0 to + /// obtain a sufficient size, allocate, then call again. + /// + /// \pre 0 == cch || nullptr != buff + static + size_type + get_current_process_path( + size_type cch + , char_type buff[/* cch */] + , char_type** ppFile + ) STLSOFT_NOEXCEPT; + + /// Attempt to obtain the current process's path into a resizeable + /// buffer. + /// + /// \param rb Reference to a resizeable buffer that will be resized as + /// required to hold the path and a terminating NUL character; + /// \param ppFile Optional pointer to receive a mutating pointer into + /// the final contents of \c rb. When non-nullptr, + /// *ppFile is set as for the raw overload, after the + /// final fill; + /// + /// \return The length of the path (excluding the NUL character) on + /// success; 0 on failure; + /// \retval 0 The function failed (query or resize). On query failure + /// \c rb is left unchanged; on resize failure + /// ENOMEM is set and \c rb is left unchanged. In neither + /// failure case is &rb[0] taken on an empty buffer; + /// + /// \note The normative pattern is a size query via the raw overload + /// with \c cch of 0, then resize, then fill — this overload performs + /// that sequence. \c ppFile is computed only after the final fill. + template< + ss_typename_param_k T_resizeableBuffer + > + static + size_type + get_current_process_path( + T_resizeableBuffer& rb + , char_type** ppFile = NULL + ); +/// @} + /// \name System Paths /// @{ public: @@ -588,6 +662,70 @@ struct system_traits return 0; } +public: + static + size_type + get_current_process_path( + size_type cch + , char_type buff[/* cch */] + , char_type** ppFile + ) STLSOFT_NOEXCEPT + { + UNIXSTL_ASSERT(0 == cch || NULL != buff); + + if (NULL != ppFile) + { + *ppFile = NULL; + } + + size_type const n = unixstl_C_get_current_process_path_m(buff, cch); + + if (0 != n && + n < cch) + { + if (NULL != ppFile) + { + *ppFile = const_cast(unixstl_C_find_last_path_name_separator_len_m(buff, n)); + } + } + + return n; + } + + template< + ss_typename_param_k T_resizeableBuffer + > + static + size_type + get_current_process_path( + T_resizeableBuffer& rb + , char_type** ppFile = NULL + ) + { + size_type const cchRequired = get_current_process_path( + 0 + , static_cast(NULL) + , static_cast(NULL) + ); + + if (0 == cchRequired) + { + return 0; + } + + if (rb.size() < cchRequired) + { + if (!STLSOFT_NS_QUAL(resizeable_buffer_resize)(rb, cchRequired)) + { + set_last_error(ENOMEM); + + return 0; + } + } + + return get_current_process_path(rb.size(), &rb[0], ppFile); + } + public: static size_type diff --git a/include/unixstl/unixstl.h b/include/unixstl/unixstl.h index 33c03a2e..2492fe3f 100644 --- a/include/unixstl/unixstl.h +++ b/include/unixstl/unixstl.h @@ -50,8 +50,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define UNIXSTL_VER_UNIXSTL_H_UNIXSTL_MAJOR 3 # define UNIXSTL_VER_UNIXSTL_H_UNIXSTL_MINOR 11 -# define UNIXSTL_VER_UNIXSTL_H_UNIXSTL_REVISION 5 -# define UNIXSTL_VER_UNIXSTL_H_UNIXSTL_EDIT 133 +# define UNIXSTL_VER_UNIXSTL_H_UNIXSTL_REVISION 6 +# define UNIXSTL_VER_UNIXSTL_H_UNIXSTL_EDIT 134 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ /** \file unixstl/unixstl.h diff --git a/include/winstl/api/internal/DynamicLinkLibrary.h b/include/winstl/api/internal/DynamicLinkLibrary.h index a8aa905b..c1e5787e 100644 --- a/include/winstl/api/internal/DynamicLinkLibrary.h +++ b/include/winstl/api/internal/DynamicLinkLibrary.h @@ -5,11 +5,11 @@ * Library Functions API. * * Created: 30th November 2020 - * Updated: 26th May 2025 + * Updated: 15th October 2026 * * Home: http://stlsoft.org/ * - * Copyright (c) 2020-2025, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2020-2026, Matthew Wilson and Synesis Information Systems * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,18 +79,18 @@ * specified module (that must have been loaded by the current process). * * \param hModule A handle to the loaded module for which the path is to be - * obtained; + * obtained; * \param filename A pointer to the buffer into which to write the path. May - * not be NULL unless \c cchFilename is 0; + * not be NULL unless \c cchFilename is 0; * \param cchFilename Number of available bytes in \c filename; * * \return The number of characters written if cchFilename is sufficient to - * hold the entire string; the number of characters required (including for - * terminating NUL character) if not. + * hold the entire string; the number of characters required (including + * for terminating NUL character) if not. * * \note The number of characters required returned is 2 (not 1) more than - * the number of characters in the string, due to the underlying API - * function not providing any direct indication as to (in)sufficient size. + * the number of characters in the string, due to the underlying API + * function not providing any direct indication as to (in)sufficient size. */ STLSOFT_INLINE DWORD @@ -238,18 +238,18 @@ WINSTL_API_INTERNAL_DynamicLinkLibrary_GetModuleFileNameA( * specified module (that must have been loaded by the current process). * * \param hModule A handle to the loaded module for which the path is to be - * obtained; + * obtained; * \param filename A pointer to the buffer into which to write the path. May - * not be NULL unless \c cchFilename is 0; + * not be NULL unless \c cchFilename is 0; * \param cchFilename Number of available characters in \c filename; * * \return The number of characters written if cchFilename is sufficient to - * hold the entire string; the number of characters required (including for - * terminating NUL character) if not. + * hold the entire string; the number of characters required (including + * for terminating NUL character) if not. * * \note The number of characters required returned is 2 (not 1) more than - * the number of characters in the string, due to the underlying API - * function not providing any direct indication as to (in)sufficient size. + * the number of characters in the string, due to the underlying API + * function not providing any direct indication as to (in)sufficient size. */ STLSOFT_INLINE DWORD diff --git a/include/winstl/system/system_traits.hpp b/include/winstl/system/system_traits.hpp index bc65f0af..a2e4ecf2 100644 --- a/include/winstl/system/system_traits.hpp +++ b/include/winstl/system/system_traits.hpp @@ -5,14 +5,14 @@ * Unicode specialisations thereof. * * Created: 15th November 2002 - * Updated: 27th May 2025 + * Updated: 15th October 2026 * * Thanks: Austin Ziegler for spotting the defective pre-condition * enforcement of expand_environment_strings(). * * Home: http://stlsoft.org/ * - * Copyright (c) 2019-2025, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems * Copyright (c) 2002-2019, Matthew Wilson and Synesis Software * All rights reserved. * @@ -57,9 +57,9 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define WINSTL_VER_WINSTL_SYSTEM_HPP_SYSTEM_TRAITS_MAJOR 6 -# define WINSTL_VER_WINSTL_SYSTEM_HPP_SYSTEM_TRAITS_MINOR 0 -# define WINSTL_VER_WINSTL_SYSTEM_HPP_SYSTEM_TRAITS_REVISION 6 -# define WINSTL_VER_WINSTL_SYSTEM_HPP_SYSTEM_TRAITS_EDIT 171 +# define WINSTL_VER_WINSTL_SYSTEM_HPP_SYSTEM_TRAITS_MINOR 1 +# define WINSTL_VER_WINSTL_SYSTEM_HPP_SYSTEM_TRAITS_REVISION 4 +# define WINSTL_VER_WINSTL_SYSTEM_HPP_SYSTEM_TRAITS_EDIT 175 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -104,6 +104,10 @@ # include #endif /* !STLSOFT_INCL_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS */ +#ifndef WINSTL_INCL_WINSTL_FILESYSTEM_H_PATH_PARSE_FUNCTIONS +# include +#endif /*! WINSTL_INCL_WINSTL_FILESYSTEM_H_PATH_PARSE_FUNCTIONS */ + #if STLSOFT_LEAD_VER >= 0x010a0000 # ifndef WINSTL_INCL_WINSTL_SYSTEM_H_DIRECTORY_FUNCTIONS # include @@ -266,6 +270,81 @@ struct system_traits #endif /* !NONLS */ /// @} +/// \name Process Paths +/// @{ +public: + /// Attempt to obtain the current process's path. + /// + /// \param cch The number of elements available in \c buff available for + /// writing; + /// \param buff Pointer to the buffer into which \c cch result can be + /// written. May not be \c nullptr except when \c cch is 0; + /// \param ppFile Optional pointer to receive a mutating pointer. When + /// non-nullptr, *ppFile is always set to + /// nullptr; then, if and only if the full path was + /// written into \c buff, *ppFile is set to the last + /// path-name separator in \c buff, if any; + /// + /// \return If \c buff is sufficient for the full path and a terminating + /// NUL character then this returns the length of the path (excluding + /// the NUL character). Otherwise, as much of the result as can be + /// written is done so and a character count of at least the + /// full length + 1 (for the NUL character) is returned, in order that + /// client code can allocate all required space. The returned demand + /// may exceed that minimum; + /// \retval 0 The function failed, and more information can be obtained + /// from get_last_error(); + /// + /// \note The normative client pattern is to call with \c cch of 0 to + /// obtain a sufficient size, allocate, then call again. + /// + /// \note Due to idiosynchracies of the Windows API, you must use the + /// the function to query the required length, or pass PATH_MAX, and + /// not rely on any a priori knowledge you may have of the presumed + /// process path length. + /// + /// \pre 0 == cch || nullptr != buff + static + size_type + get_current_process_path( + size_type cch + , char_type buff[/* cch */] + , char_type** ppFile + ) STLSOFT_NOEXCEPT; + + /// Attempt to obtain the current process's path into a resizeable + /// buffer. + /// + /// \param rb Reference to a resizeable buffer that will be resized as + /// required to hold the path and a terminating NUL character; + /// \param ppFile Optional pointer to receive a mutating pointer into + /// the final contents of \c rb. When non-nullptr, + /// *ppFile is set as for the raw overload, after the + /// final fill; + /// + /// \return The length of the path (excluding the NUL character) on + /// success; 0 on failure; + /// \retval 0 The function failed (query or resize). On query failure + /// \c rb is left unchanged; on resize failure + /// ERROR_OUTOFMEMORY is set and \c rb is left unchanged. + /// In neither failure case is &rb[0] taken on an empty + /// buffer; + /// + /// \note The normative pattern is a size query via the raw overload + /// with \c cch of 0, then resize, then fill — this overload performs + /// that sequence (required on Windows; see the note on the raw + /// overload). \c ppFile is computed only after the final fill. + template< + ss_typename_param_k T_resizeableBuffer + > + static + size_type + get_current_process_path( + T_resizeableBuffer& rb + , char_type** ppFile = NULL + ); +/// @} + /// \name System Paths /// @{ public: @@ -728,6 +807,70 @@ struct system_traits } #endif /* !NONLS */ +public: + static + size_type + get_current_process_path( + size_type cch + , char_type buff[/* cch */] + , char_type** ppFile + ) STLSOFT_NOEXCEPT + { + WINSTL_ASSERT(0 == cch || NULL != buff); + + if (NULL != ppFile) + { + *ppFile = NULL; + } + + size_type const n = class_type::GetModuleFileNameA(NULL, buff, cch); + + if (0 != n && + n < cch) + { + if (NULL != ppFile) + { + *ppFile = const_cast(winstl_C_find_last_path_name_separator_len_m(buff, n)); + } + } + + return n; + } + + template< + ss_typename_param_k T_resizeableBuffer + > + static + size_type + get_current_process_path( + T_resizeableBuffer& rb + , char_type** ppFile = NULL + ) + { + size_type const cchRequired = get_current_process_path( + 0 + , static_cast(NULL) + , static_cast(NULL) + ); + + if (0 == cchRequired) + { + return 0; + } + + if (rb.size() < cchRequired) + { + if (!STLSOFT_NS_QUAL(resizeable_buffer_resize)(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + + return get_current_process_path(rb.size(), &rb[0], ppFile); + } + public: static size_type @@ -1349,6 +1492,70 @@ struct system_traits } #endif /* !NONLS */ +public: + static + size_type + get_current_process_path( + size_type cch + , char_type buff[/* cch */] + , char_type** ppFile + ) STLSOFT_NOEXCEPT + { + WINSTL_ASSERT(0 == cch || NULL != buff); + + if (NULL != ppFile) + { + *ppFile = NULL; + } + + size_type const n = class_type::GetModuleFileNameW(NULL, buff, cch); + + if (0 != n && + n < cch) + { + if (NULL != ppFile) + { + *ppFile = const_cast(winstl_C_find_last_path_name_separator_len_w(buff, n)); + } + } + + return n; + } + + template< + ss_typename_param_k T_resizeableBuffer + > + static + size_type + get_current_process_path( + T_resizeableBuffer& rb + , char_type** ppFile = NULL + ) + { + size_type const cchRequired = get_current_process_path( + 0 + , static_cast(NULL) + , static_cast(NULL) + ); + + if (0 == cchRequired) + { + return 0; + } + + if (rb.size() < cchRequired) + { + if (!STLSOFT_NS_QUAL(resizeable_buffer_resize)(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + + return get_current_process_path(rb.size(), &rb[0], ppFile); + } + public: static size_type diff --git a/include/winstl/winstl.h b/include/winstl/winstl.h index 71de6726..441af00c 100644 --- a/include/winstl/winstl.h +++ b/include/winstl/winstl.h @@ -50,8 +50,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define WINSTL_VER_WINSTL_H_WINSTL_MAJOR 3 # define WINSTL_VER_WINSTL_H_WINSTL_MINOR 18 -# define WINSTL_VER_WINSTL_H_WINSTL_REVISION 15 -# define WINSTL_VER_WINSTL_H_WINSTL_EDIT 246 +# define WINSTL_VER_WINSTL_H_WINSTL_REVISION 16 +# define WINSTL_VER_WINSTL_H_WINSTL_EDIT 247 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ /** \file winstl/winstl.h diff --git a/test/component/unixstl/system/CMakeLists.txt b/test/component/unixstl/system/CMakeLists.txt index 82b193ab..dfc29b03 100644 --- a/test/component/unixstl/system/CMakeLists.txt +++ b/test/component/unixstl/system/CMakeLists.txt @@ -1,3 +1,4 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten add_subdirectory(environment_variable) +add_subdirectory(process_paths) add_subdirectory(system_traits) diff --git a/test/component/unixstl/system/process_paths/CMakeLists.txt b/test/component/unixstl/system/process_paths/CMakeLists.txt new file mode 100644 index 00000000..cadc56ac --- /dev/null +++ b/test/component/unixstl/system/process_paths/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test.component.unixstl.system.process_paths entry.cpp) diff --git a/test/component/unixstl/system/process_paths/entry.cpp b/test/component/unixstl/system/process_paths/entry.cpp new file mode 100644 index 00000000..f32271f4 --- /dev/null +++ b/test/component/unixstl/system/process_paths/entry.cpp @@ -0,0 +1,562 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/component/unixstl/system/process_paths/entry.cpp + * + * Purpose: Component-tests for + * `unixstl::system_traits::get_current_process_path()` (multibyte), + * including resizeable-buffer overloads. + * + * Created: 15th October 2026 + * Updated: 18th October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// + * test component header file include(s) + */ + +#include + +/* ///////////////////////////////////// + * general includes + */ + +/* xTests header files */ +#include +#include + +/* STLSoft header files */ +#include +#include + +/* Standard C++ header files */ +#include +#include + +/* Standard C header files */ +#include +#include +#include +#include + +#if 0 +#elif defined(_WIN32) + +# include +#elif defined(__APPLE__) + +# include +# include +# include +#elif defined(__linux__) + +# include +#elif 0 ||\ + defined(__FreeBSD__) ||\ + defined(__NetBSD__) ||\ + defined(__OpenBSD__) ||\ + 0 + +# include +# include +#elif defined(__sun) + +# include +#endif + +#if !defined(PATH_MAX) +# define PATH_MAX 4096 +#endif + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +namespace { + + static void TEST_SIZE_QUERY(); + static void TEST_EXACT_FIT(); + static void TEST_EXCESS_SPACE(); + static void TEST_INSUFFICIENT_SPACE(); + static void TEST_ppFile_NULL_ON_SUCCESS(); + static void TEST_ppFile_separator_on_success(); + static void TEST_ppFile_NULL_WHEN_INSUFFICIENT(); + static void TEST_RESIZEABLE_auto_buffer_SUCCESS(); + static void TEST_RESIZEABLE_vector_SUCCESS(); + static void TEST_RESIZEABLE_ppFile_NULL(); + static void TEST_RESIZEABLE_ppFile_separator(); + static void TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched(); +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main() + */ + +int main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.component.unixstl.system.process_paths", verbosity)) + { + XTESTS_RUN_CASE(TEST_SIZE_QUERY); + XTESTS_RUN_CASE(TEST_EXACT_FIT); + XTESTS_RUN_CASE(TEST_EXCESS_SPACE); + XTESTS_RUN_CASE(TEST_INSUFFICIENT_SPACE); + XTESTS_RUN_CASE(TEST_ppFile_NULL_ON_SUCCESS); + XTESTS_RUN_CASE(TEST_ppFile_separator_on_success); + XTESTS_RUN_CASE(TEST_ppFile_NULL_WHEN_INSUFFICIENT); + XTESTS_RUN_CASE(TEST_RESIZEABLE_auto_buffer_SUCCESS); + XTESTS_RUN_CASE(TEST_RESIZEABLE_vector_SUCCESS); + XTESTS_RUN_CASE(TEST_RESIZEABLE_ppFile_NULL); + XTESTS_RUN_CASE(TEST_RESIZEABLE_ppFile_separator); + XTESTS_RUN_CASE(TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +namespace { + +typedef unixstl::filesystem_traits fs_traits_m_t; +typedef unixstl::system_traits traits_m_t; + +/** Retrieves the full path of the current executable from the ambient OS + * API (not via STLSoft). + * + * \return 0 on success; -1 on failure. + */ +static +int +get_executable_path_( + char* path +, size_t size +) +{ + if (NULL == path || + 0 == size) + { + return -1; + } + +#if 0 +#elif defined(_WIN32) + + /* Windows / UNIXem: GetModuleFileNameA */ + { + DWORD const len = ::GetModuleFileNameA(NULL, path, static_cast(size - 1)); + + if (0 == len) + { + return -1; + } + + if (len == static_cast(size - 1) && + ERROR_INSUFFICIENT_BUFFER == ::GetLastError()) + { + return -1; + } + + path[len] = '\0'; + + return 0; + } +#elif defined(__APPLE__) + + /* macOS: prefer proc_pidpath; fallback to _NSGetExecutablePath (+ realpath). */ + { + pid_t const pid = ::getpid(); + int const ret = ::proc_pidpath(pid, path, static_cast(size)); + + if (ret <= 0) + { + uint32_t len = static_cast(size); + + if (0 != ::_NSGetExecutablePath(path, &len)) + { + return -1; + } + + { + char* const resolved = ::realpath(path, NULL); + + if (NULL != resolved) + { + ::strncpy(path, resolved, size - 1); + path[size - 1] = '\0'; + ::free(resolved); + } + } + } + + return 0; + } +#elif defined(__linux__) + + /* Linux: readlink("/proc/self/exe") */ + { + ssize_t const len = ::readlink("/proc/self/exe", path, size - 1); + + if (-1 == len) + { + return -1; + } + + path[len] = '\0'; + + return 0; + } +#elif 0 || \ + defined(__FreeBSD__) ||\ + defined(__NetBSD__) ||\ + defined(__OpenBSD__) ||\ + 0 + + /* BSD: sysctl(KERN_PROC_PATHNAME) */ + { + int mib[4]; + size_t len = size - 1; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + + if (0 != ::sysctl(mib, 4, path, &len, NULL, 0)) + { + return -1; + } + + path[len] = '\0'; + + return 0; + } +#elif defined(__sun) + + /* Solaris: getexecname() + realpath() */ + { + char const* const exe = ::getexecname(); + + if (NULL == exe) + { + return -1; + } + + if (NULL == ::realpath(exe, path)) + { + return -1; + } + + return 0; + } +#else + + STLSOFT_SUPPRESS_UNUSED(path); + STLSOFT_SUPPRESS_UNUSED(size); + + return -1; +#endif +} + +/** Obtains the current process image path from the ambient OS API, without + * going through STLSoft. + */ +static +std::string +obtain_raw_current_process_path() +{ + char path[PATH_MAX]; + + if (0 != get_executable_path_(path, sizeof(path))) + { + return std::string(); + } + + return std::string(path); +} + +static +char* +invalid_file_pointer() +{ + return reinterpret_cast(static_cast(-1)); +} + +/** Minimal resizeable buffer whose #resize() always fails. */ +struct fail_on_resize_buffer +{ + typedef char value_type; + + std::vector storage; + + size_t size() const + { + return storage.size(); + } + + char& operator [](size_t i) + { + return storage[i]; + } + + char const& operator [](size_t i) const + { + return storage[i]; + } + + bool resize(size_t /* newSize */) + { + return false; + } +}; +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +namespace { + +static void TEST_SIZE_QUERY() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(0, NULL, &pFile); + + TEST_INT_EQ(expected.size() + 1, cch); + TEST_PTR_EQ(NULL, pFile); +} + +static void TEST_EXACT_FIT() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + stlsoft::auto_buffer buff(len + 1); + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(len, cch); + TEST_MS_EQ(expected.c_str(), &buff[0]); + TEST_CHAR_EQ('\0', buff[len]); +} + +static void TEST_EXCESS_SPACE() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + stlsoft::auto_buffer buff(len + 64); + + ::memset(&buff[0], '~', buff.size()); + + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(len, cch); + TEST_MS_EQ(expected.c_str(), &buff[0]); + TEST_CHAR_EQ('\0', buff[len]); +} + +static void TEST_INSUFFICIENT_SPACE() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + + /* One character short of an exact fit. */ + { + size_t const cchBuff = len; + stlsoft::auto_buffer buff(cchBuff); + + ::memset(&buff[0], '~', buff.size()); + + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); + TEST_CHAR_EQ('\0', buff[cchBuff - 1]); + TEST_MS_EQ_N(expected.c_str(), &buff[0], cchBuff - 1); + } + + /* Single-character buffer: only the terminating NUL. */ + { + char buff[1] = { '~' }; + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(STLSOFT_NUM_ELEMENTS(buff), buff, &pFile); + + TEST_INT_EQ(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); + TEST_CHAR_EQ('\0', buff[0]); + } +} + +static void TEST_ppFile_NULL_ON_SUCCESS() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer buff(expected.size() + 1); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], NULL); + + TEST_INT_EQ(expected.size(), cch); + TEST_MS_EQ(expected.c_str(), &buff[0]); +} + +static void TEST_ppFile_separator_on_success() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer buff(expected.size() + 1); + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(expected.size(), cch); + TEST_MS_EQ(expected.c_str(), &buff[0]); + + char const* const expectedSep = fs_traits_m_t::find_last_path_name_separator(&buff[0]); + + TEST_PTR_EQ(expectedSep, pFile); + + if (NULL != pFile) + { + TEST_MS_EQ(expectedSep + 1, pFile + 1); + } +} + +static void TEST_ppFile_NULL_WHEN_INSUFFICIENT() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + stlsoft::auto_buffer buff(len); /* one short */ + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); +} + +static void TEST_RESIZEABLE_auto_buffer_SUCCESS() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + size_t const cch = traits_m_t::get_current_process_path(rb); + + TEST_INT_EQ(expected.size(), cch); + TEST_INT_GE(expected.size() + 1, rb.size()); + TEST_MS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_vector_SUCCESS() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + std::vector rb; + size_t const cch = traits_m_t::get_current_process_path(rb); + + TEST_INT_EQ(expected.size(), cch); + TEST_INT_GE(expected.size() + 1, rb.size()); + TEST_MS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_ppFile_NULL() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + size_t const cch = traits_m_t::get_current_process_path(rb, NULL); + + TEST_INT_EQ(expected.size(), cch); + TEST_MS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_ppFile_separator() +{ + std::string const expected = obtain_raw_current_process_path(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(rb, &pFile); + + TEST_INT_EQ(expected.size(), cch); + TEST_MS_EQ(expected.c_str(), &rb[0]); + + char const* const expectedSep = fs_traits_m_t::find_last_path_name_separator(&rb[0]); + + TEST_PTR_EQ(expectedSep, pFile); + + if (NULL != pFile) + { + TEST_MS_EQ(expectedSep + 1, pFile + 1); + } +} + +static void TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched() +{ + fail_on_resize_buffer rb; + + rb.storage.assign(4, 'X'); + + size_t const priorSize = rb.size(); + int const priorErrno = errno; + + errno = 0; + + size_t const cch = traits_m_t::get_current_process_path(rb); + + TEST_INT_EQ(0, cch); + TEST_INT_EQ(ENOMEM, errno); + TEST_INT_EQ(priorSize, rb.size()); + TEST_CHAR_EQ('X', rb.storage[0]); + TEST_CHAR_EQ('X', rb.storage[3]); + + errno = priorErrno; +} +} // anonymous namespace + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/component/winstl/system/CMakeLists.txt b/test/component/winstl/system/CMakeLists.txt index f200ec6e..331d1e0f 100644 --- a/test/component/winstl/system/CMakeLists.txt +++ b/test/component/winstl/system/CMakeLists.txt @@ -1,2 +1,3 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten add_subdirectory(directory_functions) +add_subdirectory(process_paths) diff --git a/test/component/winstl/system/process_paths/CMakeLists.txt b/test/component/winstl/system/process_paths/CMakeLists.txt new file mode 100644 index 00000000..056b6aa3 --- /dev/null +++ b/test/component/winstl/system/process_paths/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test.component.winstl.system.process_paths entry.cpp) diff --git a/test/component/winstl/system/process_paths/entry.cpp b/test/component/winstl/system/process_paths/entry.cpp new file mode 100644 index 00000000..c4360129 --- /dev/null +++ b/test/component/winstl/system/process_paths/entry.cpp @@ -0,0 +1,710 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/component/winstl/system/process_paths/entry.cpp + * + * Purpose: Component-tests for + * `winstl::system_traits::get_current_process_path()` (multibyte + * and wide-character), including resizeable-buffer overloads. + * + * Created: 15th October 2026 + * Updated: 18th October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// + * test component header file include(s) + */ + +#include + +/* ///////////////////////////////////// + * general includes + */ + +/* xTests header files */ +#include +#include + +/* STLSoft header files */ +#include +#include + +/* Standard C++ header files */ +#include +#include + +/* Standard C / Windows header files */ +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +namespace { + + static void TEST_size_QUERY_m(); + static void TEST_size_QUERY_w(); + static void TEST_size_THEN_SUFFICIENT_m(); + static void TEST_size_THEN_SUFFICIENT_w(); + static void TEST_EXCESS_SPACE_m(); + static void TEST_EXCESS_SPACE_w(); + static void TEST_INSUFFICIENT_SPACE_m(); + static void TEST_INSUFFICIENT_SPACE_w(); + static void TEST_ppFile_NULL_ON_SUCCESS_m(); + static void TEST_ppFile_NULL_ON_SUCCESS_w(); + static void TEST_ppFile_separator_ON_SUCCESS_m(); + static void TEST_ppFile_separator_ON_SUCCESS_w(); + static void TEST_ppFile_NULL_WHEN_INSUFFICIENT_m(); + static void TEST_ppFile_NULL_WHEN_INSUFFICIENT_w(); + static void TEST_RESIZEABLE_auto_buffer_SUCCESS_m(); + static void TEST_RESIZEABLE_auto_buffer_SUCCESS_w(); + static void TEST_RESIZEABLE_vector_SUCCESS_m(); + static void TEST_RESIZEABLE_vector_SUCCESS_w(); + static void TEST_RESIZEABLE_ppFile_NULL_m(); + static void TEST_RESIZEABLE_ppFile_NULL_w(); + static void TEST_RESIZEABLE_ppFile_separator_m(); + static void TEST_RESIZEABLE_ppFile_separator_w(); + static void TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched_m(); + static void TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched_w(); +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main() + */ + +int main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.component.winstl.system.process_paths", verbosity)) + { + XTESTS_RUN_CASE(TEST_size_QUERY_m); + XTESTS_RUN_CASE(TEST_size_QUERY_w); + XTESTS_RUN_CASE(TEST_size_THEN_SUFFICIENT_m); + XTESTS_RUN_CASE(TEST_size_THEN_SUFFICIENT_w); + XTESTS_RUN_CASE(TEST_EXCESS_SPACE_m); + XTESTS_RUN_CASE(TEST_EXCESS_SPACE_w); + XTESTS_RUN_CASE(TEST_INSUFFICIENT_SPACE_m); + XTESTS_RUN_CASE(TEST_INSUFFICIENT_SPACE_w); + XTESTS_RUN_CASE(TEST_ppFile_NULL_ON_SUCCESS_m); + XTESTS_RUN_CASE(TEST_ppFile_NULL_ON_SUCCESS_w); + XTESTS_RUN_CASE(TEST_ppFile_separator_ON_SUCCESS_m); + XTESTS_RUN_CASE(TEST_ppFile_separator_ON_SUCCESS_w); + XTESTS_RUN_CASE(TEST_ppFile_NULL_WHEN_INSUFFICIENT_m); + XTESTS_RUN_CASE(TEST_ppFile_NULL_WHEN_INSUFFICIENT_w); + XTESTS_RUN_CASE(TEST_RESIZEABLE_auto_buffer_SUCCESS_m); + XTESTS_RUN_CASE(TEST_RESIZEABLE_auto_buffer_SUCCESS_w); + XTESTS_RUN_CASE(TEST_RESIZEABLE_vector_SUCCESS_m); + XTESTS_RUN_CASE(TEST_RESIZEABLE_vector_SUCCESS_w); + XTESTS_RUN_CASE(TEST_RESIZEABLE_ppFile_NULL_m); + XTESTS_RUN_CASE(TEST_RESIZEABLE_ppFile_NULL_w); + XTESTS_RUN_CASE(TEST_RESIZEABLE_ppFile_separator_m); + XTESTS_RUN_CASE(TEST_RESIZEABLE_ppFile_separator_w); + XTESTS_RUN_CASE(TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched_m); + XTESTS_RUN_CASE(TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched_w); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +namespace { + +typedef winstl::filesystem_traits fs_traits_m_t; +typedef winstl::filesystem_traits fs_traits_w_t; +typedef winstl::system_traits traits_m_t; +typedef winstl::system_traits traits_w_t; + + +/** Obtains the current process image path via GetModuleFileNameA. */ +static +std::string +obtain_raw_current_process_path_m() +{ + stlsoft::auto_buffer buff(MAX_PATH); + + for (;;) + { + DWORD const n = ::GetModuleFileNameA(NULL, &buff[0], static_cast(buff.size())); + + if (0 == n) + { + return std::string(); + } + + if (n < buff.size()) + { + return std::string(&buff[0], static_cast(n)); + } + + if (!buff.resize(buff.size() * 2)) + { + return std::string(); + } + } +} + +/** Obtains the current process image path via GetModuleFileNameW. */ +static +std::wstring +obtain_raw_current_process_path_w() +{ + stlsoft::auto_buffer buff(MAX_PATH); + + for (;;) + { + DWORD const n = ::GetModuleFileNameW(NULL, &buff[0], static_cast(buff.size())); + + if (0 == n) + { + return std::wstring(); + } + + if (n < buff.size()) + { + return std::wstring(&buff[0], static_cast(n)); + } + + if (!buff.resize(buff.size() * 2)) + { + return std::wstring(); + } + } +} + +template +static +C* +invalid_file_pointer() +{ + return reinterpret_cast(static_cast(-1)); +} + +/** Minimal resizeable buffer whose #resize() always fails. */ +template +struct fail_on_resize_buffer +{ + typedef C value_type; + + std::vector storage; + + size_t size() const + { + return storage.size(); + } + + C& operator [](size_t i) + { + return storage[i]; + } + + C const& operator [](size_t i) const + { + return storage[i]; + } + + bool resize(size_t /* newSize */) + { + return false; + } +}; +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +namespace { + +static void TEST_size_QUERY_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(0, NULL, &pFile); + + /* Demand may exceed len+1 (WinSTL GetModuleFileName size protocol). */ + TEST_INT_GE(expected.size() + 1, cch); + TEST_PTR_EQ(NULL, pFile); +} + +static void TEST_size_QUERY_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + wchar_t* pFile = invalid_file_pointer(); + size_t const cch = traits_w_t::get_current_process_path(0, NULL, &pFile); + + TEST_INT_GE(expected.size() + 1, cch); + TEST_PTR_EQ(NULL, pFile); +} + +static void TEST_size_THEN_SUFFICIENT_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + size_t const need = traits_m_t::get_current_process_path(0, NULL, NULL); + + XTESTS_REQUIRE(TEST_INT_GE(len + 1, need)); + + stlsoft::auto_buffer buff(need); + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(len, cch); + TEST_MS_EQ(expected.c_str(), &buff[0]); + TEST_CHAR_EQ('\0', buff[len]); +} + +static void TEST_size_THEN_SUFFICIENT_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + size_t const need = traits_w_t::get_current_process_path(0, NULL, NULL); + + XTESTS_REQUIRE(TEST_INT_GE(len + 1, need)); + + stlsoft::auto_buffer buff(need); + wchar_t* pFile = invalid_file_pointer(); + size_t const cch = traits_w_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(len, cch); + TEST_WS_EQ(expected.c_str(), &buff[0]); + TEST_CHAR_EQ(L'\0', buff[len]); +} + +static void TEST_EXCESS_SPACE_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + stlsoft::auto_buffer buff(len + 64); + + ::memset(&buff[0], '~', buff.size()); + + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(len, cch); + TEST_MS_EQ(expected.c_str(), &buff[0]); + TEST_CHAR_EQ('\0', buff[len]); +} + +static void TEST_EXCESS_SPACE_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + stlsoft::auto_buffer buff(len + 64); + + for (size_t i = 0; i != buff.size(); ++i) + { + buff[i] = L'~'; + } + + wchar_t* pFile = invalid_file_pointer(); + size_t const cch = traits_w_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(len, cch); + TEST_WS_EQ(expected.c_str(), &buff[0]); + TEST_CHAR_EQ(L'\0', buff[len]); +} + +static void TEST_INSUFFICIENT_SPACE_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + + { + size_t const cchBuff = len; + stlsoft::auto_buffer buff(cchBuff); + + ::memset(&buff[0], '~', buff.size()); + + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_GE(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); + TEST_CHAR_EQ('\0', buff[cchBuff - 1]); + TEST_MS_EQ_N(expected.c_str(), &buff[0], cchBuff - 1); + } + + { + char buff[1] = { '~' }; + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(STLSOFT_NUM_ELEMENTS(buff), buff, &pFile); + + TEST_INT_GE(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); + TEST_CHAR_EQ('\0', buff[0]); + } +} + +static void TEST_INSUFFICIENT_SPACE_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + + { + size_t const cchBuff = len; + stlsoft::auto_buffer buff(cchBuff); + + for (size_t i = 0; i != buff.size(); ++i) + { + buff[i] = L'~'; + } + + wchar_t* pFile = invalid_file_pointer(); + size_t const cch = traits_w_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_GE(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); + TEST_CHAR_EQ(L'\0', buff[cchBuff - 1]); + TEST_WS_EQ_N(expected.c_str(), &buff[0], cchBuff - 1); + } + + { + wchar_t buff[1] = { L'~' }; + wchar_t* pFile = invalid_file_pointer(); + size_t const cch = traits_w_t::get_current_process_path(STLSOFT_NUM_ELEMENTS(buff), buff, &pFile); + + TEST_INT_GE(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); + TEST_CHAR_EQ(L'\0', buff[0]); + } +} + +static void TEST_ppFile_NULL_ON_SUCCESS_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const need = traits_m_t::get_current_process_path(0, NULL, NULL); + + XTESTS_REQUIRE(TEST_INT_GE(expected.size() + 1, need)); + + stlsoft::auto_buffer buff(need); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], NULL); + + TEST_INT_EQ(expected.size(), cch); + TEST_MS_EQ(expected.c_str(), &buff[0]); +} + +static void TEST_ppFile_NULL_ON_SUCCESS_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const need = traits_w_t::get_current_process_path(0, NULL, NULL); + + XTESTS_REQUIRE(TEST_INT_GE(expected.size() + 1, need)); + + stlsoft::auto_buffer buff(need); + size_t const cch = traits_w_t::get_current_process_path(buff.size(), &buff[0], NULL); + + TEST_INT_EQ(expected.size(), cch); + TEST_WS_EQ(expected.c_str(), &buff[0]); +} + +static void TEST_ppFile_separator_ON_SUCCESS_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const need = traits_m_t::get_current_process_path(0, NULL, NULL); + + XTESTS_REQUIRE(TEST_INT_GE(expected.size() + 1, need)); + + stlsoft::auto_buffer buff(need); + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(expected.size(), cch); + TEST_MS_EQ(expected.c_str(), &buff[0]); + + char const* const expectedSep = fs_traits_m_t::find_last_path_name_separator(&buff[0]); + + TEST_PTR_EQ(expectedSep, pFile); + + if (NULL != pFile) + { + TEST_MS_EQ(expectedSep + 1, pFile + 1); + } +} + +static void TEST_ppFile_separator_ON_SUCCESS_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const need = traits_w_t::get_current_process_path(0, NULL, NULL); + + XTESTS_REQUIRE(TEST_INT_GE(expected.size() + 1, need)); + + stlsoft::auto_buffer buff(need); + wchar_t* pFile = invalid_file_pointer(); + size_t const cch = traits_w_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_EQ(expected.size(), cch); + TEST_WS_EQ(expected.c_str(), &buff[0]); + + wchar_t const* const expectedSep = fs_traits_w_t::find_last_path_name_separator(&buff[0]); + + TEST_PTR_EQ(expectedSep, pFile); + + if (NULL != pFile) + { + TEST_WS_EQ(expectedSep + 1, pFile + 1); + } +} + +static void TEST_ppFile_NULL_WHEN_INSUFFICIENT_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + stlsoft::auto_buffer buff(len); + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_GE(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); +} + +static void TEST_ppFile_NULL_WHEN_INSUFFICIENT_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + size_t const len = expected.size(); + stlsoft::auto_buffer buff(len); + wchar_t* pFile = invalid_file_pointer(); + size_t const cch = traits_w_t::get_current_process_path(buff.size(), &buff[0], &pFile); + + TEST_INT_GE(len + 1, cch); + TEST_PTR_EQ(NULL, pFile); +} + +static void TEST_RESIZEABLE_auto_buffer_SUCCESS_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + size_t const cch = traits_m_t::get_current_process_path(rb); + + TEST_INT_EQ(expected.size(), cch); + TEST_INT_GE(expected.size() + 1, rb.size()); + TEST_MS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_auto_buffer_SUCCESS_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + size_t const cch = traits_w_t::get_current_process_path(rb); + + TEST_INT_EQ(expected.size(), cch); + TEST_INT_GE(expected.size() + 1, rb.size()); + TEST_WS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_vector_SUCCESS_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + std::vector rb; + size_t const cch = traits_m_t::get_current_process_path(rb); + + TEST_INT_EQ(expected.size(), cch); + TEST_INT_GE(expected.size() + 1, rb.size()); + TEST_MS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_vector_SUCCESS_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + std::vector rb; + size_t const cch = traits_w_t::get_current_process_path(rb); + + TEST_INT_EQ(expected.size(), cch); + TEST_INT_GE(expected.size() + 1, rb.size()); + TEST_WS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_ppFile_NULL_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + size_t const cch = traits_m_t::get_current_process_path(rb, NULL); + + TEST_INT_EQ(expected.size(), cch); + TEST_MS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_ppFile_NULL_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + size_t const cch = traits_w_t::get_current_process_path(rb, NULL); + + TEST_INT_EQ(expected.size(), cch); + TEST_WS_EQ(expected.c_str(), &rb[0]); +} + +static void TEST_RESIZEABLE_ppFile_separator_m() +{ + std::string const expected = obtain_raw_current_process_path_m(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + char* pFile = invalid_file_pointer(); + size_t const cch = traits_m_t::get_current_process_path(rb, &pFile); + + TEST_INT_EQ(expected.size(), cch); + TEST_MS_EQ(expected.c_str(), &rb[0]); + + char const* const expectedSep = fs_traits_m_t::find_last_path_name_separator(&rb[0]); + + TEST_PTR_EQ(expectedSep, pFile); + + if (NULL != pFile) + { + TEST_MS_EQ(expectedSep + 1, pFile + 1); + } +} + +static void TEST_RESIZEABLE_ppFile_separator_w() +{ + std::wstring const expected = obtain_raw_current_process_path_w(); + + XTESTS_REQUIRE(TEST_BOOLEAN_TRUE(!expected.empty())); + + stlsoft::auto_buffer rb(0); + wchar_t* pFile = invalid_file_pointer(); + size_t const cch = traits_w_t::get_current_process_path(rb, &pFile); + + TEST_INT_EQ(expected.size(), cch); + TEST_WS_EQ(expected.c_str(), &rb[0]); + + wchar_t const* const expectedSep = fs_traits_w_t::find_last_path_name_separator(&rb[0]); + + TEST_PTR_EQ(expectedSep, pFile); + + if (NULL != pFile) + { + TEST_WS_EQ(expectedSep + 1, pFile + 1); + } +} + +static void TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched_m() +{ + fail_on_resize_buffer rb; + + rb.storage.assign(4, 'X'); + + size_t const priorSize = rb.size(); + DWORD const priorError = ::GetLastError(); + + ::SetLastError(ERROR_SUCCESS); + + size_t const cch = traits_m_t::get_current_process_path(rb); + + TEST_INT_EQ(0, cch); + TEST_INT_EQ(ERROR_OUTOFMEMORY, ::GetLastError()); + TEST_INT_EQ(priorSize, rb.size()); + TEST_CHAR_EQ('X', rb.storage[0]); + TEST_CHAR_EQ('X', rb.storage[3]); + + ::SetLastError(priorError); +} + +static void TEST_RESIZEABLE_resize_FAILURE_leaves_rb_untouched_w() +{ + fail_on_resize_buffer rb; + + rb.storage.assign(4, L'X'); + + size_t const priorSize = rb.size(); + DWORD const priorError = ::GetLastError(); + + ::SetLastError(ERROR_SUCCESS); + + size_t const cch = traits_w_t::get_current_process_path(rb); + + TEST_INT_EQ(0, cch); + TEST_INT_EQ(ERROR_OUTOFMEMORY, ::GetLastError()); + TEST_INT_EQ(priorSize, rb.size()); + TEST_BOOLEAN_TRUE(L'X' == rb.storage[0]); + TEST_BOOLEAN_TRUE(L'X' == rb.storage[3]); + + ::SetLastError(priorError); +} +} // anonymous namespace + + +/* ///////////////////////////// end of file //////////////////////////// */ +