1/*
2 * Copyright 2009-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <ctype.h>
8#include <string.h>
9
10#include <Drivers.h>
11#include <KernelExport.h>
12
13#include <AutoDeleter.h>
14
15#include <kernel.h>
16#include <thread.h>
17
18#include "TestContext.h"
19#include "TestManager.h"
20#include "TestOutput.h"
21
22#include "lock/LockTestSuite.h"
23
24
25int32 api_version = B_CUR_DRIVER_API_VERSION;
26
27static const char* sDeviceNames[] = {
28	"kernel_unit_tests",
29	NULL
30};
31
32static const char* const kUsage =
33	"Usage:\n"
34	"  help\n"
35	"    Print usage info.\n"
36	"\n"
37	"  list\n"
38	"    Print available tests.\n"
39	"\n"
40	"  run [ <options> ] [ <tests> ]\n"
41	"    Run tests. The tests to be run can be given as arguments. When no "
42		"tests\n"
43	"	 are specified, all tests are run.\n"
44	"\n"
45	"    Options:\n"
46	"	  -p    - panic(), when a test check fails.\n"
47	"	  -q    - Don't run any further tests after the first test failure.\n";
48
49static TestManager* sTestManager = NULL;
50
51
52// #pragma mark -
53
54
55static bool
56parse_command_line(char* buffer, char**& _argv, int& _argc)
57{
58	// Process the argument string. We split at whitespace, heeding quotes and
59	// escaped characters. The processed arguments are written to the given
60	// buffer, separated by single null chars.
61	char* start = buffer;
62	char* out = buffer;
63	bool pendingArgument = false;
64	int argc = 0;
65	while (*start != '\0') {
66		// ignore whitespace
67		if (isspace(*start)) {
68			if (pendingArgument) {
69				*out = '\0';
70				out++;
71				argc++;
72				pendingArgument = false;
73			}
74			start++;
75			continue;
76		}
77
78		pendingArgument = true;
79
80		if (*start == '"' || *start == '\'') {
81			// quoted text -- continue until closing quote
82			char quote = *start;
83			start++;
84			while (*start != '\0' && *start != quote) {
85				if (*start == '\\' && quote == '"') {
86					start++;
87					if (*start == '\0')
88						break;
89				}
90				*out = *start;
91				start++;
92				out++;
93			}
94
95			if (*start != '\0')
96				start++;
97		} else {
98			// unquoted text
99			if (*start == '\\') {
100				// escaped char
101				start++;
102				if (start == '\0')
103					break;
104			}
105
106			*out = *start;
107			start++;
108			out++;
109		}
110	}
111
112	if (pendingArgument) {
113		*out = '\0';
114		argc++;
115	}
116
117	// allocate argument vector
118	char** argv = new(std::nothrow) char*[argc + 1];
119	if (argv == NULL)
120		return false;
121
122	// fill vector
123	start = buffer;
124	for (int i = 0; i < argc; i++) {
125		argv[i] = start;
126		start += strlen(start) + 1;
127	}
128	argv[argc] = NULL;
129
130	_argv = argv;
131	_argc = argc;
132	return true;
133}
134
135
136// #pragma mark - device hooks
137
138
139static status_t
140device_open(const char* name, uint32 openMode, void** _cookie)
141{
142	*_cookie = NULL;
143	return B_OK;
144}
145
146
147static status_t
148device_close(void* cookie)
149{
150	return B_OK;
151}
152
153
154static status_t
155device_free(void* cookie)
156{
157	return B_OK;
158}
159
160
161static status_t
162device_control(void* cookie, uint32 op, void* arg, size_t length)
163{
164	return B_BAD_VALUE;
165}
166
167
168static status_t
169device_read(void* cookie, off_t position, void* data, size_t* numBytes)
170{
171	*numBytes = 0;
172	return B_OK;
173}
174
175
176static status_t
177device_write(void* cookie, off_t position, const void* data, size_t* numBytes)
178{
179	if (position != 0)
180		return B_BAD_VALUE;
181
182	// copy data to stack buffer
183	char* buffer = (char*)malloc(*numBytes + 1);
184	if (buffer == NULL)
185		return B_NO_MEMORY;
186	MemoryDeleter bufferDeleter(buffer);
187
188	Thread* thread = thread_get_current_thread();
189	if ((thread->flags & THREAD_FLAGS_SYSCALL) != 0) {
190		if (!IS_USER_ADDRESS(data)
191			|| user_memcpy(buffer, data, *numBytes) != B_OK) {
192			return B_BAD_ADDRESS;
193		}
194	} else
195		memcpy(buffer, data, *numBytes);
196
197	buffer[*numBytes] = '\0';
198
199	// create an output
200	TestOutput* output = new(std::nothrow) DebugTestOutput;
201	if (output == NULL)
202		return B_NO_MEMORY;
203	ObjectDeleter<TestOutput> outputDeleter(output);
204
205	// parse arguments
206	char** argv;
207	int argc;
208	if (!parse_command_line(buffer, argv, argc))
209		return B_NO_MEMORY;
210	ArrayDeleter<char*> argvDeleter(argv);
211
212	if (argc == 0)
213		return B_BAD_VALUE;
214
215	// execute command
216	if (strcmp(argv[0], "help") == 0) {
217		// print usage -- print individual lines to avoid dprintf() buffer
218		// overflows
219		const char* usage = kUsage;
220		while (*usage != '\0') {
221			const char* lineEnd = strchr(usage, '\n');
222			if (lineEnd != NULL)
223				lineEnd++;
224			else
225				lineEnd = usage + strlen(usage);
226
227			output->Print("%.*s", (int)(lineEnd - usage), usage);
228			usage = lineEnd;
229		}
230	} else if (strcmp(argv[0], "list") == 0) {
231		sTestManager->ListTests(*output);
232	} else if (strcmp(argv[0], "run") == 0) {
233		// parse options
234		TestOptions options;
235		int argi = 1;
236		while (argi < argc) {
237			const char* arg = argv[argi];
238			if (*arg == '-') {
239				for (arg++; *arg != '\0'; arg++) {
240					switch (*arg) {
241						case 'p':
242							options.panicOnFailure = true;
243							break;
244						case 'q':
245							options.quitAfterFailure = true;
246							break;
247						default:
248							output->Print("Invalid option: \"-%c\"\n", *arg);
249							return B_BAD_VALUE;
250					}
251				}
252
253				argi++;
254			} else
255				break;
256		}
257
258		GlobalTestContext globalContext(*output, options);
259		sTestManager->RunTests(globalContext, argv + argi, argc - argi);
260	} else {
261		output->Print("Invalid command \"%s\"!\n", argv[0]);
262		return B_BAD_VALUE;
263	}
264
265	return B_OK;
266}
267
268
269// #pragma mark - driver interface
270
271
272static device_hooks sDeviceHooks = {
273	device_open,
274	device_close,
275	device_free,
276	device_control,
277	device_read,
278	device_write
279};
280
281
282status_t
283init_hardware(void)
284{
285	return B_OK;
286}
287
288
289status_t
290init_driver(void)
291{
292	sTestManager = new(std::nothrow) TestManager;
293	if (sTestManager == NULL)
294		return B_NO_MEMORY;
295
296	// register test suites
297	sTestManager->AddTest(create_lock_test_suite());
298
299	return B_OK;
300}
301
302
303void
304uninit_driver(void)
305{
306	delete sTestManager;
307}
308
309
310const char**
311publish_devices(void)
312{
313	return sDeviceNames;
314}
315
316
317device_hooks*
318find_device(const char* name)
319{
320	return strcmp(name, sDeviceNames[0]) == 0 ? &sDeviceHooks : NULL;
321}
322