PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::integration_test::test_server Class Reference

RAII wrapper for a test DICOM server. More...

#include <test_fixtures.h>

Collaboration diagram for kcenon::pacs::integration_test::test_server:
Collaboration graph

Public Member Functions

 test_server (uint16_t port=0, const std::string &ae_title=test_scp_ae_title)
 Construct and start a test server.
 
 ~test_server ()
 
 test_server (const test_server &)=delete
 
test_serveroperator= (const test_server &)=delete
 
 test_server (test_server &&)=delete
 
test_serveroperator= (test_server &&)=delete
 
template<typename Service >
void register_service (std::shared_ptr< Service > service)
 Register a service provider.
 
bool start ()
 Start the server and wait for it to be ready.
 
void stop ()
 Stop the server.
 
uint16_t port () const noexcept
 
const std::string & ae_title () const noexcept
 
bool is_running () const noexcept
 
network::dicom_serverserver ()
 

Static Private Member Functions

static bool check_port_listening (uint16_t port)
 Check if a port is listening (inline implementation)
 

Private Attributes

uint16_t port_
 
std::string ae_title_
 
std::unique_ptr< network::dicom_serverserver_
 
bool running_ {false}
 

Detailed Description

RAII wrapper for a test DICOM server.

Provides automatic server lifecycle management for tests. Server is started on construction and stopped on destruction.

Definition at line 530 of file test_fixtures.h.

Constructor & Destructor Documentation

◆ test_server() [1/3]

kcenon::pacs::integration_test::test_server::test_server ( uint16_t port = 0,
const std::string & ae_title = test_scp_ae_title )
inlineexplicit

Construct and start a test server.

Parameters
portPort to listen on (0 = auto-select)
ae_titleAE title for server

Definition at line 537 of file test_fixtures.h.

540 : port_(port == 0 ? find_available_port() : port)
542
543 network::server_config config;
544 config.ae_title = ae_title_;
545 config.port = port_;
546 config.max_associations = 20;
547 config.idle_timeout = std::chrono::seconds{60};
548 config.implementation_class_uid = "1.2.826.0.1.3680043.9.9999.1";
549 config.implementation_version_name = "TEST_SCP";
550
551 server_ = std::make_unique<network::dicom_server>(config);
552 }
const std::string & ae_title() const noexcept
std::unique_ptr< network::dicom_server > server_
uint16_t find_available_port(uint16_t start=default_test_port, int max_attempts=200)
Find an available port for testing.

References kcenon::pacs::network::server_config::ae_title, ae_title_, kcenon::pacs::network::server_config::idle_timeout, kcenon::pacs::network::server_config::implementation_class_uid, kcenon::pacs::network::server_config::implementation_version_name, kcenon::pacs::network::server_config::max_associations, kcenon::pacs::network::server_config::port, port_, and server_.

◆ ~test_server()

kcenon::pacs::integration_test::test_server::~test_server ( )
inline

Definition at line 554 of file test_fixtures.h.

554 {
555 stop();
556 }

References stop().

Here is the call graph for this function:

◆ test_server() [2/3]

kcenon::pacs::integration_test::test_server::test_server ( const test_server & )
delete

◆ test_server() [3/3]

kcenon::pacs::integration_test::test_server::test_server ( test_server && )
delete

Member Function Documentation

◆ ae_title()

const std::string & kcenon::pacs::integration_test::test_server::ae_title ( ) const
inlinenodiscardnoexcept
Returns
Server AE title

Definition at line 625 of file test_fixtures.h.

625{ return ae_title_; }

References ae_title_.

Referenced by TEST_CASE(), TEST_CASE(), TEST_CASE(), and TEST_CASE().

Here is the caller graph for this function:

◆ check_port_listening()

static bool kcenon::pacs::integration_test::test_server::check_port_listening ( uint16_t port)
inlinestaticprivate

Check if a port is listening (inline implementation)

This is a simplified version for use within test_server::start() since process_launcher is defined later in this file.

Definition at line 640 of file test_fixtures.h.

640 {
641 const long timeout_us = is_ci_environment() ? 1000000 : 200000;
642
643#ifdef _WIN32
644 WSADATA wsa_data;
645 if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) return false;
646
647 SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
648 if (sock == INVALID_SOCKET) { WSACleanup(); return false; }
649
650 u_long mode = 1;
651 ioctlsocket(sock, FIONBIO, &mode);
652
653 sockaddr_in addr{};
654 addr.sin_family = AF_INET;
655 addr.sin_port = htons(port);
656 addr.sin_addr.s_addr = inet_addr("127.0.0.1");
657
658 connect(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
659
660 fd_set writefds;
661 FD_ZERO(&writefds);
662 FD_SET(sock, &writefds);
663
664 timeval tv{};
665 tv.tv_sec = static_cast<decltype(tv.tv_sec)>(timeout_us / 1000000);
666 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(timeout_us % 1000000);
667 int result = select(0, nullptr, &writefds, nullptr, &tv);
668
669 bool listening = false;
670 if (result > 0) {
671 int error = 0;
672 int len = sizeof(error);
673 getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&error), &len);
674 listening = (error == 0);
675 }
676 closesocket(sock);
677 WSACleanup();
678 return listening;
679#else
680 int sock = socket(AF_INET, SOCK_STREAM, 0);
681 if (sock < 0) return false;
682
683 int flags = fcntl(sock, F_GETFL, 0);
684 fcntl(sock, F_SETFL, flags | O_NONBLOCK);
685
686 sockaddr_in addr{};
687 addr.sin_family = AF_INET;
688 addr.sin_port = htons(port);
689 addr.sin_addr.s_addr = inet_addr("127.0.0.1");
690
691 connect(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
692
693 fd_set writefds;
694 FD_ZERO(&writefds);
695 FD_SET(sock, &writefds);
696
697 timeval tv{};
698 tv.tv_sec = static_cast<decltype(tv.tv_sec)>(timeout_us / 1000000);
699 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(timeout_us % 1000000);
700 int result = select(sock + 1, nullptr, &writefds, nullptr, &tv);
701
702 bool listening = false;
703 if (result > 0) {
704 int error = 0;
705 socklen_t len = sizeof(error);
706 getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len);
707 listening = (error == 0);
708 }
709 close(sock);
710 return listening;
711#endif
712 }
@ error
Node returned an error.
bool is_ci_environment()
Check if running in a CI environment.

