49#ifdef GECODE_HAS_THREADS
53#if defined(GECODE_HAS_POSIX_BLACKBOX_EXEC)
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";
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";
75 fzn_string(
const std::string& value) {
76 std::string quoted(
"\"");
77 for (
char c : value) {
78 if ((c ==
'\\') || (c ==
'\"')) {
87 fixture_annotation(
const char* mode,
const std::string& fixture,
88 const std::vector<std::string>& args) {
89 std::string annotation(
"blackbox_");
91 annotation +=
"(" + fzn_string(fixture);
94 for (
size_t i = 0;
i < args.
size(); ++
i) {
98 annotation += fzn_string(args[i]);
102 return annotation +
")";
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;
112 reset_log(
const std::string& log) {
113 std::remove(
log.c_str());
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());
122 while (in >> event >>
id) {
123 entries.push_back(std::make_pair(event,
id));
129 concurrent_log(
const std::string& log) {
130 const std::vector<std::pair<std::string, std::string> > entries =
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")) {
139 for (
const auto& entry : entries) {
140 (entry.first ==
"start" ? starts : ready).insert(entry.second);
142 return (starts.size() == 2) && (ready == starts);
146 dll_parallel_lifecycle(
const std::string& log) {
147 const std::vector<std::pair<std::string, std::string> > entries =
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") {
164 prototype = entry.second;
165 }
else if (entry.first ==
"clone") {
167 clones.insert(entry.second);
168 }
else if (entry.first ==
"start") {
170 starts.insert(entry.second);
171 calls.push_back(entry.first);
172 }
else if (entry.first ==
"ready") {
174 ready.insert(entry.second);
175 calls.push_back(entry.first);
176 }
else if (entry.first ==
"free") {
178 freed.insert(entry.second);
179 frees_after_calls = frees_after_calls && (calls.size() == 4);
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"});
195 dll_model_lifecycle(
const std::string& log) {
196 const std::vector<std::pair<std::string, std::string> > entries =
198#ifdef GECODE_HAS_THREADS
199 if (entries.size() != 8) {
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))) {
213 if (entries.size() != 4) {
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))) {
227#if defined(GECODE_HAS_POSIX_BLACKBOX_EXEC)
229 descendant_gone(
const std::string& output,
const std::string& log) {
230 const std::vector<std::pair<std::string, std::string> > entries =
233 if (output.find(
"y = 1;") == std::string::npos) {
236 for (
const auto& entry : entries) {
237 if (entry.first ==
"descendant") {
238 const long pid = std::strtol(entry.second.c_str(),
nullptr, 10);
242 for (
unsigned int i = 0;
i < 500; ++
i) {
243 if ((kill(
static_cast<pid_t
>(pid), 0) == -1) && (errno == ESRCH)) {
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
277 return (int_output[0] == 7) && (float_output[0] == 2.5);
281#ifdef GECODE_HAS_THREADS
285 bool echoed[2] = {
false,
false};
286 std::exception_ptr exception[2];
287 auto run = [&black_box, &echoed, &exception](
int i) {
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);
295 exception[i] = std::current_exception();
300 auto join = [&first, &second](void) {
301 if (first.joinable()) {
304 if (second.joinable()) {
309 first = std::thread(run, 0);
310 second = std::thread(run, 1);
316 return (exception[0] ==
nullptr) && (exception[1] ==
nullptr) &&
317 echoed[0] && echoed[1];
320#if defined(_WIN32) || defined(GECODE_HAS_POSIX_BLACKBOX_EXEC)
321 class NativeExecParallelSessions :
public Base {
323 std::string executable;
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) {
336 ok = parallel_runs(black_box) && concurrent_log(log);
350 const std::string& log0)
351 :
Base(
"FlatZinc::blackbox::native_dll_parallel_calls"),
352 library(library0), log(log0) {}
362 ok = ok && dll_parallel_lifecycle(log);
376 std::string(blackbox_decl) +
378 "constraint gecode_blackbox([], [], [y], []) :: "
379 "blackbox_exec([]);\n"
380 "solve satisfy;\n", {},
381 "expected a target string and an argument array");
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);
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"
393 "y = 7;\n----------\n");
395 (void)
new FlatZincTest(
"blackbox::constant_value_unsat",
396 std::string(blackbox_decl) +
398 "constraint gecode_blackbox([], [], [y], []) :: " +
399 fixture_annotation(
"exec", executable, {
"value7"}) +
";\n"
401 "=====UNSATISFIABLE=====\n");
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"
409 "x = 5;\n----------\n");
411 (void)
new FlatZincTest(
"blackbox::reason_independent_bounds_unsat",
412 std::string(blackbox_bounds_decl) +
414 "constraint gecode_blackbox_bounds([x], [], [1,0,0]) :: " +
415 fixture_annotation(
"exec", executable, {
"bounds2"}) +
";\n"
417 "=====UNSATISFIABLE=====\n");
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"
425 "x = 5;\n----------\n");
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) "
436 "x = 0;\ny = 0;\n----------\n"
437 "x = 1;\ny = 5;\n----------\n==========\n",
440#ifdef GECODE_HAS_FLOAT_VARS
442 std::string(blackbox_bounds_decl) +
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");
451 std::string(blackbox_bounds_decl) +
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");
459 std::string(blackbox_bounds_decl) +
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");
465#ifdef GECODE_HAS_FLOAT_VARS
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");
475 std::string(blackbox_decl) +
476 "constraint gecode_blackbox([], [], [], []) :: " +
477 fixture_annotation(
"exec", executable, {
"nul"}) +
";\n"
478 "solve satisfy;\n", {},
"response contains NUL data");
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) "
488 {
"-p",
"2"},
"Failed to read output integer 0");
490 for (
int kind = 1; kind <= 5; ++kind) {
491 const char* expected =
nullptr;
494 expected =
"Failed to read output integer 0";
497 expected =
"provided an incomplete response";
500 expected =
"response contains NUL data";
503 expected =
"response exceeds the size limit";
506 expected =
"integer 0 is outside Gecode's integer range";
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);
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);
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"
537 [descendant_log](
const std::string& output) {
538 return descendant_gone(output, descendant_log);
540 [descendant_log](void) { reset_log(descendant_log); });
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) "
553 {
"-p",
"2"},
"starting blackbox process failed");
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"
562 {
"-p",
"2"},
"starting blackbox process failed");
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);
586 [dll_model_log](void) { reset_log(dll_model_log); });
589#ifdef GECODE_HAS_THREADS
590 const std::string dll_log = fixture_log(
"dll_parallel");
591 if (!dll_log.empty()) {
596#ifdef GECODE_HAS_FLOAT_VARS
598 std::string(blackbox_decl) +
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");
int size(void) const
Return size of array (number of elements).
Persistent-process backend shared by equal executable configurations.
Dynamic-library backend owned by one blackbox constraint.
Base class for all tests to be run
Base(std::string s)
Create and register test with name s.
Create(void)
Perform creation and registration.
virtual bool run(void)
Run test.
NativeDllParallelCalls(const std::string &library0, const std::string &log0)
virtual bool run(void)
Run test.
Base class for tests for FlatZinc
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.
void log(Home home, FloatVar x0, FloatVar x1)
Post propagator for .
bool parallel_runs(T &black_box)
Gecode::IntArgs i({1, 2, 3, 4})
Inputs and pre-sized output buffers for one backend call.