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   109 temp_socket_dir() 46   111 temp_socket_dir()
HITCBC 47   109 { 47   111 {
48   namespace fs = std::filesystem; 48   namespace fs = std::filesystem;
HITCBC 49   109 auto const base = fs::temp_directory_path(); 49   111 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   8 static std::uint64_t const seed = [] {
HITCBC 54   8 std::random_device rd; 54   8 std::random_device rd;
HITCBC 55   8 return (static_cast<std::uint64_t>(rd()) << 32) | 55   8 return (static_cast<std::uint64_t>(rd()) << 32) |
HITCBC 56   16 static_cast<std::uint64_t>(rd()); 56   16 static_cast<std::uint64_t>(rd());
HITCBC 57   117 }(); 57   119 }();
58   static std::atomic<std::uint64_t> counter{0}; 58   static std::atomic<std::uint64_t> counter{0};
59   59  
HITCBC 60   109 std::error_code ec; 60   111 std::error_code ec;
HITCBC 61   109 for (int tries = 0; tries < 32; ++tries) 61   111 for (int tries = 0; tries < 32; ++tries)
62   { 62   {
HITCBC 63   109 auto const n = counter.fetch_add(1, std::memory_order_relaxed); 63   111 auto const n = counter.fetch_add(1, std::memory_order_relaxed);
HITCBC 64   109 auto const tag = seed ^ n; 64   111 auto const tag = seed ^ n;
65   65  
66   char buf[32]; 66   char buf[32];
HITCBC 67   109 std::snprintf( 67   111 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   109 auto candidate = base / buf; 71   111 auto candidate = base / buf;
HITCBC 72   109 if (fs::create_directory(candidate, ec)) 72   111 if (fs::create_directory(candidate, ec))
73   { 73   {
HITCBC 74   109 dir_ = std::move(candidate); 74   111 dir_ = std::move(candidate);
HITCBC 75   218 return; 75   222 return;
76   } 76   }
HITCBC 77   109 } 77   111 }
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   109 } 81   111 }
82   82  
HITCBC 83   109 ~temp_socket_dir() noexcept 83   111 ~temp_socket_dir() noexcept
HITCBC 84   109 { 84   111 {
HITCBC 85   109 if (dir_.empty()) 85   111 if (dir_.empty())
MISUBC 86   return; 86   return;
87   87  
HITCBC 88   109 std::error_code ec; 88   111 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   109 std::remove(path().c_str()); 94   111 std::remove(path().c_str());
HITCBC 95   109 std::filesystem::remove_all(dir_, ec); 95   111 std::filesystem::remove_all(dir_, ec);
HITCBC 96   109 } 96   111 }
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   234 std::string path() const 102   238 std::string path() const
103   { 103   {
HITCBC 104   234 return (dir_ / "sock").string(); 104   238 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