// test.h // Common definitions for trivial test harness #ifndef TEST_H #define TEST_H #include #include #include #include #include #include #include #include #if __cplusplus #include using namespace std; #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() # include #endif #include "../runtime/isa.h" #if __cplusplus # define EXTERN_C extern "C" #else # define EXTERN_C /*empty*/ #endif // Test output static inline void succeed(const char *name) __attribute__((noreturn)); static inline void succeed(const char *name) { if (name) { char path[MAXPATHLEN+1]; strcpy(path, name); fprintf(stderr, "OK: %s\n", basename(path)); } else { fprintf(stderr, "OK\n"); } exit(0); } static inline void fail(const char *msg, ...) __attribute__((noreturn)); static inline void fail(const char *msg, ...) { if (msg) { char *msg2; asprintf(&msg2, "BAD: %s\n", msg); va_list v; va_start(v, msg); vfprintf(stderr, msg2, v); va_end(v); free(msg2); } else { fprintf(stderr, "BAD\n"); } exit(1); } #define testassert(cond) \ ((void) (((cond) != 0) ? (void)0 : __testassert(#cond, __FILE__, __LINE__))) #define __testassert(cond, file, line) \ (fail("failed assertion '%s' at %s:%u", cond, __FILE__, __LINE__)) /* time-sensitive assertion, disabled under valgrind */ #define timecheck(name, time, fast, slow) \ if (getenv("VALGRIND") && 0 != strcmp(getenv("VALGRIND"), "NO")) { \ /* valgrind; do nothing */ \ } else if (time > slow) { \ fprintf(stderr, "SLOW: %s %llu, expected %llu..%llu\n", \ name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ } else if (time < fast) { \ fprintf(stderr, "FAST: %s %llu, expected %llu..%llu\n", \ name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ } else { \ testprintf("time: %s %llu, expected %llu..%llu\n", \ name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ } // Return true if testprintf() output is enabled. static inline bool testverbose(void) { static int verbose = -1; if (verbose < 0) verbose = atoi(getenv("VERBOSE") ?: "0"); // VERBOSE=1 prints test harness info only // VERBOSE=2 prints test info return verbose >= 2; } // Print debugging info when VERBOSE=2 is set, // without disturbing the test's expected output. static inline void testprintf(const char *msg, ...) { if (msg && testverbose()) { char *msg2; asprintf(&msg2, "VERBOSE: %s", msg); va_list v; va_start(v, msg); vfprintf(stderr, msg2, v); va_end(v); free(msg2); } } // complain to output, but don't fail the test // Use when warning that some test is being temporarily skipped // because of something like a compiler bug. static inline void testwarn(const char *msg, ...) { if (msg) { char *msg2; asprintf(&msg2, "WARN: %s\n", msg); va_list v; va_start(v, msg); vfprintf(stderr, msg2, v); va_end(v); free(msg2); } } static inline void testnoop() { } // Prevent deprecation warnings from some runtime functions. static inline void test_objc_flush_caches(Class cls) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" _objc_flush_caches(cls); #pragma clang diagnostic pop } #define _objc_flush_caches(c) test_objc_flush_caches(c) static inline Class test_class_setSuperclass(Class cls, Class supercls) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return class_setSuperclass(cls, supercls); #pragma clang diagnostic pop } #define class_setSuperclass(c, s) test_class_setSuperclass(c, s) static inline void testcollect() { _objc_flush_caches(nil); } // Synchronously run test code on another thread. // The block object is unsafe_unretained because we must not allow // ARC to retain them in non-Foundation tests typedef void(^testblock_t)(void); static __unsafe_unretained testblock_t testcodehack; static inline void *_testthread(void *arg __unused) { testcodehack(); return NULL; } static inline void testonthread(__unsafe_unretained testblock_t code) { pthread_t th; testcodehack = code; // force GC not-thread-local, avoid ARC void* casts pthread_create(&th, NULL, _testthread, NULL); pthread_join(th, NULL); } /* Make sure libobjc does not call global operator new. Any test that DOES need to call global operator new must `#define TEST_CALLS_OPERATOR_NEW` before including test.h. */ #if __cplusplus && !defined(TEST_CALLS_OPERATOR_NEW) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winline-new-delete" #import inline void* operator new(std::size_t) throw (std::bad_alloc) { fail("called global operator new"); } inline void* operator new[](std::size_t) throw (std::bad_alloc) { fail("called global operator new[]"); } inline void* operator new(std::size_t, const std::nothrow_t&) throw() { fail("called global operator new(nothrow)"); } inline void* operator new[](std::size_t, const std::nothrow_t&) throw() { fail("called global operator new[](nothrow)"); } inline void operator delete(void*) throw() { fail("called global operator delete"); } inline void operator delete[](void*) throw() { fail("called global operator delete[]"); } inline void operator delete(void*, const std::nothrow_t&) throw() { fail("called global operator delete(nothrow)"); } inline void operator delete[](void*, const std::nothrow_t&) throw() { fail("called global operator delete[](nothrow)"); } #pragma clang diagnostic pop #endif /* Leak checking Fails if total malloc memory in use at leak_check(n) is more than n bytes above that at leak_mark(). */ static inline void leak_recorder(task_t task __unused, void *ctx, unsigned type __unused, vm_range_t *ranges, unsigned count) { size_t *inuse = (size_t *)ctx; while (count--) { *inuse += ranges[count].size; } } static inline size_t leak_inuse(void) { size_t total = 0; vm_address_t *zones; unsigned count; malloc_get_all_zones(mach_task_self(), NULL, &zones, &count); for (unsigned i = 0; i < count; i++) { size_t inuse = 0; malloc_zone_t *zone = (malloc_zone_t *)zones[i]; if (!zone->introspect || !zone->introspect->enumerator) continue; // skip DispatchContinuations because it sometimes claims to be // using lots of memory that then goes away later if (0 == strcmp(zone->zone_name, "DispatchContinuations")) continue; zone->introspect->enumerator(mach_task_self(), &inuse, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, NULL, leak_recorder); // fprintf(stderr, "%zu in use for zone %s\n", inuse, zone->zone_name); total += inuse; } return total; } static inline void leak_dump_heap(const char *msg) { fprintf(stderr, "%s\n", msg); // Make `heap` write to stderr int outfd = dup(STDOUT_FILENO); dup2(STDERR_FILENO, STDOUT_FILENO); pid_t pid = getpid(); char cmd[256]; // environment variables reset for iOS simulator use sprintf(cmd, "DYLD_LIBRARY_PATH= DYLD_ROOT_PATH= /usr/bin/heap -addresses all %d", (int)pid); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" system(cmd); #pragma clang diagnostic pop dup2(outfd, STDOUT_FILENO); close(outfd); } static size_t _leak_start; static inline void leak_mark(void) { testcollect(); if (getenv("LEAK_HEAP")) { leak_dump_heap("HEAP AT leak_mark"); } _leak_start = leak_inuse(); } #define leak_check(n) \ do { \ const char *_check = getenv("LEAK_CHECK"); \ size_t inuse; \ if (_check && 0 == strcmp(_check, "NO")) break; \ testcollect(); \ if (getenv("LEAK_HEAP")) { \ leak_dump_heap("HEAP AT leak_check"); \ } \ inuse = leak_inuse(); \ if (inuse > _leak_start + n) { \ fprintf(stderr, "BAD: %zu bytes leaked at %s:%u " \ "(try LEAK_HEAP and HANG_ON_LEAK to debug)\n", \ inuse - _leak_start, __FILE__, __LINE__); \ if (getenv("HANG_ON_LEAK")) { \ fprintf(stderr, "Hanging after leaks detected. " \ "Leaks command:\n"); \ fprintf(stderr, "leaks %d\n", getpid()); \ while (1) sleep(1); \ } \ } \ } while (0) // true when running under Guard Malloc static inline bool is_guardmalloc(void) { const char *env = getenv("GUARDMALLOC"); return (env && 0 == strcmp(env, "1")); } // true when running a debug build of libobjc static inline bool is_debug(void) { static int debugness = -1; if (debugness == -1) { debugness = dlsym(RTLD_DEFAULT, "_objc_isDebugBuild") ? 1 : 0; } return (bool)debugness; } /* Memory management compatibility macros */ static id self_fn(id x) __attribute__((used)); static id self_fn(id x) { return x; } #if __has_feature(objc_arc_weak) // __weak # define WEAK_STORE(dst, val) (dst = (val)) # define WEAK_LOAD(src) (src) #else // no __weak # define WEAK_STORE(dst, val) objc_storeWeak((id *)&dst, val) # define WEAK_LOAD(src) objc_loadWeak((id *)&src) #endif #if __has_feature(objc_arc) // ARC # define RELEASE_VAR(x) x = nil # define SUPER_DEALLOC() # define RETAIN(x) (self_fn(x)) # define RELEASE_VALUE(x) ((void)self_fn(x)) # define AUTORELEASE(x) (self_fn(x)) #else // MRC # define RELEASE_VAR(x) do { [x release]; x = nil; } while (0) # define SUPER_DEALLOC() [super dealloc] # define RETAIN(x) [x retain] # define RELEASE_VALUE(x) [x release] # define AUTORELEASE(x) [x autorelease] #endif /* gcc compatibility macros */ /* @autoreleasepool should generate objc_autoreleasePoolPush/Pop on 10.7/5.0 */ //#if !defined(__clang__) # define PUSH_POOL { void *pool = objc_autoreleasePoolPush(); # define POP_POOL objc_autoreleasePoolPop(pool); } //#else //# define PUSH_POOL @autoreleasepool //# define POP_POOL //#endif #if __OBJC__ /* General purpose root class */ OBJC_ROOT_CLASS @interface TestRoot { @public Class isa; } +(void) load; +(void) initialize; -(id) self; -(Class) class; -(Class) superclass; +(id) new; +(id) alloc; +(id) allocWithZone:(void*)zone; -(id) copy; -(id) mutableCopy; -(id) init; -(void) dealloc; @end @interface TestRoot (RR) -(id) retain; -(oneway void) release; -(id) autorelease; -(unsigned long) retainCount; -(id) copyWithZone:(void *)zone; -(id) mutableCopyWithZone:(void*)zone; @end // incremented for each call of TestRoot's methods extern atomic_int TestRootLoad; extern atomic_int TestRootInitialize; extern atomic_int TestRootAlloc; extern atomic_int TestRootAllocWithZone; extern atomic_int TestRootCopy; extern atomic_int TestRootCopyWithZone; extern atomic_int TestRootMutableCopy; extern atomic_int TestRootMutableCopyWithZone; extern atomic_int TestRootInit; extern atomic_int TestRootDealloc; extern atomic_int TestRootRetain; extern atomic_int TestRootRelease; extern atomic_int TestRootAutorelease; extern atomic_int TestRootRetainCount; extern atomic_int TestRootTryRetain; extern atomic_int TestRootIsDeallocating; extern atomic_int TestRootPlusRetain; extern atomic_int TestRootPlusRelease; extern atomic_int TestRootPlusAutorelease; extern atomic_int TestRootPlusRetainCount; #endif // Struct that does not return in registers on any architecture struct stret { int a; int b; int c; int d; int e; int f; int g; int h; int i; int j; }; static inline BOOL stret_equal(struct stret a, struct stret b) { return (a.a == b.a && a.b == b.b && a.c == b.c && a.d == b.d && a.e == b.e && a.f == b.f && a.g == b.g && a.h == b.h && a.i == b.i && a.j == b.j); } static struct stret STRET_RESULT __attribute__((used)) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; #if TARGET_OS_SIMULATOR // Force cwd to executable's directory during launch. // sim used to do this but simctl does not. #include __attribute__((constructor)) static void hack_cwd(void) { if (!getenv("HACKED_CWD")) { chdir(dirname((*_NSGetArgv())[0])); setenv("HACKED_CWD", "1", 1); } } #endif #endif