Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/src/Progmasoft/Runner/Files.cpp
diff options
context:
space:
mode:
authorLeitwolf2026-10-24 19:03:40 +0300
committerGitHub2026-10-24 19:03:40 +0300
commit4f1c759cffcb14bd6511e1d7c5fbace682e24f2d (patch)
tree415fe5d48a5b48650bba3747beaf245d727c4f61 /src/Progmasoft/Runner/Files.cpp
parent83c6fc3ac13abe83fa39f7b5f8559ccaf4412233 (diff)
parent622188f9c08e4af35941c146979883ed4882410e (diff)
downloadcatch3-4f1c759cffcb14bd6511e1d7c5fbace682e24f2d.tar.gz
Merge pull request #15 from Progmasoft/feature/catch3-runner
Replace Catch2's runner with the Catch3 runner
Diffstat (limited to 'src/Progmasoft/Runner/Files.cpp')
-rw-r--r--src/Progmasoft/Runner/Files.cpp205
1 files changed, 205 insertions, 0 deletions
diff --git a/src/Progmasoft/Runner/Files.cpp b/src/Progmasoft/Runner/Files.cpp
new file mode 100644
index 00000000..68bde537
--- /dev/null
+++ b/src/Progmasoft/Runner/Files.cpp
@@ -0,0 +1,205 @@
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+#include "Files.hpp"
+
+#include "Hash.hpp"
+#include "Process.hpp"
+
+#if defined(_WIN32)
+# include <fcntl.h>
+# include <io.h>
+# include <share.h>
+# include <sys/stat.h>
+#else
+# include <fcntl.h>
+# include <sys/stat.h>
+# include <unistd.h>
+#endif
+
+#include <atomic>
+#include <cstdio>
+#include <fstream>
+#include <sstream>
+#include <system_error>
+#include <thread>
+
+namespace Progmasoft::Catch3::Runner
+{
+ namespace
+ {
+ /// Creates `path` only if it does not exist, in one atomic step.
+ bool
+ CreateExclusively(const std::filesystem::path &path)
+ {
+#if defined(_WIN32)
+ int descriptor = -1;
+ if (_wsopen_s(&descriptor,
+ path.wstring().c_str(),
+ _O_CREAT | _O_EXCL | _O_WRONLY,
+ _SH_DENYNO,
+ _S_IREAD | _S_IWRITE) != 0)
+ {
+ return false;
+ }
+ _close(descriptor);
+ return true;
+#else
+ const int descriptor = open(path.c_str(),
+ O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC,
+ S_IRUSR | S_IWUSR);
+ if (descriptor < 0)
+ {
+ return false;
+ }
+ close(descriptor);
+ return true;
+#endif
+ }
+ } // namespace
+
+ FileLock::FileLock(const std::filesystem::path &file,
+ std::chrono::milliseconds wait,
+ std::chrono::milliseconds stale)
+ : path_{file.string() + ".lock"}
+ {
+ std::error_code error;
+ std::filesystem::create_directories(path_.parent_path(), error);
+ const auto deadline = std::chrono::steady_clock::now() + wait;
+ while (true)
+ {
+ if (CreateExclusively(path_))
+ {
+ held_ = true;
+ return;
+ }
+ const auto written = std::filesystem::last_write_time(path_, error);
+ if (!error &&
+ std::filesystem::file_time_type::clock::now() - written > stale)
+ {
+ std::filesystem::remove(path_, error);
+ continue;
+ }
+ if (std::chrono::steady_clock::now() >= deadline)
+ {
+ return;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+ }
+
+ FileLock::~FileLock()
+ {
+ if (held_)
+ {
+ std::error_code error;
+ std::filesystem::remove(path_, error);
+ }
+ }
+
+ Expected<std::string>
+ ReadFile(const std::filesystem::path &file)
+ {
+ std::ifstream input(file, std::ios::binary);
+ if (!input)
+ {
+ return Error{"cannot read '" + file.string() + "'"};
+ }
+ std::ostringstream contents;
+ contents << input.rdbuf();
+ if (input.bad())
+ {
+ return Error{"cannot read '" + file.string() + "'"};
+ }
+ return contents.str();
+ }
+
+ Status
+ WriteFileAtomically(const std::filesystem::path &file,
+ std::string_view contents)
+ {
+ std::error_code error;
+ if (file.has_parent_path())
+ {
+ std::filesystem::create_directories(file.parent_path(), error);
+ }
+ const auto temporary =
+ std::filesystem::path{file.string() + "." + UniqueName() + ".tmp"};
+ {
+ std::ofstream output(temporary, std::ios::binary | std::ios::trunc);
+ output.write(contents.data(),
+ static_cast<std::streamsize>(contents.size()));
+ if (!output)
+ {
+ output.close();
+ std::filesystem::remove(temporary, error);
+ return Error{"cannot write '" + file.string() + "'"};
+ }
+ }
+ std::filesystem::rename(temporary, file, error);
+ if (error)
+ {
+ std::error_code ignored;
+ std::filesystem::remove(temporary, ignored);
+ return Error{"cannot replace '" + file.string() +
+ "': " + error.message()};
+ }
+ return Status::Ok();
+ }
+
+ Status
+ CreatePrivateFile(const std::filesystem::path &file)
+ {
+#if defined(_WIN32)
+ std::error_code error;
+ const auto status = std::filesystem::symlink_status(file, error);
+ if (!error && std::filesystem::exists(status) &&
+ !std::filesystem::is_regular_file(status))
+ {
+ return Error{"'" + file.string() + "' is not a regular file"};
+ }
+ int descriptor = -1;
+ if (_wsopen_s(&descriptor,
+ file.wstring().c_str(),
+ _O_CREAT | _O_TRUNC | _O_WRONLY | _O_NOINHERIT,
+ _SH_DENYNO,
+ _S_IREAD | _S_IWRITE) != 0)
+ {
+ return Error{"cannot create '" + file.string() + "'"};
+ }
+ _close(descriptor);
+ return Status::Ok();
+#else
+ int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NONBLOCK;
+# if defined(O_NOFOLLOW)
+ flags |= O_NOFOLLOW;
+# endif
+ const int descriptor = open(file.c_str(), flags, S_IRUSR | S_IWUSR);
+ if (descriptor < 0)
+ {
+ return Error{"cannot create '" + file.string() + "'"};
+ }
+ struct stat information = {};
+ if (fstat(descriptor, &information) != 0 ||
+ !S_ISREG(information.st_mode))
+ {
+ close(descriptor);
+ return Error{"'" + file.string() + "' is not a regular file"};
+ }
+ // A file that existed keeps its mode; make it private too.
+ fchmod(descriptor, S_IRUSR | S_IWUSR);
+ close(descriptor);
+ return Status::Ok();
+#endif
+ }
+
+ std::string
+ UniqueName()
+ {
+ static std::atomic<unsigned> counter{0};
+ std::ostringstream text;
+ text << CurrentProcessId() << '-'
+ << std::chrono::steady_clock::now().time_since_epoch().count()
+ << '-' << std::this_thread::get_id() << '-' << ++counter;
+ return ToHex(Fnv1a64(text.str()));
+ }
+} // namespace Progmasoft::Catch3::Runner