YAC 3.21.0
Yet Another Coupler
Loading...
Searching...
No Matches
test_expect_abort.h
Go to the documentation of this file.
1// Copyright (c) 2026 The YAC Authors
2//
3// SPDX-License-Identifier: BSD-3-Clause
4
52#ifndef TEST_EXPECT_ABORT_H
53#define TEST_EXPECT_ABORT_H
54
55#include <setjmp.h>
56#include <stdio.h>
57#include <string.h>
58#include <mpi.h>
59
60#include "ppm/core.h"
61#include "yac_assert.h"
62
75
80#if defined(YAC_CODE_COVERAGE_TEST)
81# define YAC_TEST_ABORT_UNHAPPY_PATH_ENABLED 0
82#else
83# define YAC_TEST_ABORT_UNHAPPY_PATH_ENABLED 1
84#endif
85
86// ---- internal state (one copy per translation unit) -------------------------
87
89
90static struct {
91 jmp_buf jmp;
92 char const * substr_;
95} yac_test_abort_state_ __attribute__((unused));
96
98static inline void utest_abort_handler_(
99 MPI_Comm comm, char const * msg, char const * source, int line) {
100
101 UNUSED(comm); UNUSED(source); UNUSED(line);
102
103 // the message may be stored in memory that is no longer valid once we
104 // jumped out of the aborting routine, hence keep a copy of it
105 snprintf(
106 yac_test_abort_state_.msg_, sizeof(yac_test_abort_state_.msg_),
107 "%s", (msg != NULL)?msg:"(null)");
108
109 int is_expected =
110 (yac_test_abort_state_.substr_ == NULL) ||
111 (strstr(
112 yac_test_abort_state_.msg_, yac_test_abort_state_.substr_) != NULL);
113
114 longjmp(
115 yac_test_abort_state_.jmp,
117}
118
124static inline int utest_abort_skip_(
125 enum yac_test_abort_result expected_result) {
126
127#if YAC_TEST_ABORT_UNHAPPY_PATH_ENABLED
128 UNUSED(expected_result);
129 return 0;
130#else
131 return expected_result != YAC_TEST_ABORT_NO_CALL;
132#endif
133}
134
135static inline char const * utest_abort_result_name_(
136 enum yac_test_abort_result result) {
137
138 switch (result) {
139 case (YAC_TEST_ABORT_NO_CALL): return "no abort";
140 case (YAC_TEST_ABORT_EXPECTED): return "abort with expected message";
141 case (YAC_TEST_ABORT_UNEXPECTED): return "abort with unexpected message";
142 default: return "invalid outcome";
143 }
144}
145
151static inline int utest_abort_failed_(
152 enum yac_test_abort_result result,
153 enum yac_test_abort_result expected_result, char const * source, int line) {
154
155 if (result == expected_result) return 0;
156
157 fprintf(
158 stderr, "ERROR(%s:%d): expected \"%s\" but got \"%s\"\n", source, line,
159 utest_abort_result_name_(expected_result),
160 utest_abort_result_name_(result));
161 if (yac_test_abort_state_.msg_[0] != '\0')
162 fprintf(
163 stderr, " abort message on this process: \"%s\"\n",
164 yac_test_abort_state_.msg_);
165
166 return 1;
167}
168
174static inline enum yac_test_abort_result utest_abort_reduce_(
175 enum yac_test_abort_result result, MPI_Comm comm, int require_all) {
176
177 int mask = (int)result;
178 MPI_Allreduce(MPI_IN_PLACE, &mask, 1, MPI_INT, MPI_BOR, comm);
179
180 enum yac_test_abort_result reduced;
181 if (require_all) {
182 // anything but a unanimous outcome is a failure
183 switch (mask) {
185 reduced = YAC_TEST_ABORT_NO_CALL;
186 break;
188 reduced = YAC_TEST_ABORT_EXPECTED;
189 break;
190 default:
192 break;
193 }
194
195 } else {
196 // an unexpected message dominates, an expected abort wins over
197 // processes that did not abort at all
198 switch (mask) {
200 "unexpected mask value in yac_test_abort_reduce_");
202 reduced = YAC_TEST_ABORT_NO_CALL;
203 break;
206 reduced = YAC_TEST_ABORT_EXPECTED;
207 break;
214 break;
215 }
216 }
217
218 return reduced;
219}
220
222#define YAC_TEST_ABORT_RUN_(result_var, expected_substr, ...) \
223 do { \
224 /* a nested use is not supported because there is only a single \
225 jmp_buf per translation unit; the handler of the enclosing \
226 use has to be uninstalled first, otherwise die would longjmp \
227 into it instead of terminating */ \
228 if (yac_test_abort_state_.active_) { \
229 yac_restore_default_abort_handler(); \
230 die("nested use of YAC_TEST_EXPECT_ABORT_* is not supported");\
231 } \
232 /* volatile because the value is read after the return from \
233 longjmp */ \
234 yac_abort_func volatile yac_test_saved_handler_ = \
235 yac_get_abort_handler(); \
236 yac_test_abort_state_.substr_ = (expected_substr); \
237 yac_test_abort_state_.msg_[0] = '\0'; \
238 yac_test_abort_state_.active_ = 1; \
239 yac_set_abort_handler((yac_abort_func)utest_abort_handler_); \
240 (result_var) = YAC_TEST_ABORT_NO_CALL; \
241 switch (setjmp(yac_test_abort_state_.jmp)) { \
242 case 0: /* direct return from setjmp */\
243 __VA_ARGS__; \
244 break; \
245 case YAC_TEST_ABORT_EXPECTED: \
246 (result_var) = YAC_TEST_ABORT_EXPECTED; \
247 break; \
248 default: /* YAC_TEST_ABORT_UNEXPECTED */ \
249 (result_var) = YAC_TEST_ABORT_UNEXPECTED; \
250 break; \
251 } \
252 yac_set_abort_handler(yac_test_saved_handler_); \
253 yac_test_abort_state_.active_ = 0; \
254 } while (0)
255
256// ---- public macros ---------------------------------------------------------
257
286#define YAC_TEST_EXPECT_ABORT_SERIAL( \
287 test_failed, expected_result, expected_substr, ...) \
288 do { \
289 enum yac_test_abort_result yac_test_expected_ = (expected_result); \
290 if (utest_abort_skip_(yac_test_expected_)) { \
291 (test_failed) = 0; \
292 } else { \
293 /* volatile because the value is set between setjmp and longjmp \
294 and read afterwards */ \
295 enum yac_test_abort_result volatile yac_test_result_; \
296 YAC_TEST_ABORT_RUN_( \
297 yac_test_result_, expected_substr, __VA_ARGS__); \
298 (test_failed) = \
299 utest_abort_failed_( \
300 yac_test_result_, yac_test_expected_, __FILE__, __LINE__); \
301 } \
302 } while (0)
303
327#define YAC_TEST_EXPECT_ABORT_PAR_ANY( \
328 test_failed, expected_result, expected_substr, comm, ...) \
329 do { \
330 enum yac_test_abort_result yac_test_expected_ = (expected_result); \
331 if (utest_abort_skip_(yac_test_expected_)) { \
332 (test_failed) = 0; \
333 } else { \
334 /* volatile because the value is set between setjmp and longjmp \
335 and read afterwards */ \
336 enum yac_test_abort_result volatile yac_test_result_; \
337 YAC_TEST_ABORT_RUN_( \
338 yac_test_result_, expected_substr, __VA_ARGS__); \
339 (test_failed) = \
340 utest_abort_failed_( \
341 utest_abort_reduce_(yac_test_result_, (comm), 0), \
342 yac_test_expected_, __FILE__, __LINE__); \
343 } \
344 } while (0)
345
370#define YAC_TEST_EXPECT_ABORT_PAR_ALL( \
371 test_failed, expected_result, expected_substr, comm, ...) \
372 do { \
373 enum yac_test_abort_result yac_test_expected_ = (expected_result); \
374 if (utest_abort_skip_(yac_test_expected_)) { \
375 (test_failed) = 0; \
376 } else { \
377 /* volatile because the value is set between setjmp and longjmp \
378 and read afterwards */ \
379 enum yac_test_abort_result volatile yac_test_result_; \
380 YAC_TEST_ABORT_RUN_( \
381 yac_test_result_, expected_substr, __VA_ARGS__); \
382 (test_failed) = \
383 utest_abort_failed_( \
384 utest_abort_reduce_(yac_test_result_, (comm), 1), \
385 yac_test_expected_, __FILE__, __LINE__); \
386 } \
387 } while (0)
388
389#endif // TEST_EXPECT_ABORT_H
#define UNUSED(x)
Definition core.h:72
#define __attribute__(x)
Definition core.h:69
int active_
@ YAC_TEST_ABORT_MSG_LEN
char const * substr_
jmp_buf jmp
char msg_[YAC_TEST_ABORT_MSG_LEN]
yac_test_abort_result
@ YAC_TEST_ABORT_EXPECTED
abort was called with expected message
@ YAC_TEST_ABORT_UNEXPECTED
abort was called with unexpected message
@ YAC_TEST_ABORT_NO_CALL
abort handler was never invoked
static int mask[16]
#define YAC_UNREACHABLE_DEFAULT(msg)
Definition yac_assert.h:56