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
15static spinlock sLock = B_SPINLOCK_INITIALIZER;
16
17
18// #pragma mark - TestOptions
19
20
21TestOptions::TestOptions()
22	:
23	panicOnFailure(false),
24	quitAfterFailure(false)
25{
26}
27
28
29// #pragma mark - GlobalTestContext
30
31
32struct 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
51GlobalTestContext::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
65GlobalTestContext::~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*
81GlobalTestContext::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
98void
99GlobalTestContext::SetCurrentContext(TestContext* context)
100{
101	fCurrentContext = context;
102}
103
104
105void
106GlobalTestContext::TestDone(bool success)
107{
108	fTotalTests++;
109	if (!success)
110		fFailedTests++;
111}
112
113
114thread_id
115GlobalTestContext::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
134GlobalTestContext::_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
147GlobalTestContext::_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
176GlobalTestContext::_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
198TestContext::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
210TestContext::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
229TestContext::~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
241void
242TestContext::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
261void
262TestContext::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