xref: /haiku/src/tools/restest/restest.cpp (revision 02354704729d38c3b078c696adc1bbbd33cbcf72)
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 
16 const 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 
27 const status_t USAGE_ERROR	= B_ERRORS_END + 1;
28 const status_t USAGE_HELP	= B_ERRORS_END + 2;
29 
30 enum listing_level {
31 	NO_LISTING,
32 	SHORT_LISTING,
33 	LONG_LISTING,
34 };
35 
36 struct TestOptions {
37 	listing_level	listing;
38 	bool			write_test;
39 	bool			summary;
40 };
41 
42 struct 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
59 void
60 print_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
85 void
86 print_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
94 void
95 parse_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
174 void
175 test_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
242 void
243 test_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
311 int
312 main(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