16 #include <mach/mach.h>
17 #include <mach-o/dyld.h>
18 #include <mach/mach_vm.h>
19 #include <sys/fsgetpath.h>
20 #include <mach-o/getsect.h>
21 #include <mach/vm_region.h>
22 #include <dispatch/private.h>
23 #include <dispatch/dispatch.h>
31 #include "execserverServer.h"
33 union catch_mach_exc_request_reply
{
34 union __RequestUnion__catch_mach_exc_subsystem request
;
35 union __ReplyUnion__catch_mach_exc_subsystem reply
;
39 #include "test_support.h"
41 extern const int NXArgc
;
42 extern const char** NXArgv
;
43 extern const char** environ
;
44 extern char* __progname
;
46 static const cpu_type_t currentArch
= CPU_TYPE_X86_64
;
48 static const cpu_type_t currentArch
= CPU_TYPE_I386
;
50 static const cpu_type_t currentArch
= CPU_TYPE_ARM64
;
52 static const cpu_type_t currentArch
= CPU_TYPE_ARM
;
57 ScopedLock() : _lock(OS_UNFAIR_LOCK_INIT
) {}
60 os_unfair_lock_lock(&_lock
);
62 os_unfair_lock_unlock(&_lock
);
68 template <typename T
, int QUANT
=4, int INIT
=1>
73 T
& operator[](size_t idx
) { assert(idx
< _usedCount
); return _elements
[idx
]; }
74 const T
& operator[](size_t idx
) const { assert(idx
< _usedCount
); return _elements
[idx
]; }
75 T
& back() { assert(_usedCount
> 0); return _elements
[_usedCount
-1]; }
76 uintptr_t count() const { return _usedCount
; }
77 uintptr_t maxCount() const { return _allocCount
; }
78 bool empty() const { return (_usedCount
== 0); }
79 uintptr_t index(const T
& element
) { return &element
- _elements
; }
80 void push_back(const T
& t
) { verifySpace(1); _elements
[_usedCount
++] = t
; }
81 void pop_back() { assert(_usedCount
> 0); _usedCount
--; }
82 T
* begin() { return &_elements
[0]; }
83 T
* end() { return &_elements
[_usedCount
]; }
84 const T
* begin() const { return &_elements
[0]; }
85 const T
* end() const { return &_elements
[_usedCount
]; }
86 bool contains(const T
& targ
) const { for (const T
& a
: *this) { if ( a
== targ
) return true; } return false; }
90 void growTo(uintptr_t n
);
91 void verifySpace(uintptr_t n
) { if (this->_usedCount
+n
> this->_allocCount
) growTo(this->_usedCount
+ n
); }
94 T
* _elements
= _initialAlloc
;
95 uintptr_t _allocCount
= INIT
;
96 uintptr_t _usedCount
= 0;
97 T _initialAlloc
[INIT
] = { };
101 template <typename T
, int QUANT
, int INIT
>
102 inline void GrowableArray
<T
,QUANT
,INIT
>::growTo(uintptr_t n
)
104 uintptr_t newCount
= (n
+ QUANT
- 1) & (-QUANT
);
105 T
* newArray
= (T
*)::malloc(sizeof(T
)*newCount
);
106 T
* oldArray
= this->_elements
;
107 if ( this->_usedCount
!= 0 )
108 ::memcpy(newArray
, oldArray
, sizeof(T
)*this->_usedCount
);
109 this->_elements
= newArray
;
110 this->_allocCount
= newCount
;
111 if ( oldArray
!= this->_initialAlloc
)
115 template <typename T
, int QUANT
, int INIT
>
116 inline void GrowableArray
<T
,QUANT
,INIT
>::erase(T
& targ
)
118 intptr_t index
= &targ
- _elements
;
120 assert(index
< (intptr_t)_usedCount
);
121 intptr_t moveCount
= _usedCount
-index
-1;
123 ::memcpy(&_elements
[index
], &_elements
[index
+1], moveCount
*sizeof(T
));
129 static TestState
* getState();
130 void _PASSV(const char* file
, unsigned line
, const char* format
, va_list args
) __attribute__ ((noreturn
));
131 void _FAILV(const char* file
, unsigned line
, const char* format
, va_list args
) __attribute__ ((noreturn
));
132 void _LOGV(const char* file
, unsigned line
, const char* format
, va_list args
);
133 GrowableArray
<std::pair
<mach_port_t
, _dyld_test_crash_handler_t
>>& getCrashHandlers();
143 void getLogsString(char** buffer
);
144 static uint8_t hexCharToUInt(const char hexByte
, uint8_t* value
);
145 static uint64_t hexToUInt64(const char* startHexByte
, const char** endHexByte
);
148 GrowableArray
<const char *> logs
;
149 const char *testName
;
154 GrowableArray
<std::pair
<mach_port_t
, _dyld_test_crash_handler_t
>> crashHandlers
;
157 // Okay, this is tricky. We need something with roughly he semantics of a weak def, but without using weak defs as their presence
158 // may impact certain tests. Instead we do the following:
160 // 1. Embed a stuct containing a lock and a pointer to our global state object in each binary
161 // 2. Once per binary we walk the entire image list looking for the first entry that also has state data
162 // 3. If it has state we lock its initializaion lock, and if it is not initialized we initialize it
163 // 4. We then copy the initalized pointer into our own state, and unlock the initializer lock
165 // This should work because the image list forms a stable ordering. The one loose end is if an executable is running where logging
166 // is only used in dylibs that are all being dlopned() and dlclosed. Since many dylibs cannot be dlclosed that should be a non-issue
170 __attribute__((section("__DATA,__dyld_test")))
171 static std::atomic
<TestState
*> sState
;
174 catch_mach_exception_raise(mach_port_t exception_port
,
177 exception_type_t exception
,
178 mach_exception_data_t code
,
179 mach_msg_type_number_t codeCnt
)
181 _dyld_test_crash_handler_t crashHandler
= NULL
;
182 for (const auto& handler
: TestState::getState()->getCrashHandlers()) {
183 if (handler
.first
== exception_port
) {
184 crashHandler
= handler
.second
;
188 if (exception
== EXC_CORPSE_NOTIFY
) {
198 catch_mach_exception_raise_state(mach_port_t exception_port
,
199 exception_type_t exception
,
200 const mach_exception_data_t code
,
201 mach_msg_type_number_t codeCnt
,
203 const thread_state_t old_state
,
204 mach_msg_type_number_t old_stateCnt
,
205 thread_state_t new_state
,
206 mach_msg_type_number_t
* new_stateCnt
)
208 return KERN_NOT_SUPPORTED
;
212 catch_mach_exception_raise_state_identity(mach_port_t exception_port
,
215 exception_type_t exception
,
216 mach_exception_data_t code
,
217 mach_msg_type_number_t codeCnt
,
219 thread_state_t old_state
,
220 mach_msg_type_number_t old_stateCnt
,
221 thread_state_t new_state
,
222 mach_msg_type_number_t
* new_stateCnt
)
224 return KERN_NOT_SUPPORTED
;
227 _process::_process() : executablePath(nullptr), args(nullptr), env(nullptr), stdoutHandler(nullptr), stderrHandler(nullptr),
228 crashHandler(nullptr), exitHandler(nullptr), pid(0), arch(currentArch
), suspended(false), async(false) {}
229 _process::~_process() {
230 if (stdoutHandler
) { Block_release(stdoutHandler
);}
231 if (stderrHandler
) { Block_release(stderrHandler
);}
232 if (crashHandler
) { Block_release(crashHandler
);}
233 if (exitHandler
) { Block_release(exitHandler
);}
236 void _process::set_executable_path(const char* EP
) { executablePath
= EP
; }
237 void _process::set_args(const char** A
) { args
= A
; }
238 void _process::set_env(const char** E
) { env
= E
; }
239 void _process::set_stdout_handler(_dyld_test_reader_t SOH
) { stdoutHandler
= Block_copy(SOH
); };
240 void _process::set_stderr_handler(_dyld_test_reader_t SEH
) { stderrHandler
= Block_copy(SEH
); }
241 void _process::set_exit_handler(_dyld_test_exit_handler_t EH
) { exitHandler
= Block_copy(EH
); }
242 void _process::set_crash_handler(_dyld_test_crash_handler_t CH
) { crashHandler
= Block_copy(CH
); }
243 void _process::set_launch_suspended(bool S
) { suspended
= S
; }
244 void _process::set_launch_async(bool S
) { async
= S
; }
245 void _process::set_launch_arch(cpu_type_t A
) { arch
= A
; }
247 pid_t
_process::launch() {
248 dispatch_queue_t queue
= dispatch_queue_create("com.apple.dyld.test.launch", NULL
);
249 dispatch_block_t oneShotSemaphoreBlock
= dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS
, ^{});
250 posix_spawn_file_actions_t fileActions
= NULL
;
251 posix_spawnattr_t attr
= NULL
;
252 dispatch_source_t stdoutSource
= NULL
;
253 dispatch_source_t stderrSource
= NULL
;
257 if (posix_spawn_file_actions_init(&fileActions
) != 0) {
258 FAIL("Setting up spawn filea actions");
260 if (posix_spawnattr_init(&attr
) != 0) { FAIL("Setting up spawn attr"); }
261 if (posix_spawnattr_setflags(&attr
, POSIX_SPAWN_START_SUSPENDED
) != 0) {
262 FAIL("Setting up spawn attr: POSIX_SPAWN_START_SUSPENDED");
265 if (pipe(stdoutPipe
) != 0) { FAIL("Setting up pipe"); }
266 if (posix_spawn_file_actions_addclose(&fileActions
, stdoutPipe
[0]) != 0) { FAIL("Setting up pipe"); }
267 if (posix_spawn_file_actions_adddup2(&fileActions
, stdoutPipe
[1], STDOUT_FILENO
) != 0) { FAIL("Setting up pipe"); }
268 if (posix_spawn_file_actions_addclose(&fileActions
, stdoutPipe
[1]) != 0) { FAIL("Setting up pipe"); }
269 fcntl((int)stdoutPipe
[0], F_SETFL
, O_NONBLOCK
);
270 stdoutSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, (uintptr_t)stdoutPipe
[0], 0, queue
);
271 dispatch_source_set_event_handler(stdoutSource
, ^{
272 int fd
= (int)dispatch_source_get_handle(stdoutSource
);
279 size
= read(fd
, &buffer
[0], 16384);
283 dispatch_source_set_cancel_handler(stdoutSource
, ^{
284 dispatch_release(stdoutSource
);
286 dispatch_resume(stdoutSource
);
288 if (pipe(stderrPipe
) != 0) { FAIL("Setting up pipe"); }
289 if (posix_spawn_file_actions_addclose(&fileActions
, stderrPipe
[0]) != 0) { FAIL("Setting up pipe"); }
290 if (posix_spawn_file_actions_adddup2(&fileActions
, stderrPipe
[1], STDERR_FILENO
) != 0) { FAIL("Setting up pipe"); }
291 if (posix_spawn_file_actions_addclose(&fileActions
, stderrPipe
[1]) != 0) { FAIL("Setting up pipe"); }
292 fcntl((int)stderrPipe
[0], F_SETFL
, O_NONBLOCK
);
293 stderrSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, (uintptr_t)stderrPipe
[0], 0, queue
);
294 dispatch_source_set_event_handler(stderrSource
, ^{
295 int fd
= (int)dispatch_source_get_handle(stderrSource
);
302 size
= read(fd
, &buffer
[0], 16384);
306 dispatch_source_set_cancel_handler(stderrSource
, ^{
307 dispatch_release(stderrSource
);
309 dispatch_resume(stderrSource
);
312 auto& crashHandlers
= TestState::getState()->getCrashHandlers();
313 mach_port_t exceptionPort
= MACH_PORT_NULL
;
314 mach_port_options_t options
= { .flags
= MPO_CONTEXT_AS_GUARD
| MPO_STRICT
| MPO_INSERT_SEND_RIGHT
, .mpl
= { 1 }};
315 if ( mach_port_construct(mach_task_self(), &options
, (mach_port_context_t
)exceptionPort
, &exceptionPort
) != KERN_SUCCESS
) {
316 FAIL("Could not construct port");
318 if (posix_spawnattr_setexceptionports_np(&attr
, EXC_MASK_CRASH
| EXC_MASK_CORPSE_NOTIFY
, exceptionPort
,
319 EXCEPTION_DEFAULT
| MACH_EXCEPTION_CODES
, 0) != 0) {
320 FAIL("posix_spawnattr_setexceptionports_np failed");
322 crashHandlers
.push_back(std::make_pair(exceptionPort
, crashHandler
));
323 dispatch_source_t crashSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, exceptionPort
, 0, queue
);
324 dispatch_source_set_event_handler(crashSource
, ^{
325 dispatch_mig_server(crashSource
, sizeof(union catch_mach_exc_request_reply
), ::mach_exc_server
);
327 dispatch_source_set_cancel_handler(crashSource
, ^{
328 mach_port_destruct(mach_task_self(), exceptionPort
, 0, (mach_port_context_t
)exceptionPort
);
330 dispatch_resume(crashSource
);
336 for (argc
= 0; args
[argc
] != NULL
; ++argc
) {}
339 const char *argv
[argc
+1];
340 argv
[0] = executablePath
;
341 for (uint32_t i
= 1; i
< argc
; ++i
) {
346 int result
= posix_spawn(&pid
, executablePath
, &fileActions
, &attr
, (char **)argv
, (char **)env
);
348 FAIL("posix_spawn(%s) failed, err=%d", executablePath
, result
);
350 dispatch_source_t exitSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (pid_t
)pid
,
351 DISPATCH_PROC_EXIT
, queue
);
352 dispatch_source_set_event_handler(exitSource
, ^{
354 exitHandler((pid_t
)dispatch_source_get_handle(exitSource
));
356 dispatch_source_cancel(exitSource
);
358 dispatch_source_cancel(stdoutSource
);
361 dispatch_source_cancel(stderrSource
);
363 oneShotSemaphoreBlock();
364 dispatch_source_cancel(exitSource
);
366 dispatch_resume(exitSource
);
369 close(stdoutPipe
[1]);
372 close(stderrPipe
[1]);
375 posix_spawn_file_actions_destroy(&fileActions
);
377 posix_spawnattr_destroy(&attr
);
382 dispatch_block_wait(oneShotSemaphoreBlock
, DISPATCH_TIME_FOREVER
);
384 Block_release(oneShotSemaphoreBlock
);
385 dispatch_release(queue
);
389 void *_process::operator new(size_t size
) {
393 void _process::operator delete(void *ptr
) {
397 // MARK: Private implementation details
401 void forEachEnvVar(const char* envp
[], F
&& f
) {
402 for (uint32_t i
= 0; envp
[i
] != nullptr; ++i
) {
403 const char* envBegin
= envp
[i
];
404 const char* envEnd
= strchr(envp
[i
], '=');
405 if (!envEnd
) { continue; }
406 size_t envSize
= (envEnd
-envBegin
)+1;
407 const char* valBegin
= envEnd
+1;
408 const char* valEnd
= strchr(envp
[i
], '\0');
409 if (!valEnd
) { continue; }
410 size_t valSize
= (valEnd
-valBegin
)+1;
413 strlcpy(&env
[0], envBegin
, envSize
);
414 strlcpy(&val
[0], valBegin
, valSize
);
419 uint8_t TestState::hexCharToUInt(const char hexByte
, uint8_t* value
) {
420 if (hexByte
>= '0' && hexByte
<= '9') {
421 *value
= hexByte
- '0';
423 } else if (hexByte
>= 'A' && hexByte
<= 'F') {
424 *value
= hexByte
- 'A' + 10;
426 } else if (hexByte
>= 'a' && hexByte
<= 'f') {
427 *value
= hexByte
- 'a' + 10;
434 uint64_t TestState::hexToUInt64(const char* startHexByte
, const char** endHexByte
) {
436 if (endHexByte
== NULL
) {
437 endHexByte
= &scratch
;
439 if (startHexByte
== NULL
)
442 if (startHexByte
[0] == '0' && startHexByte
[1] == 'x') {
445 *endHexByte
= startHexByte
+ 16;
448 for (uint32_t i
= 0; i
< 16; ++i
) {
450 if (!hexCharToUInt(startHexByte
[i
], &value
)) {
451 *endHexByte
= &startHexByte
[i
];
454 retval
= (retval
<< 4) + value
;
459 void TestState::getLogsString(char** buffer
)
462 if ( logs
.count() ) {
465 for (const auto& log
: logs
) {
466 size_t logSize
= strlen(log
);
467 bufSize
+= logSize
+ 2; // \t and \n
468 logBuf
= (char*)realloc(logBuf
, bufSize
);
469 strncpy(logBuf
+idx
, "\t", 1);
471 strncpy(logBuf
+idx
, log
, logSize
);
473 strncpy(logBuf
+idx
, "\n", 1);
476 logBuf
= (char*)realloc(logBuf
, bufSize
+ 1);
477 logBuf
[bufSize
] = '\0';
482 TestState::TestState() : testName(__progname
), logImmediate(false), logOnSuccess(false), checkForLeaks(false), output(Console
) {
483 forEachEnvVar(environ
, [this](const char* env
, const char* val
) {
484 if (strcmp(env
, "TEST_LOG_IMMEDIATE") == 0) {
487 if (strcmp(env
, "TEST_LOG_ON_SUCCESS") == 0) {
490 if (strcmp(env
, "MallocStackLogging") == 0) {
491 checkForLeaks
= true;
493 if (strcmp(env
, "TEST_OUTPUT") == 0) {
494 if (strcmp(val
, "BATS") == 0) {
496 } else if (strcmp(val
, "XCTest") == 0) {
501 if (output
== BATS
) {
504 printf(" MallocStackLogging=1 MallocDebugReport=none");
506 forEachEnvVar(environ
, [this](const char* env
, const char* val
) {
507 if ((strncmp(env
, "DYLD_", 5) == 0) || (strncmp(env
, "TEST_", 5) == 0)) {
508 printf(" %s=%s", env
, val
);
511 printf(" %s", testName
);
512 for (uint32_t i
= 1; i
< NXArgc
; ++i
) {
513 printf(" %s", NXArgv
[i
]);
519 GrowableArray
<std::pair
<mach_port_t
, _dyld_test_crash_handler_t
>>& TestState::getCrashHandlers() {
520 return crashHandlers
;
523 TestState
* TestState::getState() {
525 uint32_t imageCnt
= _dyld_image_count();
526 for (uint32_t i
= 0; i
< imageCnt
; ++i
) {
528 const struct mach_header_64
* mh
= (const struct mach_header_64
*)_dyld_get_image_header(i
);
530 const struct mach_header
* mh
= _dyld_get_image_header(i
);
532 if (mh
->filetype
!= MH_EXECUTE
) {
536 auto state
= (std::atomic
<TestState
*>*)getsectiondata(mh
, "__DATA", "__dyld_test", &size
);
537 // fprintf(stderr, "__dyld_test -> 0x%llx\n", state);
539 fprintf(stderr
, "Could not find test state in main executable TestState\n");
542 if (*state
== nullptr) {
543 void *temp
= malloc(sizeof(TestState
));
544 auto newState
= new (temp
) TestState();
545 TestState
* expected
= nullptr;
546 if(!state
->compare_exchange_strong(expected
, newState
)) {
547 newState
->~TestState();
551 sState
.store(*state
);
554 assert(sState
!= nullptr);
558 __attribute__((noreturn
))
559 void TestState::runLeaks(void) {
560 auto testState
= TestState::getState();
561 pid_t pid
= getpid();
563 sprintf(&pidString
[0], "%d", pid
);
565 printf("Insufficient priviledges, skipping Leak check: %s\n", testState
->testName
);
568 const char *args
[] = { pidString
, NULL
};
569 // We do this instead of using a dispatch_semaphore to prevent priority inversions
570 __block dispatch_data_t leaksOutput
= NULL
;
572 process
.set_executable_path("/usr/bin/leaks");
573 process
.set_args(args
);
574 process
.set_stdout_handler(^(int fd
) {
578 size
= read(fd
, &buffer
[0], 16384);
579 if (size
== -1) { break; }
580 dispatch_data_t data
= dispatch_data_create(&buffer
[0], size
, NULL
, DISPATCH_DATA_DESTRUCTOR_DEFAULT
);
584 leaksOutput
= dispatch_data_create_concat(leaksOutput
, data
);
588 process
.set_exit_handler(^(pid_t pid
) {
590 (void)waitpid(pid
, &status
, 0);
592 int exitStatus
= WEXITSTATUS(status
);
593 if (exitStatus
== 0) {
599 __unused dispatch_data_t map
= dispatch_data_create_map(leaksOutput
, &buffer
, &size
);
600 FAIL("Found Leaks:\n\n%s", buffer
);
605 testState
->checkForLeaks
= false;
606 (void)process
.launch();
610 void TestState::_PASSV(const char* file
, unsigned line
, const char* format
, va_list args
) {
611 if (output
== None
) {
617 _IOlock
.withLock([this,&format
,&args
,&file
,&line
](){
618 if (output
== Console
) {
619 printf("[\033[0;32mPASS\033[0m] %s: ", testName
);
620 vprintf(format
, args
);
622 if (logOnSuccess
&& logs
.count()) {
623 printf("[\033[0;33mLOG\033[0m]\n");
624 for (const auto& log
: logs
) {
625 printf("\t%s\n", log
);
628 } else if (output
== BATS
) {
629 printf("[PASS] %s: ", testName
);
630 vprintf(format
, args
);
632 if (logOnSuccess
&& logs
.count()) {
634 for (const auto& log
: logs
) {
635 printf("\t%s\n", log
);
638 } else if (output
== XCTest
) {
639 printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
640 printf("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
641 printf("<plist version=\"1.0\">");
643 printf("<key>PASS</key><true />");
645 char *logBuffer
= NULL
;
646 getLogsString(&logBuffer
);
647 if ( logBuffer
!= NULL
) {
648 printf("<key>LOGS</key><string>%s</string>", logBuffer
);
658 __builtin_unreachable();
661 void _PASS(const char* file
, unsigned line
, const char* format
, ...) {
663 va_start (args
, format
);
664 TestState::getState()->_PASSV(file
, line
, format
, args
);
668 void TestState::_FAILV(const char* file
, unsigned line
, const char* format
, va_list args
) {
669 if (output
== None
) {
672 _IOlock
.withLock([this,&format
,&args
,&file
,&line
](){
673 if (output
== Console
) {
674 printf("[\033[0;31mFAIL\033[0m] %s: ", testName
);
675 vprintf(format
, args
);
677 printf("[\033[0;33mLOG\033[0m]\n");
679 for (const auto& log
: logs
) {
680 printf("\t%s\n", log
);
683 } else if (output
== BATS
) {
684 printf("[FAIL] %s: ", testName
);
685 vprintf(format
, args
);
689 for (const auto& log
: logs
) {
690 printf("\t%s\n", log
);
693 } else if (output
== XCTest
) {
694 printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
695 printf("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
696 printf("<plist version=\"1.0\">");
698 printf("<key>PASS</key><false />");
699 printf("<key>FILE</key><string>%s</string>", file
);
700 printf("<key>LINE</key><integer>%u</integer>", line
);
702 vasprintf(&buffer
, format
, args
);
703 printf("<key>INFO</key><string>%s</string>", buffer
);
705 char *logBuffer
= NULL
;
706 getLogsString(&logBuffer
);
707 if ( logBuffer
!= NULL
) {
708 printf("<key>LOGS</key><string>%s</string>", logBuffer
);
716 __builtin_unreachable();
719 void _FAIL(const char* file
, unsigned line
, const char* format
, ...) {
721 va_start (args
, format
);
722 TestState::getState()->_FAILV(file
, line
, format
, args
);
726 void TestState::_LOGV(const char* file
, unsigned line
, const char* format
, va_list args
) {
727 _IOlock
.withLock([this,&format
,&args
](){
729 vprintf(format
, args
);
733 vasprintf(&str
, format
, args
);
739 void _LOG(const char* file
, unsigned line
, const char* format
, ...) {
741 va_start (args
, format
);
742 TestState::getState()->_LOGV(file
, line
, format
, args
);
746 void _TIMEOUT(const char* file
, unsigned line
, uint64_t seconds
) {
747 _LOG(file
, line
, "Registering %llu second test timeout", seconds
);
748 dispatch_source_t source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER
, 0, 0, DISPATCH_TARGET_QUEUE_DEFAULT
);
749 dispatch_time_t milestone
= dispatch_time(DISPATCH_WALLTIME_NOW
, seconds
* NSEC_PER_SEC
);
750 dispatch_source_set_timer(source
, milestone
, 0, 0);
751 dispatch_source_set_event_handler(source
, ^{
752 FAIL("Test timed out");
754 dispatch_resume(source
);