camcom 1.0.0-pre2
 
Loading...
Searching...
No Matches
test_fixture.hpp
Go to the documentation of this file.
1
8
9#ifndef CAMCOM_TEST_FIXTURE_HPP
10#define CAMCOM_TEST_FIXTURE_HPP
11
12#include <gtest/gtest.h>
13#include <memory>
14#include <string>
15#include <thread>
16#include <chrono>
17#include <filesystem>
18#include <cstdlib>
19#include <sys/socket.h>
20#include <netinet/in.h>
21#include <unistd.h>
22
27
28#include <ifw/fnd/defs/base.hpp>
29
30namespace camcom::test {
31
32// ---------------------------------------------------------------------------
33// Logger installation — install a null logger so tests don't pollute
34// stdout with FND* output. Idempotent across fixtures.
35// ---------------------------------------------------------------------------
36inline void initLogger() {
37 static bool done = false;
38 if (!done) {
39 ifw::fnd::InstallLogger(ifw::fnd::MakeNullLogger());
40 done = true;
41 }
42}
43
44// ---------------------------------------------------------------------------
45// Helpers
46// ---------------------------------------------------------------------------
47
49inline int findFreePort() {
50 int sock = socket(AF_INET, SOCK_STREAM, 0);
51 struct sockaddr_in addr {};
52 addr.sin_family = AF_INET;
53 addr.sin_addr.s_addr = INADDR_ANY;
54 addr.sin_port = 0;
55 bind(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
56
57 socklen_t len = sizeof(addr);
58 getsockname(sock, reinterpret_cast<struct sockaddr*>(&addr), &len);
59 int port = ntohs(addr.sin_port);
60 close(sock);
61 return port;
62}
63
65template <typename Pred>
66bool waitFor(Pred pred, int timeout_ms = 3000, int poll_ms = 50) {
67 auto deadline =
68 std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
69 while (std::chrono::steady_clock::now() < deadline) {
70 if (pred()) return true;
71 std::this_thread::sleep_for(std::chrono::milliseconds(poll_ms));
72 }
73 return pred(); // last chance
74}
75
78 const std::string& rec_dir) {
80 cfg.camera_name = "TestSimCamera";
81 cfg.uri = "sim://localhost";
82 cfg.timeout = 10;
83
84 cfg.adapter.library = "libcamcom_sim.so";
85 cfg.adapter.factory_symbol = "CreateAdapter";
86 cfg.adapter.auto_connect = false;
87
88 // SimAdapter uses snake_case parameter names internally
89 // Set initial parameter values directly
90 cfg.set_values = {
91 {"acquisition_mode", "Continuous"},
92 {"exposure_time", "0.01"},
93 {"frame_rate", "0.0"},
94 {"width", "64"},
95 {"height", "64"},
96 {"offset_x", "0"},
97 {"offset_y", "0"},
98 {"binning_horizontal", "1"},
99 {"binning_vertical", "1"},
100 {"gain", "4.0"},
101 {"sim_type", "Gaussian"},
102 {"sim_data_type", "UInt16"},
103 {"sim_nb_planes", "11"},
104 {"sim_amplitude", "1000.0"},
105 {"sim_sigma", "5.0"},
106 {"sim_noise_level", "50"},
107 };
108
109 cfg.http_server.host = "127.0.0.1";
110 cfg.http_server.port = port;
111
112 cfg.web_interface.enabled = true;
113 cfg.web_interface.compression = "raw";
114 cfg.web_interface.max_fps = 20;
115 cfg.web_interface.max_image_width = 0; // no downscaling
117
118 cfg.streaming.enabled = true;
119 cfg.streaming.protocol = "websocket";
120
122 cfg.acquisition.skip_silently = true;
123
124 cfg.recording.directory = rec_dir;
125 cfg.recording.format = "fits:cube";
126 cfg.recording.compression = "none";
127 cfg.recording.nb_of_frames = 10;
129 cfg.recording.skip_silently = true;
130 cfg.recording.overwrite_policy = "increment";
132 cfg.recording.trigger = "immediate";
133
134 cfg.telemetry.enabled = true;
135 cfg.telemetry.sampling_period = 0.5; // fast for tests
137
138 cfg.statistics.enabled = true;
139 cfg.statistics.window_size = 100;
140
141 cfg.logging.level = "OFF";
142
143 return cfg;
144}
145
148 const std::string& rec_dir) {
150 cfg.camera_name = "TestGenICamEmu";
151 cfg.uri = "genicam-emu://localhost";
152 cfg.timeout = 10;
153
154 cfg.adapter.library = "libcamcom_genicam_emu.so";
155 cfg.adapter.factory_symbol = "CreateAdapter";
156 cfg.adapter.auto_connect = false;
157
158 // GenICam emulator uses SFNC parameter names directly
159 // Set initial parameter values using native names
160 cfg.set_values = {
161 {"AcquisitionMode", "Continuous"},
162 {"ExposureTime", "1000.0"},
163 {"AcquisitionFrameRate", "30.0"},
164 {"Width", "64"},
165 {"Height", "64"},
166 {"OffsetX", "0"},
167 {"OffsetY", "0"},
168 {"BinningHorizontal", "1"},
169 {"BinningVertical", "1"},
170 {"Gain", "1.0"},
171 {"TriggerMode", "Off"},
172 {"PixelFormat", "Mono8"},
173 };
174
175 cfg.http_server.host = "127.0.0.1";
176 cfg.http_server.port = port;
177
178 cfg.web_interface.enabled = true;
179 cfg.web_interface.compression = "raw";
180 cfg.web_interface.max_fps = 20;
183
184 cfg.streaming.enabled = true;
185 cfg.streaming.protocol = "websocket";
186
188 cfg.acquisition.skip_silently = true;
189
190 cfg.recording.directory = rec_dir;
191 cfg.recording.format = "fits:cube";
192 cfg.recording.compression = "none";
193 cfg.recording.nb_of_frames = 10;
195 cfg.recording.skip_silently = true;
196 cfg.recording.overwrite_policy = "increment";
198 cfg.recording.trigger = "immediate";
199
200 cfg.telemetry.enabled = true;
201 cfg.telemetry.sampling_period = 0.5;
203
204 cfg.statistics.enabled = true;
205 cfg.statistics.window_size = 100;
206
207 cfg.logging.level = "OFF";
208
209 return cfg;
210}
211
212// ---------------------------------------------------------------------------
213// CameraServerFixture — base fixture: starts server, provides HTTP client
214// ---------------------------------------------------------------------------
215class CameraServerFixture : public ::testing::Test {
216protected:
217 void SetUp() override {
218 initLogger();
219
220 port_ = findFreePort();
221
222 // Create temp directory for recordings
223 rec_dir_ = std::filesystem::temp_directory_path() /
224 ("camcom_test_" + std::to_string(port_));
225 std::filesystem::create_directories(rec_dir_);
226
227 config_ = buildTestConfig(port_, rec_dir_.string());
228 server_ = std::make_unique<server::CameraServer>(config_);
229 ASSERT_TRUE(server_->start()) << "Server failed to start on port " << port_;
230
231 client_ = std::make_unique<httplib::Client>("127.0.0.1", port_);
232 client_->set_connection_timeout(3);
233 client_->set_read_timeout(5);
234
235 // Wait for /health to respond
236 bool healthy = waitFor([this]() {
237 auto res = client_->Get("/health");
238 return res && res->status == 200;
239 });
240 ASSERT_TRUE(healthy) << "Server did not become healthy within timeout";
241 }
242
243 void TearDown() override {
244 if (server_) {
245 server_->stop();
246 }
247 // Clean up temp recording directory
248 std::error_code ec;
249 std::filesystem::remove_all(rec_dir_, ec);
250 }
251
252 httplib::Client& client() { return *client_; }
253 const server::ServerConfig& config() const { return config_; }
254 server::CameraServer& server() { return *server_; }
255 int port() const { return port_; }
256 const std::filesystem::path& recDir() const { return rec_dir_; }
257
259 static std::string jsonStr(const std::string& body, const std::string& key) {
260 std::string needle = "\"" + key + "\":\"";
261 auto pos = body.find(needle);
262 if (pos == std::string::npos) return "";
263 pos += needle.size();
264 auto end = body.find('"', pos);
265 if (end == std::string::npos) return "";
266 return body.substr(pos, end - pos);
267 }
268
270 static bool jsonBool(const std::string& body, const std::string& key) {
271 std::string needle = "\"" + key + "\":";
272 auto pos = body.find(needle);
273 if (pos == std::string::npos) return false;
274 pos += needle.size();
275 return body.substr(pos, 4) == "true";
276 }
277
279 static int jsonInt(const std::string& body, const std::string& key) {
280 std::string needle = "\"" + key + "\":";
281 auto pos = body.find(needle);
282 if (pos == std::string::npos) return -1;
283 pos += needle.size();
284 return std::stoi(body.substr(pos));
285 }
286
288 static double jsonDouble(const std::string& body, const std::string& key) {
289 std::string needle = "\"" + key + "\":";
290 auto pos = body.find(needle);
291 if (pos == std::string::npos) return 0.0;
292 pos += needle.size();
293 return std::stod(body.substr(pos));
294 }
295
296private:
297 int port_{0};
298 std::filesystem::path rec_dir_;
299 server::ServerConfig config_;
300 std::unique_ptr<server::CameraServer> server_;
301 std::unique_ptr<httplib::Client> client_;
302};
303
304// ---------------------------------------------------------------------------
305// ConnectedServerFixture — camera connected
306// ---------------------------------------------------------------------------
308protected:
309 void SetUp() override {
311 auto res = client().Get("/connect");
312 ASSERT_TRUE(res) << "Failed to send /connect request";
313 ASSERT_EQ(res->status, 200) << "Connect failed: " << res->body;
314 }
315
316 void TearDown() override {
317 client().Get("/disconnect");
319 }
320};
321
322// ---------------------------------------------------------------------------
323// AcquiringServerFixture — camera connected + acquisition running
324// ---------------------------------------------------------------------------
326protected:
327 void SetUp() override {
329 auto res = client().Get("/acquisition/start");
330 ASSERT_TRUE(res) << "Failed to send /acquisition/start";
331 ASSERT_EQ(res->status, 200) << "Start acquisition failed: " << res->body;
332
333 // Wait until at least one frame is available
334 bool got_frame = waitFor([this]() {
335 auto r = client().Get("/image");
336 if (!r || r->status != 200) return false;
337 return r->body.find("\"frame_id\":0") == std::string::npos &&
338 r->body.find("\"changed\":true") != std::string::npos;
339 }, 5000);
340 ASSERT_TRUE(got_frame) << "No frame received within timeout";
341 }
342
343 void TearDown() override {
344 client().Get("/acquisition/stop");
346 }
347};
348
349// ---------------------------------------------------------------------------
350// GenICam Emulator fixtures — same tiers, different adapter
351// ---------------------------------------------------------------------------
352
353class GenICamEmuServerFixture : public ::testing::Test {
354protected:
355 void SetUp() override {
356 initLogger();
357
358 port_ = findFreePort();
359
360 rec_dir_ = std::filesystem::temp_directory_path() /
361 ("camcom_emu_test_" + std::to_string(port_));
362 std::filesystem::create_directories(rec_dir_);
363
364 config_ = buildGenICamEmuTestConfig(port_, rec_dir_.string());
365 server_ = std::make_unique<server::CameraServer>(config_);
366 ASSERT_TRUE(server_->start()) << "Server failed to start on port " << port_;
367
368 client_ = std::make_unique<httplib::Client>("127.0.0.1", port_);
369 client_->set_connection_timeout(3);
370 client_->set_read_timeout(5);
371
372 bool healthy = waitFor([this]() {
373 auto res = client_->Get("/health");
374 return res && res->status == 200;
375 });
376 ASSERT_TRUE(healthy) << "Server did not become healthy within timeout";
377 }
378
379 void TearDown() override {
380 if (server_) {
381 server_->stop();
382 }
383 std::error_code ec;
384 std::filesystem::remove_all(rec_dir_, ec);
385 }
386
387 httplib::Client& client() { return *client_; }
388 const server::ServerConfig& config() const { return config_; }
389 server::CameraServer& server() { return *server_; }
390 int port() const { return port_; }
391 const std::filesystem::path& recDir() const { return rec_dir_; }
392
393 static std::string jsonStr(const std::string& body, const std::string& key) {
394 std::string needle = "\"" + key + "\":\"";
395 auto pos = body.find(needle);
396 if (pos == std::string::npos) return "";
397 pos += needle.size();
398 auto end = body.find('"', pos);
399 if (end == std::string::npos) return "";
400 return body.substr(pos, end - pos);
401 }
402
403 static bool jsonBool(const std::string& body, const std::string& key) {
404 std::string needle = "\"" + key + "\":";
405 auto pos = body.find(needle);
406 if (pos == std::string::npos) return false;
407 pos += needle.size();
408 return body.substr(pos, 4) == "true";
409 }
410
411 static int jsonInt(const std::string& body, const std::string& key) {
412 std::string needle = "\"" + key + "\":";
413 auto pos = body.find(needle);
414 if (pos == std::string::npos) return -1;
415 pos += needle.size();
416 return std::stoi(body.substr(pos));
417 }
418
419 static double jsonDouble(const std::string& body, const std::string& key) {
420 std::string needle = "\"" + key + "\":";
421 auto pos = body.find(needle);
422 if (pos == std::string::npos) return 0.0;
423 pos += needle.size();
424 return std::stod(body.substr(pos));
425 }
426
427private:
428 int port_{0};
429 std::filesystem::path rec_dir_;
430 server::ServerConfig config_;
431 std::unique_ptr<server::CameraServer> server_;
432 std::unique_ptr<httplib::Client> client_;
433};
434
436protected:
437 void SetUp() override {
439 auto res = client().Get("/connect");
440 ASSERT_TRUE(res) << "Failed to send /connect request";
441 ASSERT_EQ(res->status, 200) << "Connect failed: " << res->body;
442 }
443
444 void TearDown() override {
445 client().Get("/disconnect");
447 }
448};
449
451protected:
452 void SetUp() override {
454 auto res = client().Get("/acquisition/start");
455 ASSERT_TRUE(res) << "Failed to send /acquisition/start";
456 ASSERT_EQ(res->status, 200) << "Start acquisition failed: " << res->body;
457
458 bool got_frame = waitFor([this]() {
459 auto r = client().Get("/image");
460 if (!r || r->status != 200) return false;
461 return r->body.find("\"frame_id\":0") == std::string::npos &&
462 r->body.find("\"changed\":true") != std::string::npos;
463 }, 5000);
464 ASSERT_TRUE(got_frame) << "No frame received within timeout";
465 }
466
467 void TearDown() override {
468 client().Get("/acquisition/stop");
470 }
471};
472
473} // namespace camcom::test
474
475#endif // CAMCOM_TEST_FIXTURE_HPP
Main camera server class Orchestrates all components: adapter, HTTP server, frame pipeline.
Definition server.hpp:43
Definition test_fixture.hpp:450
void TearDown() override
Definition test_fixture.hpp:467
void SetUp() override
Definition test_fixture.hpp:452
Definition test_fixture.hpp:325
void TearDown() override
Definition test_fixture.hpp:343
void SetUp() override
Definition test_fixture.hpp:327
Definition test_fixture.hpp:215
static double jsonDouble(const std::string &body, const std::string &key)
Parse JSON double value: {"key":1.5} → 1.5.
Definition test_fixture.hpp:288
int port() const
Definition test_fixture.hpp:255
static int jsonInt(const std::string &body, const std::string &key)
Parse JSON int value: {"key":42} → 42.
Definition test_fixture.hpp:279
void TearDown() override
Definition test_fixture.hpp:243
static bool jsonBool(const std::string &body, const std::string &key)
Parse JSON bool value: {"key":true} → true.
Definition test_fixture.hpp:270
static std::string jsonStr(const std::string &body, const std::string &key)
Parse JSON string value: {"key":"value"} → value.
Definition test_fixture.hpp:259
const server::ServerConfig & config() const
Definition test_fixture.hpp:253
server::CameraServer & server()
Definition test_fixture.hpp:254
const std::filesystem::path & recDir() const
Definition test_fixture.hpp:256
void SetUp() override
Definition test_fixture.hpp:217
httplib::Client & client()
Definition test_fixture.hpp:252
Definition test_fixture.hpp:435
void SetUp() override
Definition test_fixture.hpp:437
void TearDown() override
Definition test_fixture.hpp:444
Definition test_fixture.hpp:307
void SetUp() override
Definition test_fixture.hpp:309
void TearDown() override
Definition test_fixture.hpp:316
Definition test_fixture.hpp:353
static std::string jsonStr(const std::string &body, const std::string &key)
Definition test_fixture.hpp:393
int port() const
Definition test_fixture.hpp:390
server::CameraServer & server()
Definition test_fixture.hpp:389
void TearDown() override
Definition test_fixture.hpp:379
static int jsonInt(const std::string &body, const std::string &key)
Definition test_fixture.hpp:411
const std::filesystem::path & recDir() const
Definition test_fixture.hpp:391
static bool jsonBool(const std::string &body, const std::string &key)
Definition test_fixture.hpp:403
void SetUp() override
Definition test_fixture.hpp:355
httplib::Client & client()
Definition test_fixture.hpp:387
static double jsonDouble(const std::string &body, const std::string &key)
Definition test_fixture.hpp:419
const server::ServerConfig & config() const
Definition test_fixture.hpp:388
Definition httplib.h:2388
Result Get(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:14740
Header file for the CamCom Common Library.
Definition test_fixture.hpp:30
server::ServerConfig buildGenICamEmuTestConfig(int port, const std::string &rec_dir)
Build a minimal ServerConfig for GenICam emulator integration tests.
Definition test_fixture.hpp:147
bool waitFor(Pred pred, int timeout_ms=3000, int poll_ms=50)
Poll a predicate with timeout. Returns true if predicate became true.
Definition test_fixture.hpp:66
void initLogger()
Definition test_fixture.hpp:36
server::ServerConfig buildTestConfig(int port, const std::string &rec_dir)
Build a minimal ServerConfig for integration tests.
Definition test_fixture.hpp:77
int findFreePort()
Find a free TCP port by binding to port 0.
Definition test_fixture.hpp:49
bool skip_silently
Definition config.hpp:136
int frame_queue_size
Definition config.hpp:135
bool auto_connect
Definition config.hpp:24
std::string factory_symbol
Definition config.hpp:23
std::string library
Definition config.hpp:21
int port
Definition config.hpp:40
std::string host
Definition config.hpp:39
std::string level
Definition config.hpp:113
std::string trigger
Definition config.hpp:83
bool cube_timestamp_table
Definition config.hpp:81
std::string compression
Definition config.hpp:70
std::string format
Definition config.hpp:69
bool skip_silently
Definition config.hpp:79
std::string overwrite_policy
Definition config.hpp:80
int nb_of_frames
Definition config.hpp:74
std::string directory
Definition config.hpp:68
int max_frames_buffer
Definition config.hpp:78
Main server configuration.
Definition config.hpp:157
SetValues set_values
Definition config.hpp:168
AcquisitionConfig acquisition
Definition config.hpp:174
std::string camera_name
Definition config.hpp:162
LoggingConfig logging
Definition config.hpp:178
WebInterfaceConfig web_interface
Definition config.hpp:172
HttpServerConfig http_server
Definition config.hpp:171
RecordingConfig recording
Definition config.hpp:175
StreamingConfig streaming
Definition config.hpp:173
TelemetryConfig telemetry
Definition config.hpp:176
AdapterConfig adapter
Definition config.hpp:167
std::string uri
Definition config.hpp:163
int timeout
Definition config.hpp:164
StatisticsConfig statistics
Definition config.hpp:177
bool enabled
Definition config.hpp:105
int window_size
Definition config.hpp:106
bool enabled
Definition config.hpp:60
std::string protocol
Definition config.hpp:61
double sampling_period
Definition config.hpp:93
bool sample_read_only_pars
Definition config.hpp:96
bool enabled
Definition config.hpp:92
bool enabled
Definition config.hpp:48
int max_image_height
Definition config.hpp:50
std::string compression
Definition config.hpp:52
int max_image_width
Definition config.hpp:49
int max_fps
Definition config.hpp:51
std::string body
Definition httplib.h:1294