rippled
Loading...
Searching...
No Matches
antithesis_sdk.h
1#pragma once
2
3// This header file contains the Antithesis C++ SDK, which enables C++ applications to integrate with the [Antithesis platform].
4//
5// Documentation for the SDK is found at https://antithesis.com/docs/using_antithesis/sdk/cpp/overview/.
6
7#ifndef NO_ANTITHESIS_SDK
8
9#if __cplusplus < 202000L
10 #error "The Antithesis C++ API requires C++20 or higher"
11 #define NO_ANTITHESIS_SDK
12#endif
13
14#if !defined(__clang__)
15 #error "The Antithesis C++ API requires a clang compiler"
16 #define NO_ANTITHESIS_SDK
17#endif
18
19#if __clang_major__ < 16
20 #error "The Antithesis C++ API requires clang version 16 or higher"
21 #define NO_ANTITHESIS_SDK
22#endif
23
24#else
25
26#if __cplusplus < 201700L
27 #error "The Antithesis C++ API (with NO_ANTITHESIS_SDK) requires C++17 or higher"
28#endif
29
30#endif
31
32/*****************************************************************************
33 * COMMON
34 *****************************************************************************/
35
36#include <cstdint>
37#include <string>
38#include <map>
39#include <set>
40#include <variant>
41#include <vector>
42#include <utility>
43
44namespace antithesis {
45 inline const char* SDK_VERSION = "0.4.4";
46 inline const char* PROTOCOL_VERSION = "1.1.0";
47
48 struct JSON; struct JSONArray;
50
51 struct JSONArray : std::vector<JSONValue> {
52 using std::vector<JSONValue>::vector;
53
54 template<typename T, typename std::enable_if<std::is_convertible<T, JSONValue>::value, bool>::type = true>
55 JSONArray(std::vector<T> vals) : std::vector<JSONValue>(vals.begin(), vals.end()) {}
56 };
57
58 struct JSON : std::map<std::string, JSONValue> {
59 JSON() : std::map<std::string, JSONValue>() {}
61
63 for (auto& pair : more_args) {
64 (*this)[pair.first] = pair.second;
65 }
66 }
67 };
68}
69
70
71/*****************************************************************************
72 * INTERNAL HELPERS: LOCAL RANDOM
73 * Used in both the NO_ANTITHESIS_SDK version and when running locally
74 *****************************************************************************/
75
76#include <random>
77
79 struct LocalRandom {
83
85
86 uint64_t random() {
87#ifdef ANTITHESIS_RANDOM_OVERRIDE
88 return ANTITHESIS_RANDOM_OVERRIDE();
89#else
90 return distribution(gen);
91#endif
92 }
93 };
94}
95
96/*****************************************************************************
97 * INTERNAL HELPERS: JSON
98 *****************************************************************************/
99
100#ifndef NO_ANTITHESIS_SDK
101
102#include <array>
103#include <iomanip>
104
105namespace antithesis::internal::json {
106 template<class>
107 inline constexpr bool always_false_v = false;
108
109 static std::ostream& operator<<(std::ostream& out, const JSON& details);
110
111 static void escaped(std::ostream& out, const char c) {
112 const char HEX[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
113 switch (c) {
114 case '\t': out << "\\t"; break;
115 case '\b': out << "\\b"; break;
116 case '\n': out << "\\n"; break;
117 case '\f': out << "\\f"; break;
118 case '\r': out << "\\r"; break;
119 case '\"': out << "\\\""; break;
120 case '\\': out << "\\\\"; break;
121 default:
122 if ('\u0000' <= c && c <= '\u001F') {
123 out << "\\u00" << HEX[(c >> 4) & 0x0F] << HEX[c & 0x0F];
124 } else {
125 out << c;
126 }
127 }
128 }
129
130 static std::ostream& operator<<(std::ostream& out, const JSONValue& json) {
131 std::visit([&](auto&& arg) {
132 using T = std::decay_t<decltype(arg)>;
133 if constexpr (std::is_same_v<T, std::string>) {
134 out << '"';
135 for (auto c : arg) {
136 escaped(out, c);
137 }
138 out << '"';
139 } else if constexpr (std::is_same_v<T, bool>) {
140 out << (arg ? "true" : "false");
141 } else if constexpr (std::is_same_v<T, char>) {
142 out << '"';
143 escaped(out, arg);
144 out << '"';
145 } else if constexpr (std::is_same_v<T, int>) {
146 out << arg;
147 } else if constexpr (std::is_same_v<T, uint64_t>) {
148 out << arg;
149 } else if constexpr (std::is_same_v<T, float>) {
150 out << arg;
151 } else if constexpr (std::is_same_v<T, double>) {
152 out << arg;
153 } else if constexpr (std::is_same_v<T, const char*>) {
154 out << '"';
155 for (auto str = arg; *str != '\0'; str++) {
156 escaped(out, *str);
157 }
158 out << '"';
159 } else if constexpr (std::is_same_v<T, std::nullptr_t>) {
160 out << "null";
161 } else if constexpr (std::is_same_v<T, JSON>) {
162 out << arg;
163 } else if constexpr (std::is_same_v<T, JSONArray>) {
164 out << '[';
165 bool first = true;
166 for (auto &item : arg) {
167 if (!first) {
168 out << ',';
169 }
170 first = false;
171 out << item;
172 }
173 out << ']';
174 } else {
175 static_assert(always_false_v<T>, "non-exhaustive JSONValue visitor!");
176 }
177 }, json);
178
179 return out;
180 }
181
182 static std::ostream& operator<<(std::ostream& out, const JSON& details) {
183 out << '{';
184
185 bool first = true;
186 for (auto [key, value] : details) {
187 if (!first) {
188 out << ',';
189 }
190 out << '"';
191 for (auto c : key) {
192 escaped(out, c);
193 }
194 out << '"' << ':' << value;
195 first = false;
196 }
197
198 out << '}';
199 return out;
200 }
201}
202
203#endif
204
205/*****************************************************************************
206 * INTERNAL HELPERS: HANDLERS
207 * Implementations for running locally and running in Antithesis
208 *****************************************************************************/
209
210#ifndef NO_ANTITHESIS_SDK
211
212#include <cstdio>
213#include <iostream>
214#include <sstream>
215#include <dlfcn.h>
216#include <memory>
217#include <cstring>
218#include <cstdlib>
219#include <fcntl.h>
220#include <unistd.h>
221#include <sys/stat.h>
222
223
224namespace antithesis::internal::handlers {
225 constexpr const char* const ERROR_LOG_LINE_PREFIX = "[* antithesis-sdk-cpp *]";
226 constexpr const char* LIB_PATH = "/usr/lib/libvoidstar.so";
227 constexpr const char* LOCAL_OUTPUT_ENVIRONMENT_VARIABLE = "ANTITHESIS_SDK_LOCAL_OUTPUT";
228
229 using namespace antithesis::internal::json;
230
231 struct LibHandler {
232 virtual ~LibHandler() = default;
233 virtual void output(const char* message) const = 0;
234 virtual uint64_t random() = 0;
235
236 void output(const JSON& json) const {
238 out << json;
239 output(out.str().c_str());
240 }
241 };
242
243 struct AntithesisHandler : LibHandler {
244 void output(const char* message) const override {
245 if (message != nullptr) {
246 fuzz_json_data(message, strlen(message));
247 fuzz_flush();
248 }
249 }
250
251 uint64_t random() override {
252 return fuzz_get_random();
253 }
254
256 void* shared_lib = dlopen(LIB_PATH, RTLD_NOW);
257 if (!shared_lib) {
258 error("Can not load the Antithesis native library");
259 return nullptr;
260 }
261
262 void* fuzz_json_data = dlsym(shared_lib, "fuzz_json_data");
263 if (!fuzz_json_data) {
264 error("Can not access symbol fuzz_json_data");
265 return nullptr;
266 }
267
268 void* fuzz_flush = dlsym(shared_lib, "fuzz_flush");
269 if (!fuzz_flush) {
270 error("Can not access symbol fuzz_flush");
271 return nullptr;
272 }
273
274 void* fuzz_get_random = dlsym(shared_lib, "fuzz_get_random");
275 if (!fuzz_get_random) {
276 error("Can not access symbol fuzz_get_random");
277 return nullptr;
278 }
279
280 return std::unique_ptr<AntithesisHandler>(new AntithesisHandler(
281 reinterpret_cast<fuzz_json_data_t>(fuzz_json_data),
282 reinterpret_cast<fuzz_flush_t>(fuzz_flush),
283 reinterpret_cast<fuzz_get_random_t>(fuzz_get_random)));
284 }
285
286 private:
287 typedef void (*fuzz_json_data_t)( const char* message, size_t length );
288 typedef void (*fuzz_flush_t)();
289 typedef uint64_t (*fuzz_get_random_t)();
290
291
292 fuzz_json_data_t fuzz_json_data;
293 fuzz_flush_t fuzz_flush;
294 fuzz_get_random_t fuzz_get_random;
295
296 AntithesisHandler(fuzz_json_data_t fuzz_json_data, fuzz_flush_t fuzz_flush, fuzz_get_random_t fuzz_get_random) :
297 fuzz_json_data(fuzz_json_data), fuzz_flush(fuzz_flush), fuzz_get_random(fuzz_get_random) {}
298
299 static void error(const char* message) {
300 fprintf(stderr, "%s %s: %s\n", ERROR_LOG_LINE_PREFIX, message, dlerror());
301 }
302 };
303
304 struct LocalHandler : LibHandler{
305 ~LocalHandler() override {
306 if (file != nullptr) {
307 fclose(file);
308 }
309 }
310
311 void output(const char* message) const override {
312 if (file != nullptr && message != nullptr) {
313 fprintf(file, "%s\n", message);
314 }
315 }
316
317 uint64_t random() override {
318 return random_gen.random();
319 }
320
322 return std::unique_ptr<LocalHandler>(new LocalHandler(create_internal()));
323 }
324 private:
325 FILE* file;
327
328 LocalHandler(FILE* file): file(file), random_gen() {
329 }
330
331 // If `localOutputEnvVar` is set to a non-empty path, attempt to open that path and truncate the file
332 // to serve as the log file of the local handler.
333 // Otherwise, we don't have a log file, and logging is a no-op in the local handler.
334 static FILE* create_internal() {
335 const char* path = std::getenv(LOCAL_OUTPUT_ENVIRONMENT_VARIABLE);
336 if (!path || !path[0]) {
337 return nullptr;
338 }
339
340 // Open the file for writing (create if needed and possible) and truncate it
341 FILE* file = fopen(path, "w");
342 if (file == nullptr) {
343 fprintf(stderr, "%s Failed to open path %s: %s\n", ERROR_LOG_LINE_PREFIX, path, strerror(errno));
344 return nullptr;
345 }
346 int ret = fchmod(fileno(file), 0644);
347 if (ret != 0) {
348 fprintf(stderr, "%s Failed to set permissions for path %s: %s\n", ERROR_LOG_LINE_PREFIX, path, strerror(errno));
349 fclose(file);
350 return nullptr;
351 }
352
353 return file;
354 }
355 };
356
357 static std::unique_ptr<LibHandler> init() {
358 struct stat stat_buf;
359 if (stat(LIB_PATH, &stat_buf) == 0) {
360 std::unique_ptr<LibHandler> tmp = AntithesisHandler::create();
361 if (!tmp) {
362 fprintf(stderr, "%s Failed to create handler for Antithesis library\n", ERROR_LOG_LINE_PREFIX);
363 exit(-1);
364 }
365 return tmp;
366 } else {
367 return LocalHandler::create();
368 }
369 }
370
371 inline LibHandler& get_lib_handler() {
372 static LibHandler* lib_handler = nullptr;
373 if (lib_handler == nullptr) {
374 lib_handler = init().release(); // Leak on exit, rather than exit-time-destructor
375
376 JSON language_block{
377 {"name", "C++"},
378 {"version", __VERSION__}
379 };
380
381 JSON version_message{
382 {"antithesis_sdk", JSON{
383 {"language", language_block},
384 {"sdk_version", SDK_VERSION},
385 {"protocol_version", PROTOCOL_VERSION}
386 }
387 }};
388 lib_handler->output(version_message);
389 }
390
391 return *lib_handler;
392 }
393}
394
395#endif
396
397/*****************************************************************************
398 * INTERNAL HELPERS: Various classes related to assertions
399 *****************************************************************************/
400
401#ifndef NO_ANTITHESIS_SDK
402
403namespace antithesis::internal::assertions {
404 using namespace antithesis::internal::handlers;
405
406 struct AssertionState {
407 uint8_t false_not_seen : 1;
408 uint8_t true_not_seen : 1;
409 uint8_t rest : 6;
410
411 AssertionState() : false_not_seen(true), true_not_seen(true), rest(0) {}
412 };
413
414 enum AssertionType {
415 ALWAYS_ASSERTION,
416 ALWAYS_OR_UNREACHABLE_ASSERTION,
417 SOMETIMES_ASSERTION,
418 REACHABLE_ASSERTION,
419 UNREACHABLE_ASSERTION,
420 };
421
422 inline constexpr bool get_must_hit(AssertionType type) {
423 switch (type) {
424 case ALWAYS_ASSERTION:
425 case SOMETIMES_ASSERTION:
426 case REACHABLE_ASSERTION:
427 return true;
428 case ALWAYS_OR_UNREACHABLE_ASSERTION:
429 case UNREACHABLE_ASSERTION:
430 return false;
431 }
432 }
433
434 inline constexpr const char* get_assert_type_string(AssertionType type) {
435 switch (type) {
436 case ALWAYS_ASSERTION:
437 case ALWAYS_OR_UNREACHABLE_ASSERTION:
438 return "always";
439 case SOMETIMES_ASSERTION:
440 return "sometimes";
441 case REACHABLE_ASSERTION:
442 case UNREACHABLE_ASSERTION:
443 return "reachability";
444 }
445 }
446
447 inline constexpr const char* get_display_type_string(AssertionType type) {
448 switch (type) {
449 case ALWAYS_ASSERTION: return "Always";
450 case ALWAYS_OR_UNREACHABLE_ASSERTION: return "AlwaysOrUnreachable";
451 case SOMETIMES_ASSERTION: return "Sometimes";
452 case REACHABLE_ASSERTION: return "Reachable";
453 case UNREACHABLE_ASSERTION: return "Unreachable";
454 }
455 }
456
457 struct LocationInfo {
458 const char* class_name;
459 const char* function_name;
460 const char* file_name;
461 const int line;
462 const int column;
463
464 JSON to_json() const {
465 return JSON{
466 {"class", class_name},
467 {"function", function_name},
468 {"file", file_name},
469 {"begin_line", line},
470 {"begin_column", column},
471 };
472 }
473 };
474
475 inline std::string make_key([[maybe_unused]] const char* message, const LocationInfo& location_info) {
476 return message;
477 }
478
479 inline void assert_impl(bool cond, const char* message, const JSON& details, const LocationInfo& location_info,
480 bool hit, bool must_hit, const char* assert_type, const char* display_type, const char* id) {
481 JSON assertion{
482 {"antithesis_assert", JSON{
483 {"hit", hit},
484 {"must_hit", must_hit},
485 {"assert_type", assert_type},
486 {"display_type", display_type},
487 {"message", message},
488 {"condition", cond},
489 {"id", id},
490 {"location", location_info.to_json()},
491 {"details", details},
492 }}
493 };
494 antithesis::internal::handlers::get_lib_handler().output(assertion);
495 }
496
497 inline void assert_raw(bool cond, const char* message, const JSON& details,
498 const char* class_name, const char* function_name, const char* file_name, const int line, const int column,
499 bool hit, bool must_hit, const char* assert_type, const char* display_type, const char* id) {
500 LocationInfo location_info{ class_name, function_name, file_name, line, column };
501 assert_impl(cond, message, details, location_info, hit, must_hit, assert_type, display_type, id);
502 }
503
504 typedef std::set<std::string> CatalogEntryTracker;
505
506 inline CatalogEntryTracker& get_catalog_entry_tracker() {
507 static CatalogEntryTracker catalog_entry_tracker;
508 return catalog_entry_tracker;
509 }
510
511 struct Assertion {
512 AssertionState state;
513 AssertionType type;
514 const char* message;
515 LocationInfo location;
516
517 Assertion(const char* message, AssertionType type, LocationInfo&& location) :
518 state(), type(type), message(message), location(std::move(location)) {
519 this->add_to_catalog();
520 }
521
522 void add_to_catalog() const {
523 std::string id = make_key(message, location);
524 CatalogEntryTracker& tracker = get_catalog_entry_tracker();
525 if (!tracker.contains(id)) {
526 tracker.insert(id);
527 const bool condition = (type == REACHABLE_ASSERTION ? true : false);
528 const bool hit = false;
529 const char* assert_type = get_assert_type_string(type);
530 const bool must_hit = get_must_hit(type);
531 const char* display_type = get_display_type_string(type);
532 assert_impl(condition, message, {}, location, hit, must_hit, assert_type, display_type, id.c_str());
533 }
534 }
535
536 [[clang::always_inline]] inline void check_assertion(auto&& cond, const JSON& details)
537 requires requires { static_cast<bool>(std::forward<decltype(cond)>(cond)); } {
538 #if defined(NO_ANTITHESIS_SDK)
539 #error "Antithesis SDK has been disabled"
540 #endif
541 if (__builtin_expect(state.false_not_seen || state.true_not_seen, false)) {
542 check_assertion_internal(static_cast<bool>(std::forward<decltype(cond)>(cond)), details);
543 }
544 }
545
546 private:
547 void check_assertion_internal(bool cond, const JSON& details) {
548 bool emit = false;
549 if (!cond && state.false_not_seen) {
550 emit = true;
551 state.false_not_seen = false; // TODO: is the race OK?
552 }
553
554 if (cond && state.true_not_seen) {
555 emit = true;
556 state.true_not_seen = false; // TODO: is the race OK?
557 }
558
559 if (emit) {
560 const bool hit = true;
561 const char* assert_type = get_assert_type_string(type);
562 const bool must_hit = get_must_hit(type);
563 const char* display_type = get_display_type_string(type);
564 std::string id = make_key(message, location);
565 assert_impl(cond, message, details, location, hit, must_hit, assert_type, display_type, id.c_str());
566 }
567 }
568 };
569
570 enum GuidepostType {
571 GUIDEPOST_MAXIMIZE,
572 GUIDEPOST_MINIMIZE,
573 GUIDEPOST_EXPLORE,
574 GUIDEPOST_ALL,
575 GUIDEPOST_NONE
576 };
577
578 inline constexpr const char* get_guidance_type_string(GuidepostType type) {
579 switch (type) {
580 case GUIDEPOST_MAXIMIZE:
581 case GUIDEPOST_MINIMIZE:
582 return "numeric";
583 case GUIDEPOST_ALL:
584 case GUIDEPOST_NONE:
585 return "boolean";
586 case GUIDEPOST_EXPLORE:
587 return "json";
588 }
589 }
590
591 inline constexpr bool does_guidance_maximize(GuidepostType type) {
592 switch (type) {
593 case GUIDEPOST_MAXIMIZE:
594 case GUIDEPOST_ALL:
595 return true;
596 case GUIDEPOST_EXPLORE:
597 case GUIDEPOST_MINIMIZE:
598 case GUIDEPOST_NONE:
599 return false;
600 }
601 }
602
603 template <typename NumericValue, class Value=std::pair<NumericValue, NumericValue>>
604 struct NumericGuidepost {
605 const char* message;
606 LocationInfo location;
607 GuidepostType type;
608 // an approximation of (left - right) / 2; contains an absolute value and a sign bit
609 std::pair<NumericValue, bool> extreme_half_gap;
610
611 NumericGuidepost(const char* message, LocationInfo&& location, GuidepostType type) :
612 message(message), location(std::move(location)), type(type) {
613 this->add_to_catalog();
614 if (type == GUIDEPOST_MAXIMIZE) {
615 extreme_half_gap = { std::numeric_limits<NumericValue>::max(), false };
616 } else {
617 extreme_half_gap = { std::numeric_limits<NumericValue>::max(), true };
618 }
619 }
620
621 inline void add_to_catalog() {
622 std::string id = make_key(message, location);
623 JSON catalog{
624 {"antithesis_guidance", JSON{
625 {"guidance_type", get_guidance_type_string(type)},
626 {"message", message},
627 {"id", id},
628 {"location", location.to_json()},
629 {"maximize", does_guidance_maximize(type)},
630 {"hit", false}
631 }}
632 };
633 get_lib_handler().output(catalog);
634 }
635
636 std::pair<NumericValue, bool> compute_half_gap(NumericValue left, NumericValue right) {
637 // An extremely baroque way to compute (left - right) / 2, rounded toward 0, without overflowing or underflowing
639 // If both numbers are odd then the gap doesn't change if we subtract 1 from both sides
640 // Also subtracting 1 from both sides won't underflow
641 if (left % 2 == 1 && right % 2 == 1)
642 return compute_half_gap( left - 1, right - 1);
643 // If one number is odd then we subtract 1 from the larger number
644 // This rounds the computation toward 0 but again won't underflow
645 if (left % 2 == 1 || right % 2 == 1) {
646 if (left > right) {
647 return compute_half_gap( left - 1, right );
648 } else {
649 return compute_half_gap( left, right - 1 );
650 }
651 }
652 // At this point both numbers are even, so the midpoint calculation is exact
653 NumericValue half_left = left / 2;
654 NumericValue half_right = right / 2;
655 NumericValue midpoint = half_left + half_right;
656 // This won't overflow or underflow because we're subtracting the midpoint
657 // We compute a positive value and a sign so that we don't have to do weird things with unsigned types
658 if (left > right) {
659 return { midpoint - right, true };
660 } else {
661 return { right - midpoint, false };
662 }
663 } else {
664 // If it's floating point we don't need to worry about overflowing, just do the arithmetic
665 return { left > right ? (left - right) / 2 : (right - left) / 2, left > right };
666 }
667 }
668
669 bool should_send_value(std::pair<NumericValue, bool> half_gap) {
670 if (this->type == GUIDEPOST_MAXIMIZE) {
671 if (half_gap.second && !extreme_half_gap.second) {
672 // we're positive and the extreme value isn't; always send back
673 return true;
674 } else if (!half_gap.second && extreme_half_gap.second) {
675 // we're negative and the extreme value is positive; never send back
676 return false;
677 } else if (half_gap.second && extreme_half_gap.second) {
678 // both positive; send back if our absolute value is at least as large
679 return half_gap.first >= extreme_half_gap.first;
680 } else {
681 // both negative; send back if our absolute value is at least as small
682 return half_gap.first <= extreme_half_gap.first;
683 }
684 } else {
685 if (half_gap.second && !extreme_half_gap.second) {
686 // we're positive and the extreme value isn't; never send back
687 return false;
688 } else if (!half_gap.second && extreme_half_gap.second) {
689 // we're negative and the extreme value is positive; always send back
690 return true;
691 } else if (half_gap.second && extreme_half_gap.second) {
692 // both positive; send back if our absolute value is at least as small
693 return half_gap.first <= extreme_half_gap.first;
694 } else {
695 // both negative; send back if our absolute value is at least as large
696 return half_gap.first >= extreme_half_gap.first;
697 }
698 }
699 }
700
701 [[clang::always_inline]] inline void send_guidance(Value value) {
702 std::pair<NumericValue, bool> half_gap = compute_half_gap(value.first, value.second);
703 if (should_send_value(half_gap)) {
704 extreme_half_gap = half_gap;
705 std::string id = make_key(this->message, this->location);
706 JSON guidance{
707 {"antithesis_guidance", JSON{
708 {"guidance_type", get_guidance_type_string(this->type)},
709 {"message", this->message},
710 {"id", id},
711 {"location", this->location.to_json()},
712 {"maximize", does_guidance_maximize(this->type)},
713 {"guidance_data", JSON{
714 { "left", value.first },
715 { "right", value.second } }},
716 {"hit", true}
717 }}
718 };
719 get_lib_handler().output(guidance);
720 }
721 }
722 };
723
724 template <typename GuidanceType>
725 struct BooleanGuidepost {
726 const char* message;
727 LocationInfo location;
728 GuidepostType type;
729
730 BooleanGuidepost(const char* message, LocationInfo&& location, GuidepostType type) :
731 message(message), location(std::move(location)), type(type) {
732 this->add_to_catalog();
733 }
734
735 inline void add_to_catalog() {
736 std::string id = make_key(message, location);
737 JSON catalog{
738 {"antithesis_guidance", JSON{
739 {"guidance_type", get_guidance_type_string(type)},
740 {"message", message},
741 {"id", id},
742 {"location", location.to_json()},
743 {"maximize", does_guidance_maximize(type)},
744 {"hit", false}
745 }}
746 };
747 get_lib_handler().output(catalog);
748 }
749
750 inline virtual void send_guidance(GuidanceType data) {
751 std::string id = make_key(this->message, this->location);
752 JSON guidance{
753 {"antithesis_guidance", JSON{
754 {"guidance_type", get_guidance_type_string(this->type)},
755 {"message", this->message},
756 {"id", id},
757 {"location", location.to_json()},
758 {"maximize", does_guidance_maximize(this->type)},
759 {"guidance_data", data},
760 {"hit", true}
761 }}
762 };
763 get_lib_handler().output(guidance);
764 }
765 };
766}
767
768namespace antithesis::internal {
769namespace { // Anonymous namespace which is translation-unit-specific; certain symbols aren't exposed in the symbol table as a result
770 template <std::size_t N>
771 struct fixed_string {
772 std::array<char, N> contents;
773 constexpr fixed_string() {
774 for(unsigned int i=0; i<N; i++) contents[i] = 0;
775 }
776
777 #pragma clang diagnostic push
778 #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
779 constexpr fixed_string( const char (&arr)[N] )
780 {
781 for(unsigned int i=0; i<N; i++) contents[i] = arr[i];
782 }
783
784 static constexpr fixed_string<N> from_c_str( const char* s ) {
785 fixed_string<N> it;
786 for(unsigned int i=0; i<N && s[i]; i++)
787 it.contents[i] = s[i];
788 return it;
789 }
790 #pragma clang diagnostic pop
791
792 const char* c_str() const { return contents.data(); }
793 };
794
795 template <std::size_t N>
796 fixed_string( const char (&arr)[N] ) -> fixed_string<N>;
797
798 #pragma clang diagnostic push
799 #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
800 static constexpr size_t string_length( const char * s ) {
801 for(int l = 0; ; l++)
802 if (!s[l])
803 return l;
804 }
805 #pragma clang diagnostic pop
806
807 template <antithesis::internal::assertions::AssertionType type, fixed_string message, fixed_string file_name, fixed_string function_name, int line, int column>
808 struct CatalogEntry {
809 [[clang::always_inline]] static inline antithesis::internal::assertions::Assertion create() {
810 antithesis::internal::assertions::LocationInfo location{ "", function_name.c_str(), file_name.c_str(), line, column };
811 return antithesis::internal::assertions::Assertion(message.c_str(), type, std::move(location));
812 }
813
814 static inline antithesis::internal::assertions::Assertion assertion = create();
815 };
816
817 template<typename GuidanceDataType, antithesis::internal::assertions::GuidepostType type, fixed_string message, fixed_string file_name, fixed_string function_name, int line, int column>
818 struct BooleanGuidanceCatalogEntry {
819 [[clang::always_inline]] static inline antithesis::internal::assertions::BooleanGuidepost<GuidanceDataType> create() {
820 antithesis::internal::assertions::LocationInfo location{ "", function_name.c_str(), file_name.c_str(), line, column };
821 switch (type) {
822 case antithesis::internal::assertions::GUIDEPOST_ALL:
823 case antithesis::internal::assertions::GUIDEPOST_NONE:
824 return antithesis::internal::assertions::BooleanGuidepost<GuidanceDataType>(message.c_str(), std::move(location), type);
825 default:
826 throw std::runtime_error("Can't create boolean guidepost with non-boolean type");
827 }
828 }
829
830 static inline antithesis::internal::assertions::BooleanGuidepost<GuidanceDataType> guidepost = create();
831 };
832
833 template<typename NumericType, antithesis::internal::assertions::GuidepostType type, fixed_string message, fixed_string file_name, fixed_string function_name, int line, int column>
834 struct NumericGuidanceCatalogEntry {
835 [[clang::always_inline]] static inline antithesis::internal::assertions::NumericGuidepost<NumericType> create() {
836 antithesis::internal::assertions::LocationInfo location{ "", function_name.c_str(), file_name.c_str(), line, column };
837 switch (type) {
838 case antithesis::internal::assertions::GUIDEPOST_MAXIMIZE:
839 case antithesis::internal::assertions::GUIDEPOST_MINIMIZE:
840 return antithesis::internal::assertions::NumericGuidepost<NumericType>(message.c_str(), std::move(location), type);
841 default:
842 throw std::runtime_error("Can't create numeric guidepost with non-numeric type");
843 }
844 }
845
846 static inline antithesis::internal::assertions::NumericGuidepost<NumericType> guidepost = create();
847 };
848}
849}
850
851#endif
852
853/*****************************************************************************
854 * PUBLIC SDK: ASSERTIONS
855 *****************************************************************************/
856
857#define _NL_1(foo) { #foo, foo }
858#define _NL_2(foo, ...) { #foo, foo }, _NL_1(__VA_ARGS__)
859#define _NL_3(foo, ...) { #foo, foo }, _NL_2(__VA_ARGS__)
860#define _NL_4(foo, ...) { #foo, foo }, _NL_3(__VA_ARGS__)
861#define _NL_5(foo, ...) { #foo, foo }, _NL_4(__VA_ARGS__)
862#define _NL_6(foo, ...) { #foo, foo }, _NL_5(__VA_ARGS__)
863#define _NL_7(foo, ...) { #foo, foo }, _NL_6(__VA_ARGS__)
864#define _NL_8(foo, ...) { #foo, foo }, _NL_7(__VA_ARGS__)
865#define _NL_9(foo, ...) { #foo, foo }, _NL_8(__VA_ARGS__)
866#define _NL_10(foo, ...) { #foo, foo }, _NL_9(__VA_ARGS__)
867
868#define _ELEVENTH_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
869
870#define _GET_NL(...) \
871 _ELEVENTH_ARG(__VA_ARGS__, _NL_10, _NL_9, _NL_8, _NL_7, _NL_6, _NL_5, _NL_4, _NL_3, _NL_2, _NL_1)
872
873#define NAMED_LIST(...) { _GET_NL(__VA_ARGS__)(__VA_ARGS__) }
874
875#ifdef NO_ANTITHESIS_SDK
876
877#ifndef ANTITHESIS_SDK_ALWAYS_POLYFILL
878 #define ANTITHESIS_SDK_ALWAYS_POLYFILL(...)
879#endif
880
881#ifndef ANTITHESIS_SDK_SOMETIMES_POLYFILL
882 #define ANTITHESIS_SDK_SOMETIMES_POLYFILL(...)
883#endif
884
885#ifndef ANTITHESIS_SDK_ALWAYS_OR_UNREACHABLE_POLYFILL
886 #define ANTITHESIS_SDK_ALWAYS_OR_UNREACHABLE_POLYFILL(...) \
887 ANTITHESIS_SDK_ALWAYS_POLYFILL(__VA_ARGS__)
888#endif
889
890#define ALWAYS(cond, message, ...) \
891 ANTITHESIS_SDK_ALWAYS_POLYFILL(cond, message, __VA_ARGS__)
892#define ALWAYS_OR_UNREACHABLE(cond, message, ...) \
893 ANTITHESIS_SDK_ALWAYS_OR_UNREACHABLE_POLYFILL(cond, message, __VA_ARGS__)
894#define SOMETIMES(cond, message, ...) \
895 ANTITHESIS_SDK_SOMETIMES_POLYFILL(cond, message, __VA_ARGS__)
896#define REACHABLE(message, ...) \
897 ANTITHESIS_SDK_SOMETIMES_POLYFILL(true, message, __VA_ARGS__)
898#define UNREACHABLE(message, ...) \
899 ANTITHESIS_SDK_ALWAYS_POLYFILL(false, message, __VA_ARGS__)
900#define ALWAYS_GREATER_THAN(val, threshold, message, ...) \
901 ANTITHESIS_SDK_ALWAYS_POLYFILL((val > threshold), message, __VA_ARGS__)
902#define ALWAYS_GREATER_THAN_OR_EQUAL_TO(val, threshold, message, ...) \
903 ANTITHESIS_SDK_ALWAYS_POLYFILL((val >= threshold), message, __VA_ARGS__)
904#define SOMETIMES_GREATER_THAN(val, threshold, message, ...) \
905 ANTITHESIS_SDK_SOMETIMES_POLYFILL((val > threshold), message, __VA_ARGS__)
906#define SOMETIMES_GREATER_THAN_OR_EQUAL_TO(val, threshold, message, ...) \
907 ANTITHESIS_SDK_SOMETIMES_POLYFILL((val >= threshold), message, __VA_ARGS__)
908#define ALWAYS_LESS_THAN(val, threshold, message, ...) \
909 ANTITHESIS_SDK_ALWAYS_POLYFILL((val < threshold), message, __VA_ARGS__)
910#define ALWAYS_LESS_THAN_OR_EQUAL_TO(val, threshold, message, ...) \
911 ANTITHESIS_SDK_ALWAYS_POLYFILL((val <= threshold), message, __VA_ARGS__)
912#define SOMETIMES_LESS_THAN(val, threshold, message, ...) \
913 ANTITHESIS_SDK_SOMETIMES_POLYFILL((val < threshold), message, __VA_ARGS__)
914#define SOMETIMES_LESS_THAN_OR_EQUAL_TO(val, threshold, message, ...) \
915 ANTITHESIS_SDK_SOMETIMES_POLYFILL((val <= threshold), message, __VA_ARGS__)
916#define ALWAYS_SOME(pairs, message, ...) \
917 ANTITHESIS_SDK_ALWAYS_POLYFILL(([&](){ \
918 std::initializer_list<std::pair<std::string, bool>> ps = pairs; \
919 for (auto const& pair : ps) \
920 if (pair.second) return true; \
921 return false; }()), message, __VA_ARGS__)
922#define SOMETIMES_ALL(pairs, message, ...) \
923 ANTITHESIS_SDK_SOMETIMES_POLYFILL(([&](){ \
924 std::initializer_list<std::pair<std::string, bool>> ps = pairs; \
925 for (auto const& pair : ps) \
926 if (!pair.second) return false; \
927 return true; }()), message, __VA_ARGS__)
928
929#else
930
931#include <source_location>
932
933#define FIXED_STRING_FROM_C_STR(s) (antithesis::internal::fixed_string<antithesis::internal::string_length(s)+1>::from_c_str(s))
934
935#define ANTITHESIS_ASSERT_RAW(type, cond, message, ...) ( \
936 antithesis::internal::CatalogEntry< \
937 type, \
938 antithesis::internal::fixed_string(message), \
939 FIXED_STRING_FROM_C_STR(std::source_location::current().file_name()), \
940 FIXED_STRING_FROM_C_STR(std::source_location::current().function_name()), \
941 std::source_location::current().line(), \
942 std::source_location::current().column() \
943 >::assertion.check_assertion(cond, (antithesis::JSON(__VA_ARGS__)) ) )
944
945#define ALWAYS(cond, message, ...) ANTITHESIS_ASSERT_RAW(antithesis::internal::assertions::ALWAYS_ASSERTION, cond, message, __VA_ARGS__)
946#define ALWAYS_OR_UNREACHABLE(cond, message, ...) ANTITHESIS_ASSERT_RAW(antithesis::internal::assertions::ALWAYS_OR_UNREACHABLE_ASSERTION, cond, message, __VA_ARGS__)
947#define SOMETIMES(cond, message, ...) ANTITHESIS_ASSERT_RAW(antithesis::internal::assertions::SOMETIMES_ASSERTION, cond, message, __VA_ARGS__)
948#define REACHABLE(message, ...) ANTITHESIS_ASSERT_RAW(antithesis::internal::assertions::REACHABLE_ASSERTION, true, message, __VA_ARGS__)
949#define UNREACHABLE(message, ...) ANTITHESIS_ASSERT_RAW(antithesis::internal::assertions::UNREACHABLE_ASSERTION, false, message, __VA_ARGS__)
950
951#define ANTITHESIS_NUMERIC_ASSERT_RAW(name, assertion_type, guidepost_type, left, cmp, right, message, ...) \
952do { \
953 static_assert(std::is_same_v<decltype(left), decltype(right)>, "Values compared in " #name " must be of same type"); \
954 ANTITHESIS_ASSERT_RAW(assertion_type, left cmp right, message, __VA_ARGS__ __VA_OPT__(,) {{ "left", left }, { "right", right }} ); \
955 antithesis::internal::NumericGuidanceCatalogEntry< \
956 decltype(left), \
957 guidepost_type, \
958 antithesis::internal::fixed_string(message), \
959 FIXED_STRING_FROM_C_STR(std::source_location::current().file_name()), \
960 FIXED_STRING_FROM_C_STR(std::source_location::current().function_name()), \
961 std::source_location::current().line(), \
962 std::source_location::current().column() \
963 >::guidepost.send_guidance({ left, right }); \
964} while (0)
965
966#define ALWAYS_GREATER_THAN(left, right, message, ...) \
967ANTITHESIS_NUMERIC_ASSERT_RAW(ALWAYS_GREATER_THAN, antithesis::internal::assertions::ALWAYS_ASSERTION, antithesis::internal::assertions::GUIDEPOST_MINIMIZE, left, >, right, message, __VA_ARGS__)
968#define ALWAYS_GREATER_THAN_OR_EQUAL_TO(left, right, message, ...) \
969ANTITHESIS_NUMERIC_ASSERT_RAW(ALWAYS_GREATER_THAN_OR_EQUAL_TO, antithesis::internal::assertions::ALWAYS_ASSERTION, antithesis::internal::assertions::GUIDEPOST_MINIMIZE, left, >=, right, message, __VA_ARGS__)
970#define SOMETIMES_GREATER_THAN(left, right, message, ...) \
971ANTITHESIS_NUMERIC_ASSERT_RAW(SOMETIMES_GREATER_THAN, antithesis::internal::assertions::SOMETIMES_ASSERTION, antithesis::internal::assertions::GUIDEPOST_MAXIMIZE, left, >, right, message, __VA_ARGS__)
972#define SOMETIMES_GREATER_THAN_OR_EQUAL_TO(left, right, message, ...) \
973ANTITHESIS_NUMERIC_ASSERT_RAW(SOMETIMES_GREATER_THAN_OR_EQUAL_TO, antithesis::internal::assertions::SOMETIMES_ASSERTION, antithesis::internal::assertions::GUIDEPOST_MAXIMIZE, left, >=, right, message, __VA_ARGS__)
974#define ALWAYS_LESS_THAN(left, right, message, ...) \
975ANTITHESIS_NUMERIC_ASSERT_RAW(ALWAYS_LESS_THAN, antithesis::internal::assertions::ALWAYS_ASSERTION, antithesis::internal::assertions::GUIDEPOST_MAXIMIZE, left, <, right, message, __VA_ARGS__)
976#define ALWAYS_LESS_THAN_OR_EQUAL_TO(left, right, message, ...) \
977ANTITHESIS_NUMERIC_ASSERT_RAW(ALWAYS_LESS_THAN_OR_EQUAL_TO, antithesis::internal::assertions::ALWAYS_ASSERTION, antithesis::internal::assertions::GUIDEPOST_MAXIMIZE, left, <=, right, message, __VA_ARGS__)
978#define SOMETIMES_LESS_THAN(left, right, message, ...) \
979ANTITHESIS_NUMERIC_ASSERT_RAW(SOMETIMES_LESS_THAN, antithesis::internal::assertions::SOMETIMES_ASSERTION, antithesis::internal::assertions::GUIDEPOST_MINIMIZE, left, <, right, message, __VA_ARGS__)
980#define SOMETIMES_LESS_THAN_OR_EQUAL_TO(left, right, message, ...) \
981ANTITHESIS_NUMERIC_ASSERT_RAW(SOMETIMES_LESS_THAN_OR_EQUAL_TO, antithesis::internal::assertions::SOMETIMES_ASSERTION, antithesis::internal::assertions::GUIDEPOST_MINIMIZE, left, <=, right, message, __VA_ARGS__)
982
983#define ALWAYS_SOME(pairs, message, ...) \
984do { \
985 bool disjunction = false; \
986 std::vector<std::pair<std::string, bool>> vec_pairs = pairs; \
987 for (std::pair<std::string, bool> pair : vec_pairs) { \
988 if (pair.second) { \
989 disjunction = true; \
990 break; \
991 } \
992 } \
993 ANTITHESIS_ASSERT_RAW(antithesis::internal::assertions::ALWAYS_ASSERTION, disjunction, message, __VA_ARGS__ __VA_OPT__(,) pairs); \
994 antithesis::JSON json_pairs = antithesis::JSON(pairs); \
995 antithesis::internal::BooleanGuidanceCatalogEntry< \
996 decltype(json_pairs), \
997 antithesis::internal::assertions::GUIDEPOST_NONE, \
998 antithesis::internal::fixed_string(message), \
999 FIXED_STRING_FROM_C_STR(std::source_location::current().file_name()), \
1000 FIXED_STRING_FROM_C_STR(std::source_location::current().function_name()), \
1001 std::source_location::current().line(), \
1002 std::source_location::current().column() \
1003 >::guidepost.send_guidance(json_pairs); \
1004} while (0)
1005
1006#define SOMETIMES_ALL(pairs, message, ...) \
1007do { \
1008 bool conjunction = true; \
1009 std::vector<std::pair<std::string, bool>> vec_pairs = pairs; \
1010 for (std::pair<std::string, bool> pair : vec_pairs) { \
1011 if (!pair.second) { \
1012 conjunction = false; \
1013 break; \
1014 } \
1015 } \
1016 ANTITHESIS_ASSERT_RAW(antithesis::internal::assertions::SOMETIMES_ASSERTION, conjunction, message, __VA_ARGS__ __VA_OPT__(,) pairs); \
1017 antithesis::JSON json_pairs = antithesis::JSON(pairs); \
1018 antithesis::internal::BooleanGuidanceCatalogEntry< \
1019 decltype(json_pairs), \
1020 antithesis::internal::assertions::GUIDEPOST_ALL, \
1021 antithesis::internal::fixed_string(message), \
1022 FIXED_STRING_FROM_C_STR(std::source_location::current().file_name()), \
1023 FIXED_STRING_FROM_C_STR(std::source_location::current().function_name()), \
1024 std::source_location::current().line(), \
1025 std::source_location::current().column() \
1026 >::guidepost.send_guidance(json_pairs); \
1027} while (0)
1028
1029#endif
1030
1031/*****************************************************************************
1032 * PUBLIC SDK: LIFECYCLE
1033 *****************************************************************************/
1034
1035#ifdef NO_ANTITHESIS_SDK
1036
1037namespace antithesis {
1038 inline void setup_complete(const JSON& details) {
1039 }
1040
1041 inline void send_event(const char* name, const JSON& details) {
1042 }
1043}
1044
1045#else
1046
1047namespace antithesis {
1048 inline void setup_complete(const JSON& details) {
1049 JSON json{
1050 { "antithesis_setup", JSON{
1051 {"status", "complete"},
1052 {"details", details}
1053 }}
1054 };
1055 antithesis::internal::handlers::get_lib_handler().output(json);
1056 }
1057
1058 inline void send_event(const char* name, const JSON& details) {
1059 JSON json = { { name, details } };
1060 antithesis::internal::handlers::get_lib_handler().output(json);
1061 }
1062}
1063#endif
1064
1065/*****************************************************************************
1066 * PUBLIC SDK: RANDOM
1067 *****************************************************************************/
1068
1069namespace antithesis {
1070 // Declarations that we expose
1071 uint64_t get_random();
1072}
1073
1074#ifdef NO_ANTITHESIS_SDK
1075
1076namespace antithesis {
1077 inline uint64_t get_random() {
1079 return random_gen.random();
1080 }
1081}
1082
1083#else
1084
1085namespace antithesis {
1086 inline uint64_t get_random() {
1087 return antithesis::internal::handlers::get_lib_handler().random();
1088 }
1089}
1090
1091#endif
1092
1093namespace antithesis {
1094 template <typename Iter>
1095 Iter random_choice(Iter begin, Iter end) {
1096 ssize_t num_things = end - begin;
1097 if (num_things == 0) {
1098 return end;
1099 }
1100
1101 uint64_t uval = get_random();
1102 ssize_t index = uval % num_things;
1103 return begin + index;
1104 }
1105}
JSONValue begin(JSONValue ... args)
JSONValue end(JSONValue ... args)
T exit(T... args)
T fclose(T... args)
T fopen(T... args)
T forward(T... args)
T fprintf(T... args)
T getenv(T... args)
T is_same_v
T left(T... args)
T max(T... args)
T midpoint(T... args)
T move(T... args)
Value to_json(ripple::Number const &number)
Definition json_value.h:444
void setup_complete(const JSON &details)
std::variant< JSON, std::nullptr_t, std::string, bool, char, int, uint64_t, float, double, const char *, JSONArray > JSONValue
uint64_t get_random()
const char * PROTOCOL_VERSION
Iter random_choice(Iter begin, Iter end)
const char * SDK_VERSION
void send_event(const char *name, const JSON &details)
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:244
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:32
auto const data
General field definitions, or fields used in multiple transaction namespaces.
std::ostream & operator<<(std::ostream &out, base_uint< Bits, Tag > const &u)
Definition base_uint.h:647
STL namespace.
T strerror(T... args)
T strlen(T... args)
JSONArray(std::vector< T > vals)
JSON(std::initializer_list< std::pair< const std::string, JSONValue > > args)
JSON(std::initializer_list< std::pair< const std::string, JSONValue > > args, std::vector< std::pair< const std::string, JSONValue > > more_args)
std::uniform_int_distribution< unsigned long long > distribution
T visit(T... args)