13#ifndef PACS_INTEGRATION_TESTS_DCMTK_TOOL_HPP
14#define PACS_INTEGRATION_TESTS_DCMTK_TOOL_HPP
71 auto result =
run_tool(
"echoscu", {
"--version"}, std::chrono::seconds{5});
79 static std::optional<std::string>
version() {
80 auto result =
run_tool(
"echoscu", {
"--version"}, std::chrono::seconds{5});
81 if (result.exit_code != 0) {
87 ? result.stderr_output
88 : result.stdout_output;
90 std::istringstream stream(output);
91 std::string first_line;
92 if (std::getline(stream, first_line) && !first_line.empty()) {
112 const std::string& host,
114 const std::string& called_ae,
115 const std::string& calling_ae =
"ECHOSCU",
116 std::chrono::seconds timeout = std::chrono::seconds{30}) {
118 std::vector<std::string> args = {
125 return run_tool(
"echoscu", args, timeout);
139 const std::string& host,
141 const std::string& called_ae,
142 const std::vector<std::filesystem::path>& files,
143 const std::string& calling_ae =
"STORESCU",
144 std::chrono::seconds timeout = std::chrono::seconds{60}) {
146 std::vector<std::string> args = {
153 for (
const auto& file : files) {
154 args.push_back(file.string());
157 return run_tool(
"storescu", args, timeout);
172 const std::string& host,
174 const std::string& called_ae,
175 const std::string& query_level,
176 const std::vector<std::pair<std::string, std::string>>& keys,
177 const std::string& calling_ae =
"FINDSCU",
178 std::chrono::seconds timeout = std::chrono::seconds{30}) {
180 std::vector<std::string> args = {
187 if (query_level ==
"PATIENT" || query_level ==
"STUDY" ||
188 query_level ==
"SERIES" || query_level ==
"IMAGE") {
189 args.push_back(
"-k");
190 args.push_back(
"QueryRetrieveLevel=" + query_level);
194 for (
const auto& [key, value] : keys) {
195 args.push_back(
"-k");
196 args.push_back(key +
"=" + value);
199 args.push_back(host);
200 args.push_back(std::to_string(port));
202 return run_tool(
"findscu", args, timeout);
218 const std::string& host,
220 const std::string& called_ae,
221 const std::string& dest_ae,
222 const std::string& query_level,
223 const std::vector<std::pair<std::string, std::string>>& keys,
224 const std::string& calling_ae =
"MOVESCU",
225 std::chrono::seconds timeout = std::chrono::seconds{120}) {
227 std::vector<std::string> args = {
231 "-k",
"QueryRetrieveLevel=" + query_level
235 for (
const auto& [key, value] : keys) {
236 args.push_back(
"-k");
237 args.push_back(key +
"=" + value);
240 args.push_back(host);
241 args.push_back(std::to_string(port));
243 return run_tool(
"movescu", args, timeout);
261 ? std::chrono::seconds{60}
262 : std::chrono::seconds{15};
276 const std::string& ae_title,
277 const std::filesystem::path& output_dir,
281 std::filesystem::create_directories(output_dir);
283 std::vector<std::string> args = {
285 "-od", output_dir.string(),
313 const std::string& ae_title,
316 std::vector<std::string> args = {
346 std::vector<std::string> search_paths = {
353 for (
const auto& path : search_paths) {
354 std::filesystem::path full_path = std::filesystem::path(path) / tool_name;
355 if (std::filesystem::exists(full_path)) {
356 return full_path.string();
372 const std::string& tool_name,
373 const std::vector<std::string>& args,
374 std::chrono::seconds timeout) {
381 result.
exit_code = process_res.exit_code;
384 result.
duration = process_res.duration;
385 result.
timed_out = process_res.timed_out;
397 const std::string& tool_name,
398 const std::vector<std::string>& args) {
424 const std::string& tool_name,
426 const std::vector<std::string>& args)
445 :
process_(std::move(other.process_))
446 ,
port_(other.port_) {
451 if (
this != &other) {
453 process_ = std::move(other.process_);
466 std::chrono::seconds timeout = std::chrono::seconds{10})
const {
484 [[nodiscard]] uint16_t
port() const noexcept {
return port_; }
493 std::vector<std::string> search_paths = {
500 for (
const auto& path : search_paths) {
501 std::filesystem::path full_path = std::filesystem::path(path) / tool_name;
502 if (std::filesystem::exists(full_path)) {
503 return full_path.string();
RAII wrapper for a background process.
void stop()
Stop the process.
void set_pid(process_launcher::pid_type pid)
Set the process ID.
process_launcher::pid_type pid() const noexcept
bool is_running() const
Check if process is running.
RAII guard for DCMTK server processes.
void stop()
Stop the server.
dcmtk_server_guard(const dcmtk_server_guard &)=delete
dcmtk_server_guard & operator=(const dcmtk_server_guard &)=delete
process_launcher::pid_type pid() const noexcept
dcmtk_server_guard(const std::string &tool_name, uint16_t port, const std::vector< std::string > &args)
Construct a server guard.
background_process_guard process_
bool wait_for_ready(std::chrono::seconds timeout=std::chrono::seconds{10}) const
Wait for the server to be ready (accepting connections)
uint16_t port() const noexcept
dcmtk_server_guard & operator=(dcmtk_server_guard &&other) noexcept
static std::string find_tool_path(const std::string &tool_name)
dcmtk_server_guard(dcmtk_server_guard &&other) noexcept
static constexpr pid_type invalid_pid
Invalid PID constant (0 is reserved on both platforms)
pid_t pid_type
Start a process in the background.
static process_result run(const std::string &executable, const std::vector< std::string > &args={}, std::chrono::seconds timeout=std::chrono::seconds{30})
Run a process and wait for completion.
static pid_type start_background(const std::string &executable, const std::vector< std::string > &args={})
static bool wait_for_port(uint16_t port, std::chrono::seconds timeout=std::chrono::seconds{10}, const std::string &host="127.0.0.1")
Wait for a port to be listening.
bool is_ci_environment()
Check if running in a CI environment.
Result of a DCMTK tool execution.
bool success() const noexcept
int exit_code
Process exit code.
std::chrono::milliseconds duration
Execution duration.
std::string stderr_output
Standard error.
std::string stdout_output
Standard output.
bool has_error() const noexcept
bool timed_out
Whether the process timed out.
Common test fixtures and utilities for integration tests.