References kcenon::pacs::integration_test::is_ci_environment(), and port().

Referenced by start().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_running()

bool kcenon::pacs::integration_test::test_server::is_running ( ) const
inlinenodiscardnoexcept
Returns
true if server is running

Definition at line 628 of file test_fixtures.h.

References running_.

Referenced by TEST_CASE().

Here is the caller graph for this function:

◆ operator=() [1/2]

test_server & kcenon::pacs::integration_test::test_server::operator= ( const test_server & )
delete

◆ operator=() [2/2]

test_server & kcenon::pacs::integration_test::test_server::operator= ( test_server && )
delete

◆ port()

uint16_t kcenon::pacs::integration_test::test_server::port ( ) const
inlinenodiscardnoexcept
Returns
Server port

Definition at line 622 of file test_fixtures.h.

622{ return port_; }

References port_.

Referenced by check_port_listening().

Here is the caller graph for this function:

◆ register_service()

template<typename Service >
void kcenon::pacs::integration_test::test_server::register_service ( std::shared_ptr< Service > service)
inline

Register a service provider.

Parameters
serviceService provider to register

Definition at line 569 of file test_fixtures.h.

569 {
570 server_->register_service(std::move(service));
571 }

References server_.

Referenced by TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), and TEST_CASE().

Here is the caller graph for this function:

◆ server()

network::dicom_server & kcenon::pacs::integration_test::test_server::server ( )
inlinenodiscard
Returns
Reference to underlying server

Definition at line 631 of file test_fixtures.h.

631{ return *server_; }

References server_.

◆ start()

bool kcenon::pacs::integration_test::test_server::start ( )
inlinenodiscard

Start the server and wait for it to be ready.

Returns
true if server started and is accepting connections

This method:

  1. Starts the underlying DICOM server
  2. Waits for the port to actually be listening (with adaptive timeout)

The port listening check ensures the server is truly ready to accept connections, which is critical for reliable tests in CI environments where process startup can be slower.

Definition at line 585 of file test_fixtures.h.

585 {
586 auto result = server_->start();
587 if (!result.is_ok()) {
588 return false;
589 }
590
591 running_ = true;
592
593 // Wait for port to actually be listening
594 // This is more reliable than a fixed delay, especially in CI
596 auto start_time = std::chrono::steady_clock::now();
597
598 while (std::chrono::steady_clock::now() - start_time < timeout) {
600 return true;
601 }
602 std::this_thread::sleep_for(std::chrono::milliseconds{50});
603 }
604
605 // Timeout waiting for port - stop the server and return failure
606 server_->stop();
607 running_ = false;
608 return false;
609 }
static bool check_port_listening(uint16_t port)
Check if a port is listening (inline implementation)
std::chrono::milliseconds server_ready_timeout()
Port listening timeout for pacs_system servers (5s normal, 30s CI)
constexpr int timeout
Lock timeout exceeded.

References check_port_listening(), port_, running_, server_, and kcenon::pacs::integration_test::server_ready_timeout().

Referenced by TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), and TEST_CASE().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ stop()

void kcenon::pacs::integration_test::test_server::stop ( )
inline

Stop the server.

Definition at line 614 of file test_fixtures.h.

614 {
615 if (running_) {
616 server_->stop();
617 running_ = false;
618 }
619 }

References running_, and server_.

Referenced by TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), and ~test_server().

Here is the caller graph for this function:

Member Data Documentation

◆ ae_title_

std::string kcenon::pacs::integration_test::test_server::ae_title_
private

Definition at line 715 of file test_fixtures.h.

Referenced by ae_title(), and test_server().

◆ port_

uint16_t kcenon::pacs::integration_test::test_server::port_
private

Definition at line 714 of file test_fixtures.h.

Referenced by port(), start(), and test_server().

◆ running_

bool kcenon::pacs::integration_test::test_server::running_ {false}
private

Definition at line 717 of file test_fixtures.h.

717{false};

Referenced by is_running(), start(), and stop().

◆ server_

std::unique_ptr<network::dicom_server> kcenon::pacs::integration_test::test_server::server_
private

Definition at line 716 of file test_fixtures.h.

Referenced by register_service(), server(), start(), stop(), and test_server().


The documentation for this class was generated from the following file: