90.32% Lines (28/31) 100.00% Functions (4/4)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_TEST_TEMP_PATH_HPP 10   #ifndef BOOST_COROSIO_TEST_TEMP_PATH_HPP
11   #define BOOST_COROSIO_TEST_TEMP_PATH_HPP 11   #define BOOST_COROSIO_TEST_TEMP_PATH_HPP
12   12  
13   #include <atomic> 13   #include <atomic>
14   #include <cstdint> 14   #include <cstdint>
15   #include <cstdio> 15   #include <cstdio>
16   #include <filesystem> 16   #include <filesystem>
17   #include <random> 17   #include <random>
18   #include <stdexcept> 18   #include <stdexcept>
19   #include <string> 19   #include <string>
20   #include <system_error> 20   #include <system_error>
21   21  
22   namespace boost::corosio::test { 22   namespace boost::corosio::test {
23   23  
24   /** RAII temp directory holding a path for a Unix-domain socket. 24   /** RAII temp directory holding a path for a Unix-domain socket.
25   25  
26   Creates a unique empty directory under 26   Creates a unique empty directory under
27   `std::filesystem::temp_directory_path()` and exposes a path under 27   `std::filesystem::temp_directory_path()` and exposes a path under
28   it suitable for binding `local_stream_socket` / 28   it suitable for binding `local_stream_socket` /
29   `local_datagram_socket`. The destructor removes the directory 29   `local_datagram_socket`. The destructor removes the directory
30   (and the bound socket file inside it) recursively, so tests that 30   (and the bound socket file inside it) recursively, so tests that
31   throw mid-execution still clean up. 31   throw mid-execution still clean up.
32   32  
33   Naming entropy comes from a process-wide atomic counter mixed with 33   Naming entropy comes from a process-wide atomic counter mixed with
34   a one-time `random_device` seed; that's enough to avoid collisions 34   a one-time `random_device` seed; that's enough to avoid collisions
35   between parallel test runs without requiring cryptographic 35   between parallel test runs without requiring cryptographic
36   randomness. The constructor retries on collision and throws if it 36   randomness. The constructor retries on collision and throws if it
37   cannot create a directory in a reasonable number of attempts. 37   cannot create a directory in a reasonable number of attempts.
38   38  
39   Platform note: the helper exists so tests don't need to call 39   Platform note: the helper exists so tests don't need to call
40   `mkdtemp` / `unlink` / `rmdir` directly. On Windows, the path is 40   `mkdtemp` / `unlink` / `rmdir` directly. On Windows, the path is
41   a filesystem AF_UNIX path (Windows 10 1803+). 41   a filesystem AF_UNIX path (Windows 10 1803+).
42   */ 42   */
43   class temp_socket_dir 43   class temp_socket_dir
44   { 44   {
45   public: 45   public:
HITCBC 46   111 temp_socket_dir() 46   121 temp_socket_dir()
HITCBC 47   111 { 47   121 {
48   namespace fs = std::filesystem; 48   namespace fs = std::filesystem;
HITCBC 49   111 auto const base = fs::temp_directory_path(); 49   121 auto const base = fs::temp_directory_path();
50   50  
51   // 64 bits of mixed entropy: a random seed established once 51   // 64 bits of mixed entropy: a random seed established once
52   // at static init, XORed with a monotonic counter. 52   // at static init, XORed with a monotonic counter.
HITCBC 53   8 static std::uint64_t const seed = [] { 53   9 static std::uint64_t const seed = [] {
HITCBC 54   8 std::random_device rd; 54   9 std::random_device rd;
HITCBC 55   8 return (static_cast<std::uint64_t>(rd()) << 32) | 55   9 return (static_cast<std::uint64_t>(rd()) << 32) |
HITCBC 56   16 static_cast<std::uint64_t>(rd()); 56   18 static_cast<std::uint64_t>(rd());
HITCBC 57   119 }(); 57   130 }();
58   static std::atomic<std::uint64_t> counter{0}; 58   static std::atomic<std::uint64_t> counter{0};
59   59  
HITCBC 60   111 std::error_code ec; 60   121 std::error_code ec;
HITCBC 61   111 for (int tries = 0; tries < 32; ++tries) 61   121 for (int tries = 0; tries < 32; ++tries)
62   { 62   {
HITCBC 63   111 auto const n = counter.fetch_add(1, std::memory_order_relaxed); 63   121 auto const n = counter.fetch_add(1, std::memory_order_relaxed);
HITCBC 64   111 auto const tag = seed ^ n; 64   121 auto const tag = seed ^ n;
65   65  
66   char buf[32]; 66   char buf[32];
HITCBC 67   111 std::snprintf( 67   121 std::snprintf(
68   buf, sizeof(buf), "corosio_test_%016llx", 68   buf, sizeof(buf), "corosio_test_%016llx",
69   static_cast<unsigned long long>(tag)); 69   static_cast<unsigned long long>(tag));
70   70  
HITCBC 71   111 auto candidate = base / buf; 71   121 auto candidate = base / buf;
HITCBC 72   111 if (fs::create_directory(candidate, ec)) 72   121 if (fs::create_directory(candidate, ec))
73   { 73   {
HITCBC 74   111 dir_ = std::move(candidate); 74   121 dir_ = std::move(candidate);
HITCBC 75   222 return; 75   242 return;
76   } 76   }
HITCBC 77   111 } 77   121 }
78   throw std::runtime_error( 78   throw std::runtime_error(
MISUBC 79   "temp_socket_dir: could not create temp directory: " + 79   "temp_socket_dir: could not create temp directory: " +
MISUBC 80   ec.message()); 80   ec.message());
HITCBC 81   111 } 81   121 }
82   82  
HITCBC 83   111 ~temp_socket_dir() noexcept 83   121 ~temp_socket_dir() noexcept
HITCBC 84   111 { 84   121 {
HITCBC 85   111 if (dir_.empty()) 85   121 if (dir_.empty())
MISUBC 86   return; 86   return;
87   87  
HITCBC 88   111 std::error_code ec; 88   121 std::error_code ec;
89   89  
90   // Unlink the socket file with C's std::remove from <cstdio> 90   // Unlink the socket file with C's std::remove from <cstdio>
91   // (deletes by name, no open) before remove_all, whose 91   // (deletes by name, no open) before remove_all, whose
92   // symlink_status opens AF_UNIX socket files via CreateFileW and 92   // symlink_status opens AF_UNIX socket files via CreateFileW and
93   // hangs on Windows. 93   // hangs on Windows.
HITCBC 94   111 std::remove(path().c_str()); 94   121 std::remove(path().c_str());
HITCBC 95   111 std::filesystem::remove_all(dir_, ec); 95   121 std::filesystem::remove_all(dir_, ec);
HITCBC 96   111 } 96   121 }
97   97  
98   temp_socket_dir(temp_socket_dir const&) = delete; 98   temp_socket_dir(temp_socket_dir const&) = delete;
99   temp_socket_dir& operator=(temp_socket_dir const&) = delete; 99   temp_socket_dir& operator=(temp_socket_dir const&) = delete;
100   100  
101   /// Path suitable for binding a local socket. 101   /// Path suitable for binding a local socket.
HITCBC 102   238 std::string path() const 102   260 std::string path() const
103   { 103   {
HITCBC 104   238 return (dir_ / "sock").string(); 104   260 return (dir_ / "sock").string();
105   } 105   }
106   106  
107   private: 107   private:
108   std::filesystem::path dir_; 108   std::filesystem::path dir_;
109   }; 109   };
110   110  
111   } // namespace boost::corosio::test 111   } // namespace boost::corosio::test
112   112  
113   #endif // BOOST_COROSIO_TEST_TEMP_PATH_HPP 113   #endif // BOOST_COROSIO_TEST_TEMP_PATH_HPP