Generated on for Gecode by doxygen 1.15.0
blackbox.cpp
Go to the documentation of this file.
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2/*
3 * Main authors:
4 * Jip J. Dekker <jip.dekker@monash.edu>
5 *
6 * Contributing authors:
7 * Mikael Zayenz Lagerkvist <lagerkvist@gecode.dev>
8 *
9 * Copyright:
10 * Jip J. Dekker, 2026
11 *
12 * This file is part of Gecode, the generic constraint
13 * development environment:
14 * http://www.gecode.dev
15 *
16 * Permission is hereby granted, free of charge, to any person obtaining
17 * a copy of this software and associated documentation files (the
18 * "Software"), to deal in the Software without restriction, including
19 * without limitation the rights to use, copy, modify, merge, publish,
20 * distribute, sublicense, and/or sell copies of the Software, and to
21 * permit persons to whom the Software is furnished to do so, subject to
22 * the following conditions:
23 *
24 * The above copyright notice and this permission notice shall be
25 * included in all copies or substantial portions of the Software.
26 *
27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 *
35 */
36
37#include "test/flatzinc.hh"
38
40
41#include <cstdint>
42#include <cerrno>
43#include <cstdio>
44#include <cstdlib>
45#include <exception>
46#include <fstream>
47#include <set>
48
49#ifdef GECODE_HAS_THREADS
50#include <thread>
51#endif
52
53#if defined(GECODE_HAS_POSIX_BLACKBOX_EXEC)
54#include <signal.h>
55#include <unistd.h>
56#endif
57
58namespace Test { namespace FlatZinc {
59
60 namespace {
61 const char* blackbox_decl =
62 "predicate gecode_blackbox("
63 "array[int] of var int: int_input, "
64 "array[int] of var float: float_input, "
65 "array[int] of var int: int_output, "
66 "array[int] of var float: float_output);\n";
67
68 const char* blackbox_bounds_decl =
69 "predicate gecode_blackbox_bounds("
70 "array[int] of var int: int_input, "
71 "array[int] of var float: float_input, "
72 "array[int] of int: flat_reason);\n";
73
74 std::string
75 fzn_string(const std::string& value) {
76 std::string quoted("\"");
77 for (char c : value) {
78 if ((c == '\\') || (c == '\"')) {
79 quoted += '\\';
80 }
81 quoted += c;
82 }
83 return quoted + "\"";
84 }
85
86 std::string
87 fixture_annotation(const char* mode, const std::string& fixture,
88 const std::vector<std::string>& args) {
89 std::string annotation("blackbox_");
90 annotation += mode;
91 annotation += "(" + fzn_string(fixture);
92 if (!args.empty()) {
93 annotation += ", [";
94 for (size_t i = 0; i < args.size(); ++i) {
95 if (i != 0) {
96 annotation += ", ";
97 }
98 annotation += fzn_string(args[i]);
99 }
100 annotation += "]";
101 }
102 return annotation + ")";
103 }
104
105 std::string
106 fixture_log(const std::string& name) {
107 const char* base = std::getenv("GECODE_TEST_BLACKBOX_LOG");
108 return (base == nullptr) ? std::string() : std::string(base) + "." + name;
109 }
110
111 void
112 reset_log(const std::string& log) {
113 std::remove(log.c_str());
114 }
115
116 std::vector<std::pair<std::string, std::string> >
117 read_log(const std::string& log) {
118 std::vector<std::pair<std::string, std::string> > entries;
119 std::ifstream in(log.c_str());
120 std::string event;
121 std::string id;
122 while (in >> event >> id) {
123 entries.push_back(std::make_pair(event, id));
124 }
125 return entries;
126 }
127
128 bool
129 concurrent_log(const std::string& log) {
130 const std::vector<std::pair<std::string, std::string> > entries =
131 read_log(log);
132 std::set<std::string> starts;
133 std::set<std::string> ready;
134 if ((entries.size() != 4) || (entries[0].first != "start") ||
135 (entries[1].first != "start") || (entries[2].first != "ready") ||
136 (entries[3].first != "ready")) {
137 return false;
138 }
139 for (const auto& entry : entries) {
140 (entry.first == "start" ? starts : ready).insert(entry.second);
141 }
142 return (starts.size() == 2) && (ready == starts);
143 }
144
145 bool
146 dll_parallel_lifecycle(const std::string& log) {
147 const std::vector<std::pair<std::string, std::string> > entries =
148 read_log(log);
149 std::set<std::string> clones;
150 std::set<std::string> starts;
151 std::set<std::string> ready;
152 std::set<std::string> freed;
153 std::vector<std::string> calls;
154 std::string prototype;
155 unsigned int inits = 0;
156 unsigned int clone_count = 0;
157 unsigned int start_count = 0;
158 unsigned int ready_count = 0;
159 unsigned int free_count = 0;
160 bool frees_after_calls = true;
161 for (const auto& entry : entries) {
162 if (entry.first == "init") {
163 ++inits;
164 prototype = entry.second;
165 } else if (entry.first == "clone") {
166 ++clone_count;
167 clones.insert(entry.second);
168 } else if (entry.first == "start") {
169 ++start_count;
170 starts.insert(entry.second);
171 calls.push_back(entry.first);
172 } else if (entry.first == "ready") {
173 ++ready_count;
174 ready.insert(entry.second);
175 calls.push_back(entry.first);
176 } else if (entry.first == "free") {
177 ++free_count;
178 freed.insert(entry.second);
179 frees_after_calls = frees_after_calls && (calls.size() == 4);
180 } else {
181 return false;
182 }
183 }
184 std::set<std::string> instances(clones);
185 instances.insert(prototype);
186 return (entries.size() == 10) && (entries[0].first == "init") &&
187 (inits == 1) && (clone_count == 2) && (start_count == 2) &&
188 (ready_count == 2) && (free_count == 3) && (clones.size() == 2) &&
189 (clones.find(prototype) == clones.end()) && (starts == clones) &&
190 (ready == clones) && (freed == instances) && frees_after_calls &&
191 (calls == std::vector<std::string>{"start", "start", "ready", "ready"});
192 }
193
194 bool
195 dll_model_lifecycle(const std::string& log) {
196 const std::vector<std::pair<std::string, std::string> > entries =
197 read_log(log);
198#ifdef GECODE_HAS_THREADS
199 if (entries.size() != 8) {
200 return false;
201 }
202 for (size_t i = 0; i < entries.size(); i += 4) {
203 if ((entries[i].first != "init") ||
204 (entries[i+1].first != "clone") ||
205 (entries[i+2] != std::make_pair(std::string("free"),
206 entries[i+1].second)) ||
207 (entries[i+3] != std::make_pair(std::string("free"),
208 entries[i].second))) {
209 return false;
210 }
211 }
212#else
213 if (entries.size() != 4) {
214 return false;
215 }
216 for (size_t i = 0; i < entries.size(); i += 2) {
217 if ((entries[i].first != "init") ||
218 (entries[i+1] != std::make_pair(std::string("free"),
219 entries[i].second))) {
220 return false;
221 }
222 }
223#endif
224 return true;
225 }
226
227#if defined(GECODE_HAS_POSIX_BLACKBOX_EXEC)
228 bool
229 descendant_gone(const std::string& output, const std::string& log) {
230 const std::vector<std::pair<std::string, std::string> > entries =
231 read_log(log);
232 reset_log(log);
233 if (output.find("y = 1;") == std::string::npos) {
234 return false;
235 }
236 for (const auto& entry : entries) {
237 if (entry.first == "descendant") {
238 const long pid = std::strtol(entry.second.c_str(), nullptr, 10);
239 if (pid <= 0) {
240 return false;
241 }
242 for (unsigned int i = 0; i < 500; ++i) {
243 if ((kill(static_cast<pid_t>(pid), 0) == -1) && (errno == ESRCH)) {
244 return true;
245 }
246 usleep(10000);
247 }
248 return false;
249 }
250 }
251 return false;
252 }
253#endif
254 }
255
256 namespace Blackbox {
257 class NativeProtocol : public Base {
258 public:
259 NativeProtocol(void) : Base("FlatZinc::blackbox::native_protocol") {}
260 virtual bool run(void) {
261 std::vector<int64_t> int_input{-2};
262 std::vector<double> float_input{1.25};
263 std::vector<int64_t> int_output(1);
264 std::vector<double> float_output(1);
266 int_input, float_input, int_output, float_output
267 };
268 try {
270 "-2;1.25\n") {
271 return false;
272 }
274 } catch (...) {
275 return false;
276 }
277 return (int_output[0] == 7) && (float_output[0] == 2.5);
278 }
279 };
280
281#ifdef GECODE_HAS_THREADS
282 template<class T>
283 bool
284 parallel_runs(T& black_box) {
285 bool echoed[2] = {false, false};
286 std::exception_ptr exception[2];
287 auto run = [&black_box, &echoed, &exception](int i) {
288 try {
289 const int64_t input = (i == 0) ? -1 : i + 1;
290 std::vector<int64_t> int_out(1);
291 std::vector<double> float_out;
292 black_box.run({input}, {}, int_out, float_out);
293 echoed[i] = (int_out[0] == input);
294 } catch (...) {
295 exception[i] = std::current_exception();
296 }
297 };
298 std::thread first;
299 std::thread second;
300 auto join = [&first, &second](void) {
301 if (first.joinable()) {
302 first.join();
303 }
304 if (second.joinable()) {
305 second.join();
306 }
307 };
308 try {
309 first = std::thread(run, 0);
310 second = std::thread(run, 1);
311 } catch (...) {
312 join();
313 return false;
314 }
315 join();
316 return (exception[0] == nullptr) && (exception[1] == nullptr) &&
317 echoed[0] && echoed[1];
318 }
319
320#if defined(_WIN32) || defined(GECODE_HAS_POSIX_BLACKBOX_EXEC)
321 class NativeExecParallelSessions : public Base {
322 private:
323 std::string executable;
324 std::string log;
325 public:
326 NativeExecParallelSessions(const std::string& executable0,
327 const std::string& log0)
328 : Base("FlatZinc::blackbox::native_exec_parallel_sessions"),
329 executable(executable0), log(log0) {}
330 virtual bool run(void) {
331 reset_log(log);
332 bool ok = false;
333 try {
334 Gecode::FlatZinc::BlackBoxExec black_box(executable,
335 {"normal", log});
336 ok = parallel_runs(black_box) && concurrent_log(log);
337 } catch (...) {}
338 reset_log(log);
339 return ok;
340 }
341 };
342#endif
343
345 private:
346 std::string library;
347 std::string log;
348 public:
349 NativeDllParallelCalls(const std::string& library0,
350 const std::string& log0)
351 : Base("FlatZinc::blackbox::native_dll_parallel_calls"),
352 library(library0), log(log0) {}
353 virtual bool run(void) {
354 reset_log(log);
355 bool ok = false;
356 try {
357 {
358 Gecode::FlatZinc::BlackBoxLibrary black_box(library,
359 {"normal", log});
360 ok = parallel_runs(black_box);
361 }
362 ok = ok && dll_parallel_lifecycle(log);
363 } catch (...) {}
364 reset_log(log);
365 return ok;
366 }
367 };
368#endif
369
370 class Create {
371 public:
373 Create(void) {
374 (void) new NativeProtocol;
375 (void) new FlatZincErrorTest("blackbox::malformed_annotation",
376 std::string(blackbox_decl) +
377 "var 0..1: y;\n"
378 "constraint gecode_blackbox([], [], [y], []) :: "
379 "blackbox_exec([]);\n"
380 "solve satisfy;\n", {},
381 "expected a target string and an argument array");
382
383#if defined(_WIN32) || defined(GECODE_HAS_POSIX_BLACKBOX_EXEC)
384 const char* exec = std::getenv("GECODE_TEST_BLACKBOX_EXEC");
385 if (exec != nullptr) {
386 const std::string executable(exec);
387 (void) new FlatZincTest("blackbox::constant_value",
388 std::string(blackbox_decl) +
389 "var 7..7: y :: output_var;\n"
390 "constraint gecode_blackbox([], [], [y], []) :: " +
391 fixture_annotation("exec", executable, {"value7"}) + ";\n"
392 "solve satisfy;\n",
393 "y = 7;\n----------\n");
394
395 (void) new FlatZincTest("blackbox::constant_value_unsat",
396 std::string(blackbox_decl) +
397 "var 8..8: y;\n"
398 "constraint gecode_blackbox([], [], [y], []) :: " +
399 fixture_annotation("exec", executable, {"value7"}) + ";\n"
400 "solve satisfy;\n",
401 "=====UNSATISFIABLE=====\n");
402
403 (void) new FlatZincTest("blackbox::reason_independent_bounds",
404 std::string(blackbox_bounds_decl) +
405 "var 5..5: x :: output_var;\n"
406 "constraint gecode_blackbox_bounds([x], [], [1,0,0]) :: " +
407 fixture_annotation("exec", executable, {"bounds2"}) + ";\n"
408 "solve satisfy;\n",
409 "x = 5;\n----------\n");
410
411 (void) new FlatZincTest("blackbox::reason_independent_bounds_unsat",
412 std::string(blackbox_bounds_decl) +
413 "var 6..6: x;\n"
414 "constraint gecode_blackbox_bounds([x], [], [1,0,0]) :: " +
415 fixture_annotation("exec", executable, {"bounds2"}) + ";\n"
416 "solve satisfy;\n",
417 "=====UNSATISFIABLE=====\n");
418
419 (void) new FlatZincTest("blackbox::reason_dependent_bounds",
420 std::string(blackbox_bounds_decl) +
421 "var 5..5: x :: output_var;\n"
422 "constraint gecode_blackbox_bounds([x], [], [1,1,1,1,0]) :: " +
423 fixture_annotation("exec", executable, {"bounds2"}) + ";\n"
424 "solve satisfy;\n",
425 "x = 5;\n----------\n");
426
427 (void) new FlatZincTest("blackbox::bounds_rescheduled_after_branch",
428 std::string(blackbox_bounds_decl) +
429 "var 0..1: x :: output_var;\n"
430 "var 0..5: y :: output_var;\n"
431 "constraint gecode_blackbox_bounds([x,y], [], "
432 "[1,0,0,2,1,1,1,1,1,2]) :: " +
433 fixture_annotation("exec", executable, {"dependent_bounds"}) +
434 ";\nsolve :: int_search([x], input_order, indomain_min, complete) "
435 "satisfy;\n",
436 "x = 0;\ny = 0;\n----------\n"
437 "x = 1;\ny = 5;\n----------\n==========\n",
438 false, {"-a"});
439
440#ifdef GECODE_HAS_FLOAT_VARS
441 (void) new FlatZincErrorTest("blackbox::missing_bounds_reason_entry",
442 std::string(blackbox_bounds_decl) +
443 "var 0..10: x;\n"
444 "var 0.0..10.0: y;\n"
445 "constraint gecode_blackbox_bounds([x], [y], [1,0,0]) :: " +
446 fixture_annotation("exec", executable, {"mixed"}) + ";\n"
447 "solve satisfy;\n", {}, "missing explained variable entry");
448#endif
449
450 (void) new FlatZincErrorTest("blackbox::duplicate_bounds_reason_entry",
451 std::string(blackbox_bounds_decl) +
452 "var 0..10: x;\n"
453 "var 0..10: y;\n"
454 "constraint gecode_blackbox_bounds([x,y], [], [1,0,0,1,0,0]) :: " +
455 fixture_annotation("exec", executable, {"bounds4"}) + ";\n"
456 "solve satisfy;\n", {}, "duplicate explained variable index");
457
458 (void) new FlatZincErrorTest("blackbox::invalid_bounds_reason_code",
459 std::string(blackbox_bounds_decl) +
460 "var 0..10: x;\n"
461 "constraint gecode_blackbox_bounds([x], [], [1,1,1,0,0]) :: " +
462 fixture_annotation("exec", executable, {"bounds2"}) + ";\n"
463 "solve satisfy;\n", {}, "dependency bound code is out of range");
464
465#ifdef GECODE_HAS_FLOAT_VARS
466 (void) new FlatZincErrorTest("blackbox::invalid_float_output",
467 std::string(blackbox_decl) +
468 "var 0.0..10.0: y;\n"
469 "constraint gecode_blackbox([], [], [], [y]) :: " +
470 fixture_annotation("exec", executable, {"nan"}) + ";\n"
471 "solve satisfy;\n", {}, "Failed to read output float 0");
472#endif
473
474 (void) new FlatZincErrorTest("blackbox::nul_output",
475 std::string(blackbox_decl) +
476 "constraint gecode_blackbox([], [], [], []) :: " +
477 fixture_annotation("exec", executable, {"nul"}) + ";\n"
478 "solve satisfy;\n", {}, "response contains NUL data");
479
480 (void) new FlatZincErrorTest("blackbox::malformed_exec_parallel",
481 std::string(blackbox_decl) +
482 "var 0..1: x :: output_var;\n"
483 "var 0..1: y :: output_var;\n"
484 "constraint gecode_blackbox([x], [], [y], []) :: " +
485 fixture_annotation("exec", executable, {"malformed"}) + ";\n"
486 "solve :: int_search([x], first_fail, indomain_min, complete) "
487 "satisfy;\n",
488 {"-p", "2"}, "Failed to read output integer 0");
489
490 for (int kind = 1; kind <= 5; ++kind) {
491 const char* expected = nullptr;
492 switch (kind) {
493 case 1:
494 expected = "Failed to read output integer 0";
495 break;
496 case 2:
497 expected = "provided an incomplete response";
498 break;
499 case 3:
500 expected = "response contains NUL data";
501 break;
502 case 4:
503 expected = "response exceeds the size limit";
504 break;
505 default:
506 expected = "integer 0 is outside Gecode's integer range";
507 break;
508 }
509 (void) new FlatZincErrorTest(
510 "blackbox::native_exec_fault_" + std::to_string(kind),
511 std::string(blackbox_decl) +
512 "var " + std::to_string(kind) + ".." + std::to_string(kind) +
513 ": x;\nvar 0..1: y;\n"
514 "constraint gecode_blackbox([x], [], [y], []) :: " +
515 fixture_annotation("exec", executable, {"fault"}) + ";\n"
516 "solve satisfy;\n", {}, expected);
517 }
518
519#ifdef GECODE_HAS_THREADS
520 const std::string exec_log = fixture_log("exec_parallel");
521 if (!exec_log.empty()) {
522 (void) new NativeExecParallelSessions(executable, exec_log);
523 }
524#endif
525
526#if defined(GECODE_HAS_POSIX_BLACKBOX_EXEC)
527 const std::string descendant_log = fixture_log("exec_descendant");
528 if (!descendant_log.empty()) {
529 (void) new FlatZincTest("blackbox::native_exec_descendant_cleanup",
530 std::string(blackbox_decl) +
531 "var 1..1: y :: output_var;\n"
532 "constraint gecode_blackbox([], [], [y], []) :: " +
533 fixture_annotation("exec", executable,
534 {"descendant", descendant_log}) + ";\n"
535 "solve satisfy;\n",
536 "", false, {},
537 [descendant_log](const std::string& output) {
538 return descendant_gone(output, descendant_log);
539 },
540 [descendant_log](void) { reset_log(descendant_log); });
541 }
542#endif
543 }
544
545 (void) new FlatZincErrorTest("blackbox::missing_exec_parallel",
546 std::string(blackbox_decl) +
547 "var 0..1: x :: output_var;\n"
548 "var 0..1: y :: output_var;\n"
549 "constraint gecode_blackbox([x], [], [y], []) :: "
550 "blackbox_exec(\"gecode-blackbox-missing-program\");\n"
551 "solve :: int_search([x], first_fail, indomain_min, complete) "
552 "satisfy;\n",
553 {"-p", "2"}, "starting blackbox process failed");
554
555 (void) new FlatZincErrorTest("blackbox::missing_exec_root_status",
556 std::string(blackbox_decl) +
557 "var 0..0: x :: output_var;\n"
558 "var 0..1: y :: output_var;\n"
559 "constraint gecode_blackbox([x], [], [y], []) :: "
560 "blackbox_exec(\"gecode-blackbox-missing-program\");\n"
561 "solve satisfy;\n",
562 {"-p", "2"}, "starting blackbox process failed");
563#endif
564
565 const char* dll = std::getenv("GECODE_TEST_BLACKBOX_DLL");
566 if (dll != nullptr) {
567 const std::string library(dll);
568 const std::string dll_model_log = fixture_log("dll_model");
569 if (!dll_model_log.empty()) {
570 (void) new FlatZincTest("blackbox::native_dll_per_constraint",
571 std::string(blackbox_decl) +
572 "var 1..1: a :: output_var;\n"
573 "var 1..1: b :: output_var;\n"
574 "constraint gecode_blackbox([], [], [a], []) :: " +
575 fixture_annotation("dll", library, {"normal", dll_model_log}) +
576 ";\nconstraint gecode_blackbox([], [], [b], []) :: " +
577 fixture_annotation("dll", library, {"normal", dll_model_log}) +
578 ";\nsolve satisfy;\n",
579 "a = 1;\nb = 1;\n----------\n", false, {},
580 [dll_model_log](const std::string& output) {
581 const bool ok = (output == "a = 1;\nb = 1;\n----------\n") &&
582 dll_model_lifecycle(dll_model_log);
583 reset_log(dll_model_log);
584 return ok;
585 },
586 [dll_model_log](void) { reset_log(dll_model_log); });
587 }
588
589#ifdef GECODE_HAS_THREADS
590 const std::string dll_log = fixture_log("dll_parallel");
591 if (!dll_log.empty()) {
592 (void) new NativeDllParallelCalls(library, dll_log);
593 }
594#endif
595
596#ifdef GECODE_HAS_FLOAT_VARS
597 (void) new FlatZincErrorTest("blackbox::native_dll_nonfinite",
598 std::string(blackbox_decl) +
599 "var 0.0..1.0: y;\n"
600 "constraint gecode_blackbox([], [], [], [y]) :: " +
601 fixture_annotation("dll", library, {"nan"}) + ";\n"
602 "solve satisfy;\n", {},
603 "library output float 0 is not a finite value");
604#endif
605 }
606 }
607 };
608
610 }
611
612}}
613
614// STATISTICS: test-flatzinc
int size(void) const
Return size of array (number of elements).
Definition array.hpp:1597
Persistent-process backend shared by equal executable configurations.
Dynamic-library backend owned by one blackbox constraint.
Base class for all tests to be run
Definition test.hh:121
Base(std::string s)
Create and register test with name s.
Definition test.cpp:60
Create(void)
Perform creation and registration.
Definition blackbox.cpp:373
NativeDllParallelCalls(const std::string &library0, const std::string &log0)
Definition blackbox.cpp:349
virtual bool run(void)
Run test.
Definition blackbox.cpp:260
Base class for tests for FlatZinc
Definition flatzinc.hh:55
std::string encode_blackbox_request(const BlackBoxCall &call)
Encode one request for the executable backend's line protocol.
void decode_blackbox_response(const std::string &response, BlackBoxCall &call)
Decode and validate one response from the executable backend.
const double base
Base for geometric restart sequence.
Definition search.hh:130
void log(Home home, FloatVar x0, FloatVar x1)
Post propagator for .
bool parallel_runs(T &black_box)
Definition blackbox.cpp:284
Tests for FlatZinc.
Definition flatzinc.cpp:38
Gecode::IntArgs i({1, 2, 3, 4})
General test support.
Definition afc.cpp:39
Inputs and pre-sized output buffers for one backend call.