1 /* 2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "TestContext.h" 8 9 #include <util/AutoLock.h> 10 11 #include "Test.h" 12 #include "TestError.h" 13 14 15 static spinlock sLock = B_SPINLOCK_INITIALIZER; 16 17 18 // #pragma mark - TestOptions 19 20 21 TestOptions::TestOptions() 22 : 23 panicOnFailure(false), 24 quitAfterFailure(false) 25 { 26 } 27 28 29 // #pragma mark - GlobalTestContext 30 31 32 struct GlobalTestContext::ThreadCookie { 33 GlobalTestContext* context; 34 thread_func function; 35 void* arg; 36 37 ThreadCookie(GlobalTestContext* context, thread_func function, void* arg) 38 : 39 context(context), 40 function(function), 41 arg(arg) 42 { 43 } 44 }; 45 46 47 /*static*/ GlobalTestContext::ThreadEntry* GlobalTestContext::sGlobalThreads 48 = NULL; 49 50 51 GlobalTestContext::GlobalTestContext(TestOutput& output, TestOptions& options) 52 : 53 fThreads(NULL), 54 fThreadEntry(this), 55 fOutput(output), 56 fOptions(options), 57 fCurrentContext(NULL), 58 fTotalTests(0), 59 fFailedTests(0) 60 { 61 _SetCurrent(&fThreadEntry); 62 } 63 64 65 GlobalTestContext::~GlobalTestContext() 66 { 67 InterruptsSpinLocker locker(sLock); 68 69 // remove all of our entries from the global list 70 ThreadEntry** entry = &sGlobalThreads; 71 while (*entry != NULL) { 72 if ((*entry)->context == this) 73 *entry = (*entry)->globalNext; 74 else 75 entry = &(*entry)->globalNext; 76 } 77 } 78 79 80 /*static*/ GlobalTestContext* 81 GlobalTestContext::Current() 82 { 83 thread_id thread = find_thread(NULL); 84 85 InterruptsSpinLocker locker(sLock); 86 87 ThreadEntry* entry = sGlobalThreads; 88 while (entry != NULL) { 89 if (entry->thread == thread) 90 return entry->context; 91 entry = entry->globalNext; 92 } 93 94 return NULL; 95 } 96 97 98 void 99 GlobalTestContext::SetCurrentContext(TestContext* context) 100 { 101 fCurrentContext = context; 102 } 103 104 105 void 106 GlobalTestContext::TestDone(bool success) 107 { 108 fTotalTests++; 109 if (!success) 110 fFailedTests++; 111 } 112 113 114 thread_id 115 GlobalTestContext::SpawnThread(thread_func function, const char* name, 116 int32 priority, void* arg) 117 { 118 ThreadCookie* cookie = new(std::nothrow) ThreadCookie(this, function, arg); 119 if (cookie == NULL) 120 return B_NO_MEMORY; 121 122 thread_id thread = spawn_kernel_thread(_ThreadEntry, name, priority, 123 cookie); 124 if (thread < 0) { 125 delete cookie; 126 return thread; 127 } 128 129 return thread; 130 } 131 132 133 /*static*/ void 134 GlobalTestContext::_SetCurrent(ThreadEntry* entry) 135 { 136 InterruptsSpinLocker locker(sLock); 137 138 entry->contextNext = entry->context->fThreads; 139 entry->context->fThreads = entry; 140 141 entry->globalNext = sGlobalThreads; 142 sGlobalThreads = entry; 143 } 144 145 146 /*static*/ void 147 GlobalTestContext::_UnsetCurrent(ThreadEntry* entryToRemove) 148 { 149 InterruptsSpinLocker locker(sLock); 150 151 // remove from the global list 152 ThreadEntry** entry = &sGlobalThreads; 153 while (*entry != NULL) { 154 if (*entry == entryToRemove) { 155 *entry = (*entry)->globalNext; 156 break; 157 } 158 159 entry = &(*entry)->globalNext; 160 } 161 162 // remove from the context's list 163 entry = &entryToRemove->context->fThreads; 164 while (*entry != NULL) { 165 if (*entry == entryToRemove) { 166 *entry = (*entry)->contextNext; 167 break; 168 } 169 170 entry = &(*entry)->contextNext; 171 } 172 } 173 174 175 /*static*/ status_t 176 GlobalTestContext::_ThreadEntry(void* data) 177 { 178 ThreadCookie* cookie = (ThreadCookie*)data; 179 180 ThreadEntry entry(cookie->context); 181 _SetCurrent(&entry); 182 183 thread_func function = cookie->function; 184 void* arg = cookie->arg; 185 delete cookie; 186 187 status_t result = function(arg); 188 189 _UnsetCurrent(&entry); 190 191 return result; 192 } 193 194 195 // #pragma mark - TestContext 196 197 198 TestContext::TestContext(GlobalTestContext* globalContext) 199 : 200 fGlobalContext(globalContext), 201 fParent(NULL), 202 fTest(NULL), 203 fErrors(NULL), 204 fLevel(0) 205 { 206 fGlobalContext->SetCurrentContext(this); 207 } 208 209 210 TestContext::TestContext(TestContext& parent, Test* test) 211 : 212 fGlobalContext(parent.GlobalContext()), 213 fParent(&parent), 214 fTest(test), 215 fErrors(NULL), 216 fLevel(parent.Level() + 1) 217 { 218 fGlobalContext->SetCurrentContext(this); 219 220 if (fTest != NULL) { 221 if (fTest->IsLeafTest()) 222 Print("%*s%s...", fLevel * 2, "", test->Name()); 223 else 224 Print("%*s%s:\n", fLevel * 2, "", test->Name()); 225 } 226 } 227 228 229 TestContext::~TestContext() 230 { 231 fGlobalContext->SetCurrentContext(fParent); 232 233 while (fErrors != NULL) { 234 TestError* error = fErrors; 235 fErrors = error->ListLink(); 236 delete error; 237 } 238 } 239 240 241 void 242 TestContext::TestDone(bool success) 243 { 244 if (fTest != NULL && fTest->IsLeafTest()) { 245 if (success) { 246 Print(" ok\n"); 247 } else { 248 Print(" FAILED\n"); 249 TestError* error = fErrors; 250 while (error != NULL) { 251 Print("%s", error->Message()); 252 error = error->ListLink(); 253 } 254 } 255 256 fGlobalContext->TestDone(success); 257 } 258 } 259 260 261 void 262 TestContext::ErrorArgs(const char* format, va_list args) 263 { 264 int size = vsnprintf(NULL, 0, format, args) + 1; 265 char* buffer = (char*)malloc(size); 266 if (buffer == NULL) 267 return; 268 269 vsnprintf(buffer, size, format, args); 270 271 TestError* error = new(std::nothrow) TestError(fTest, buffer); 272 if (error == NULL) { 273 free(buffer); 274 return; 275 } 276 277 InterruptsSpinLocker locker(sLock); 278 error->ListLink() = fErrors; 279 fErrors = error; 280 281 if (Options().panicOnFailure) 282 panic("Test check failed: %s", error->Message()); 283 } 284