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   105 temp_socket_dir() 46   109 temp_socket_dir()
HITCBC 47   105 { 47   109 {
48   namespace fs = std::filesystem; 48   namespace fs = std::filesystem;
HITCBC 49   105 auto const base = fs::temp_directory_path(); 49   109 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   113 }(); 57   117 }();
58   static std::atomic<std::uint64_t> counter{0}; 58   static std::atomic<std::uint64_t> counter{0};
59   59  
HITCBC 60   105 std::error_code ec; 60   109 std::error_code ec;
HITCBC 61   105 for (int tries = 0; tries < 32; ++tries) 61   109 for (int tries = 0; tries < 32; ++tries)
62   { 62   {
HITCBC 63   105 auto const n = counter.fetch_add(1, std::memory_order_relaxed); 63   109 auto const n = counter.fetch_add(1, std::memory_order_relaxed);
HITCBC 64   105 auto const tag = seed ^ n; 64   109 auto const tag = seed ^ n;
65   65  
66   char buf[32]; 66   char buf[32];
HITCBC 67   105 std::snprintf( 67   109 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   105 auto candidate = base / buf; 71   109 auto candidate = base / buf;
HITCBC 72   105 if (fs::create_directory(candidate, ec)) 72   109 if (fs::create_directory(candidate, ec))
73   { 73   {
HITCBC 74   105 dir_ = std::move(candidate); 74   109 dir_ = std::move(candidate);
HITCBC 75   210 return; 75   218 return;
76   } 76   }
HITCBC 77   105 } 77   109 }
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: " +
MISUNC   80 + ec.message());
HITCBC 80   105 } 81   109 }
81   82  
HITCBC 82   105 ~temp_socket_dir() noexcept 83   109 ~temp_socket_dir() noexcept
HITCBC 83   105 { 84   109 {
HITCBC 84   105 if (dir_.empty()) 85   109 if (dir_.empty())
MISUBC 85   return; 86   return;
86   87  
HITCBC 87   105 std::error_code ec; 88   109 std::error_code ec;
88   89  
89   // Unlink the socket file with C's std::remove from <cstdio> 90   // Unlink the socket file with C's std::remove from <cstdio>
90   // (deletes by name, no open) before remove_all, whose 91   // (deletes by name, no open) before remove_all, whose
91   // symlink_status opens AF_UNIX socket files via CreateFileW and 92   // symlink_status opens AF_UNIX socket files via CreateFileW and
92   // hangs on Windows. 93   // hangs on Windows.
HITCBC 93   105 std::remove(path().c_str()); 94   109 std::remove(path().c_str());
HITCBC 94   105 std::filesystem::remove_all(dir_, ec); 95   109 std::filesystem::remove_all(dir_, ec);
HITCBC 95   105 } 96   109 }
96   97  
97   temp_socket_dir(temp_socket_dir const&) = delete; 98   temp_socket_dir(temp_socket_dir const&) = delete;
98   temp_socket_dir& operator=(temp_socket_dir const&) = delete; 99   temp_socket_dir& operator=(temp_socket_dir const&) = delete;
99   100  
100   /// Path suitable for binding a local socket. 101   /// Path suitable for binding a local socket.
HITCBC 101   224 std::string path() const 102   234 std::string path() const
102   { 103   {
HITCBC 103   224 return (dir_ / "sock").string(); 104   234 return (dir_ / "sock").string();
104   } 105   }
105   106  
106   private: 107   private:
107   std::filesystem::path dir_; 108   std::filesystem::path dir_;
108   }; 109   };
109   110  
110   } // namespace boost::corosio::test 111   } // namespace boost::corosio::test
111   112  
112   #endif // BOOST_COROSIO_TEST_TEMP_PATH_HPP 113   #endif // BOOST_COROSIO_TEST_TEMP_PATH_HPP