1// restest.cpp
2
3#include <algobase.h>
4#include <stdio.h>
5#include <string.h>
6
7#include <Entry.h>
8#include <File.h>
9#include <String.h>
10
11#include "Exception.h"
12#include "OffsetFile.h"
13#include "ResourceFile.h"
14#include "Warnings.h"
15
16const char kUsage[] = {
17"Usage: %s <options> <filenames>\n"
18"options:\n"
19"  -h, --help         print this help\n"
20"  -l, --list         list each file's resources (short version)\n"
21"  -L, --list-long    list each file's resources (long version)\n"
22"  -s, --summary      print a summary\n"
23"  -w, --write-test   write the file resources (in memory only) and\n"
24"                     compare the data with the file's\n"
25};
26
27const status_t USAGE_ERROR	= B_ERRORS_END + 1;
28const status_t USAGE_HELP	= B_ERRORS_END + 2;
29
30enum listing_level {
31	NO_LISTING,
32	SHORT_LISTING,
33	LONG_LISTING,
34};
35
36struct TestOptions {
37	listing_level	listing;
38	bool			write_test;
39	bool			summary;
40};
41
42struct TestResult {
43	TestResult(const char* filename)
44		: filename(filename), warnings(), exception(NULL)
45	{
46	}
47
48	~TestResult()
49	{
50		delete exception;
51	}
52
53	BString			filename;
54	Warnings		warnings;
55	Exception*		exception;
56};
57
58// print_indented
59void
60print_indented(const char* str, uint32 chars, bool indentFirst = true)
61{
62	const uint32 MAX_CHARS_PER_LINE = 75;
63	int32 charsLeft = strlen(str);
64	if (chars < MAX_CHARS_PER_LINE) {
65		for (int32 line = 0; charsLeft > 0; line++) {
66			if (line != 0 || indentFirst) {
67				for (int32 i = 0; (uint32)i < chars; i++)
68					printf(" ");
69			}
70			int32 bytesLeftOnLine = MAX_CHARS_PER_LINE - chars;
71			int32 printChars = min(bytesLeftOnLine, charsLeft);
72			printf("%.*s\n", (int)printChars, str);
73			str += printChars;
74			charsLeft -= printChars;
75			// skip spaces
76			while (*str == ' ') {
77				str++;
78				charsLeft--;
79			}
80		}
81	}
82}
83
84// print_indented
85void
86print_indented(const char* indentStr, const char* str)
87{
88	uint32 chars = strlen(indentStr);
89	printf(indentStr);
90	print_indented(str, chars, false);
91}
92
93// parse_arguments
94void
95parse_arguments(int argc, const char* const* argv, BList& files,
96				TestOptions& options)
97{
98	// default options
99	options.listing = NO_LISTING;
100	options.write_test = false;
101	options.summary = false;
102	// parse arguments
103	for (int32 i = 1; i < argc; i++) {
104		const char* arg = argv[i];
105		int32 len = strlen(arg);
106		if (len == 0)
107			throw Exception(USAGE_ERROR, "Illegal argument: `'.");
108		if (arg[0] == '-') {
109			if (len < 2)
110				throw Exception(USAGE_ERROR, "Illegal argument: `-'.");
111			if (arg[1] == '-') {
112				const char* option = arg + 2;
113				// help
114				if (!strcmp(option, "help")) {
115					throw Exception(USAGE_HELP);
116				// list
117				} else if (!strcmp(option, "list")) {
118					if (options.listing == NO_LISTING)
119						options.listing = SHORT_LISTING;
120				// list-long
121				} else if (!strcmp(option, "list-long")) {
122					options.listing = LONG_LISTING;
123				// summary
124				} else if (!strcmp(option, "summary")) {
125					options.summary = true;
126				// write-test
127				} else if (!strcmp(option, "write-test")) {
128					options.write_test = true;
129				// error
130				} else {
131					throw Exception(USAGE_ERROR, BString("Illegal option: `")
132												 << arg << "'.");
133				}
134			} else {
135				for (int32 i = 1; i < len; i++) {
136					char option = arg[i];
137					switch (option) {
138						// help
139						case 'h':
140							throw Exception(USAGE_HELP);
141							break;
142						// list
143						case 'l':
144							if (options.listing == NO_LISTING)
145								options.listing = SHORT_LISTING;
146							break;
147						// list long
148						case 'L':
149							options.listing = LONG_LISTING;
150							break;
151						// summary
152						case 's':
153							options.summary = true;
154							break;
155						// write test
156						case 'w':
157							options.write_test = true;
158							break;
159						// error
160						default:
161							throw Exception(USAGE_ERROR,
162											BString("Illegal option: `")
163											<< arg << "'.");
164							break;
165					}
166				}
167			}
168		} else
169			files.AddItem(const_cast<char*>(arg));
170	}
171}
172
173// test_file
174void
175test_file(const char* filename, const TestOptions& options,
176		  TestResult& testResult)
177{
178	Warnings::SetCurrentWarnings(&testResult.warnings);
179	ResourceFile resFile;
180	try {
181		// check if the file exists
182		BEntry entry(filename, true);
183		status_t error = entry.InitCheck();
184		if (error != B_OK)
185			throw Exception(error);
186		if (!entry.Exists() || !entry.IsFile())
187			throw Exception("Entry doesn't exist or is no regular file.");
188		entry.Unset();
189		// open the file
190		BFile file(filename, B_READ_ONLY);
191		error = file.InitCheck();
192		if (error != B_OK)
193			throw Exception(error, "Failed to open file.");
194		// do the actual test
195		resFile.Init(file);
196		if (options.write_test)
197			resFile.WriteTest();
198	} catch (Exception exception) {
199		testResult.exception = new Exception(exception);
200	}
201	Warnings::SetCurrentWarnings(NULL);
202	// print warnings and error
203	if (options.listing != NO_LISTING
204		|| testResult.warnings.CountWarnings() > 0 || testResult.exception) {
205		printf("\nFile `%s':\n", filename);
206	}
207	// warnings
208	if (testResult.warnings.CountWarnings() > 0) {
209		for (int32 i = 0;
210			 const char* warning = testResult.warnings.WarningAt(i);
211			 i++) {
212			print_indented("  Warning: ", warning);
213		}
214	}
215	// error
216	if (testResult.exception) {
217		status_t error = testResult.exception->GetError();
218		const char* description = testResult.exception->GetDescription();
219		if (strlen(description) > 0) {
220			print_indented("  Error:   ", description);
221			if (error != B_OK)
222				print_indented("           ", strerror(error));
223		} else if (error != B_OK)
224			print_indented("  Error:   ", strerror(error));
225	}
226	// list resources
227	if (resFile.InitCheck() == B_OK) {
228		switch (options.listing) {
229			case NO_LISTING:
230				break;
231			case SHORT_LISTING:
232				resFile.PrintToStream(false);
233				break;
234			case LONG_LISTING:
235				resFile.PrintToStream(true);
236				break;
237		}
238	}
239}
240
241// test_files
242void
243test_files(BList& files, TestOptions& options)
244{
245	BList testResults;
246	int32 successTestCount = 0;
247	int32 warningTestCount = 0;
248	int32 failedTestCount = 0;
249	for (int32 i = 0;
250		 const char* filename = (const char*)files.ItemAt(i);
251		 i++) {
252		TestResult* testResult = new TestResult(filename);
253		testResults.AddItem(testResult);
254		test_file(filename, options, *testResult);
255		if (testResult->exception)
256			failedTestCount++;
257		else if (testResult->warnings.CountWarnings() > 0)
258			warningTestCount++;
259		else
260			successTestCount++;
261	}
262	// print summary
263	if (options.summary) {
264		printf("\nSummary:\n");
265		printf(  "=======\n");
266		// successful tests
267		if (successTestCount > 0) {
268			if (successTestCount == 1)
269				printf("one successful test\n");
270			else
271				printf("%ld successful tests\n", successTestCount);
272		}
273		// tests with warnings
274		if (warningTestCount > 0) {
275			if (warningTestCount == 1)
276				printf("one test with warnings:\n");
277			else
278				printf("%ld tests with warnings:\n", warningTestCount);
279			for (int32 i = 0;
280				 TestResult* testResult = (TestResult*)testResults.ItemAt(i);
281				 i++) {
282				if (!testResult->exception
283					&& testResult->warnings.CountWarnings() > 0) {
284					printf("  `%s'\n", testResult->filename.String());
285				}
286			}
287		}
288		// failed tests
289		if (failedTestCount > 0) {
290			if (failedTestCount == 1)
291				printf("one test failed:\n");
292			else
293				printf("%ld tests failed:\n", failedTestCount);
294			for (int32 i = 0;
295				 TestResult* testResult = (TestResult*)testResults.ItemAt(i);
296				 i++) {
297				if (testResult->exception)
298					printf("  `%s'\n", testResult->filename.String());
299			}
300		}
301	}
302	// cleanup
303	for (int32 i = 0;
304		 TestResult* testResult = (TestResult*)testResults.ItemAt(i);
305		 i++) {
306		delete testResult;
307	}
308}
309
310// main
311int
312main(int argc, const char* const* argv)
313{
314	int returnValue = 0;
315	const char* cmdName = argv[0];
316	TestOptions options;
317	BList files;
318	try {
319		// parse arguments
320		parse_arguments(argc, argv, files, options);
321		if (files.CountItems() == 0)
322			throw Exception(USAGE_ERROR, "No files given.");
323		// test the files
324		test_files(files, options);
325	} catch (Exception exception) {
326		status_t error = exception.GetError();
327		const char* description = exception.GetDescription();
328		switch (error) {
329			case B_OK:
330				if (strlen(description) > 0)
331					fprintf(stderr, "%s\n", description);
332				returnValue = 1;
333				break;
334			case USAGE_ERROR:
335				if (strlen(description) > 0)
336					fprintf(stderr, "%s\n", description);
337				fprintf(stderr, kUsage, cmdName);
338				returnValue = 1;
339				break;
340			case USAGE_HELP:
341				printf(kUsage, cmdName);
342				break;
343			default:
344				fprintf(stderr, "  error: %s\n", strerror(error));
345				returnValue = 1;
346				break;
347		}
348	}
349	return returnValue;
350}
351
352