500bb351a269ad37bf8bd01a85d0f7eb5a505b91
[reactos.git] / reactos / tools / regtests.c
1 /*
2 * Generate a file with test registrations from a list
3 * of files in a directory.
4 * Casper S. Hornstrup <chorns@users.sourceforge.net>
5 */
6
7 #include <stdio.h>
8 #include <fcntl.h>
9 #include <sys/stat.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <ctype.h>
13
14 #ifdef WIN32
15 #include <io.h>
16 #include <dos.h>
17 #else
18 #include <sys/io.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <dirent.h>
22 #include <unistd.h>
23 #endif
24 #ifndef MAX_PATH
25 #define MAX_PATH 260
26 #endif
27 #ifndef WIN32
28 #define DIR_SEPARATOR_CHAR '/'
29 #define DIR_SEPARATOR_STRING "/"
30 #else
31 #define DIR_SEPARATOR_CHAR '\\'
32 #define DIR_SEPARATOR_STRING "\\"
33 #endif
34
35 static FILE *out;
36 static char *path;
37 static char *file;
38 static char *makefile;
39 static char *exestubfile;
40
41 static char*
42 convert_path(char* origpath)
43 {
44 char* newpath;
45 int i;
46
47 /* for no good reason, i'm having trouble getting gcc to link strdup */
48 //newpath = strdup(origpath);
49 newpath = malloc(strlen(origpath)+1);
50 strcpy(newpath, origpath);
51
52 i = 0;
53 while (newpath[i] != 0)
54 {
55 #ifndef WIN32
56 if (newpath[i] == '\\')
57 {
58 newpath[i] = '/';
59 }
60 #else
61 #ifdef WIN32
62 if (newpath[i] == '/')
63 {
64 newpath[i] = '\\';
65 }
66 #endif
67 #endif
68 i++;
69 }
70 return(newpath);
71 }
72
73 static void
74 write_line(char *line)
75 {
76 int n_out;
77 char buf[200];
78
79 memset(buf, 0, sizeof(buf));
80 strcpy(buf, line);
81 /* Terminate the line */
82 buf[strlen(buf)] = '\r';
83 buf[strlen(buf)] = '\n';
84
85 n_out = fwrite(&buf[0], 1, strlen(buf), out);
86 }
87
88 static void
89 change_extension(char *filenamebuffer,
90 char *filename,
91 char *newextension)
92 {
93 char *ptr;
94
95 if (newextension == NULL)
96 {
97 strcpy(filenamebuffer, filename);
98 return;
99 }
100
101 ptr = strrchr(filename, '.');
102 if (ptr != NULL)
103 {
104 strncpy(filenamebuffer, filename, ptr - filename);
105 filenamebuffer[ptr - filename] = 0;
106 strcat(filenamebuffer, newextension);
107 }
108 else
109 {
110 strcpy(filenamebuffer, filename);
111 strcat(filenamebuffer, newextension);
112 }
113 }
114
115 static void
116 get_test_name(char *filename,
117 char *testname)
118 {
119 int i;
120
121 strcpy(testname, filename);
122
123 i = strlen(testname);
124 while (i > 0 && testname[i] != '.')
125 {
126 i--;
127 }
128 if (i > 0)
129 {
130 testname[i] = 0;
131 }
132
133 /* Make a capital first letter and make all other letters lower case */
134 testname[0] = toupper(testname[0]);
135 if (!((testname[0] >= 'A' && testname[0] <= 'Z') ||
136 (testname[0] >= '0' && testname[0] <= '9')))
137 {
138 testname[0] = '_';
139 }
140 i = 1;
141 while (i < strlen(testname))
142 {
143 testname[i] = tolower(testname[i]);
144 if (!((testname[i] >= 'a' && testname[i] <= 'z') ||
145 (testname[i] >= '0' && testname[i] <= '9')))
146 {
147 testname[i] = '_';
148 }
149 i++;
150 }
151 }
152
153 /*
154 * filename - name of file to make registrations for
155 * type - type of registration (0 = prototype, 1 = call, 2 = makefile)
156 */
157 static void
158 register_test(char *filename,
159 int type)
160 {
161 char ext[100];
162 char testname[100];
163 char call[100];
164 char regtest[100];
165 char filenamebuffer[MAX_PATH];
166 int i;
167
168 i = strlen(filename);
169 while (i > 0 && filename[i] != '.')
170 {
171 i--;
172 }
173 if (i > 0)
174 {
175 memset(ext, 0, sizeof(ext));
176 strncpy(&ext[0], &filename[i], strlen(&filename[i]));
177
178 if ((strncmp(ext, ".c", 2) != 0) && (strncmp(ext, ".C", 2) != 0))
179 {
180 return;
181 }
182 }
183 else
184 {
185 return;
186 }
187
188 memset(testname, 0, sizeof(testname));
189 get_test_name(filename, testname);
190
191 if (type == 0)
192 {
193 sprintf(regtest, "extern int %sTest(int Command, char *Buffer);", testname);
194 write_line(regtest);
195 }
196 else if (type == 1)
197 {
198 sprintf(call, "%sTest", testname);
199 sprintf(regtest, " AddTest((TestRoutine)%s);", call);
200 write_line(regtest);
201 }
202 else if (type == 2)
203 {
204 change_extension(filenamebuffer, filename, ".o");
205 sprintf(regtest, "%s \\", filenamebuffer);
206 write_line(regtest);
207 }
208 }
209
210 #ifdef WIN32
211
212 /* Win32 version */
213
214 static void
215 make_file_list (int type)
216 {
217 struct _finddata_t f;
218 int findhandle;
219 char searchbuf[MAX_PATH];
220
221 strcpy(searchbuf, path);
222 strcat(searchbuf, "*.*");
223 findhandle =_findfirst(searchbuf, &f);
224 if (findhandle != -1)
225 {
226 do
227 {
228 if (f.attrib & _A_SUBDIR)
229 {
230 /* Skip subdirectories */
231 continue;
232 }
233
234 register_test(f.name, type);
235 }
236 while (_findnext(findhandle, &f) == 0);
237 _findclose(findhandle);
238 }
239 }
240
241 #else
242
243 /* Linux version */
244 static void
245 make_file_list (int type)
246 {
247 DIR *dirp;
248 struct dirent *entry;
249 struct stat stbuf;
250 char buf[MAX_PATH];
251
252 #ifdef HAVE_D_TYPE
253 dirp = opendir(path);
254 if (dirp != NULL)
255 {
256 while ((entry = readdir(dirp)) != NULL)
257 {
258 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
259 continue; // skip self and parent
260
261 if (entry->d_type == DT_REG) // normal file
262 {
263 // Check for an absolute path
264 if (path[0] == DIR_SEPARATOR_CHAR)
265 {
266 strcpy(buf, path);
267 strcat(buf, DIR_SEPARATOR_STRING);
268 strcat(buf, entry->d_name);
269 }
270 else
271 {
272 getcwd(buf, sizeof(buf));
273 strcat(buf, DIR_SEPARATOR_STRING);
274 strcat(buf, path);
275 strcat(buf, entry->d_name);
276 }
277
278 if (stat(buf, &stbuf) == -1)
279 {
280 printf("Can't access '%s' (%s)\n", buf, strerror(errno));
281 return;
282 }
283
284 if (S_ISDIR(stbuf.st_mode))
285 {
286 /* Skip subdirectories */
287 continue;
288 }
289
290 register_test(entry->d_name, type);
291 }
292 }
293 closedir(dirp);
294 }
295 else
296 {
297 printf("Can't open %s\n", path);
298 return;
299 }
300
301 #else
302
303 dirp = opendir(path);
304 if (dirp != NULL)
305 {
306 while ((entry = readdir(dirp)) != NULL)
307 {
308 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
309 continue; // skip self and parent
310
311 // Check for an absolute path
312 if (path[0] == DIR_SEPARATOR_CHAR)
313 {
314 strcpy(buf, path);
315 strcat(buf, DIR_SEPARATOR_STRING);
316 strcat(buf, entry->d_name);
317 }
318 else
319 {
320 getcwd(buf, sizeof(buf));
321 strcat(buf, DIR_SEPARATOR_STRING);
322 strcat(buf, path);
323 strcat(buf, entry->d_name);
324 }
325
326 if (stat(buf, &stbuf) == -1)
327 {
328 printf("Can't access '%s' (%s)\n", buf, strerror(errno));
329 return;
330 }
331
332 if (S_ISDIR(stbuf.st_mode))
333 {
334 /* Skip subdirectories */
335 continue;
336 }
337
338 register_test(entry->d_name, type);
339 }
340 closedir(dirp);
341 }
342 else
343 {
344 printf("Can't open %s\n", path);
345 return;
346 }
347
348 #endif
349 }
350
351 #endif
352
353 static int
354 is_file_changed(char *filename,
355 char *content)
356 {
357 FILE *file;
358 int size;
359 int n;
360 char *filecontent;
361
362 file = fopen(filename, "rb");
363 if (file == NULL)
364 {
365 return 1;
366 }
367
368 fseek(file, 0, SEEK_END);
369 size = ftell(file);
370 fseek(file, 0, SEEK_SET);
371 if (size <= 0)
372 {
373 fclose(file);
374 return 1;
375 }
376 filecontent = malloc(size);
377 if (filecontent == NULL)
378 {
379 fclose(file);
380 return 1;
381 }
382
383 n = fread(filecontent, 1, size, file);
384
385 if (n != strlen(content))
386 {
387 free(filecontent);
388 fclose(file);
389 return 1;
390 }
391
392 if (strcmp(content, filecontent) != 0)
393 {
394 free(filecontent);
395 fclose(file);
396 return 1;
397 }
398
399 free(filecontent);
400
401 fclose(file);
402
403 return 0;
404 }
405
406 static int
407 write_file_if_changed(char *filename,
408 char *content)
409 {
410 FILE *file;
411 int n;
412
413 if (is_file_changed(filename, content) == 0)
414 {
415 return 0;
416 }
417
418 file = fopen(filename, "wb");
419 if (file == NULL)
420 {
421 return 1;
422 }
423
424 n = fwrite(content, 1, strlen(content), file);
425
426 fclose(file);
427
428 return 0;
429 }
430
431 static char EXESTUB[] =
432 "/* This file is autogenerated. */\n"
433 "\n"
434 "#include \"regtests.h\"\n"
435 "\n"
436 "#if defined(__USE_W32API)\n"
437 " #define HANDLE PVOID\n"
438 " #define NTSTATUS ULONG\n"
439 "\n"
440 " NTSTATUS STDCALL\n"
441 " NtTerminateProcess(HANDLE ProcessHandle,\n"
442 " NTSTATUS ExitStatus);\n"
443 "\n"
444 " #define NtCurrentProcess() ((HANDLE) 0xFFFFFFFF)\n"
445 "#endif\n"
446 "\n"
447 "void\n"
448 "ConsoleWrite(char *Buffer)\n"
449 "{\n"
450 " printf(Buffer);\n"
451 "}\n"
452 "\n"
453 "int\n"
454 "mainCRTStartup(HANDLE hInstance,\n"
455 " HANDLE hPrevInstance,\n"
456 " LPSTR lpszCmdParam,\n"
457 " int nCmdShow)\n"
458 "{\n"
459 " InitializeTests();\n"
460 " RegisterTests();\n"
461 " SetupOnce();\n"
462 " PerformTests(ConsoleWrite, NULL);\n"
463 " NtTerminateProcess (NtCurrentProcess(), 0);\n"
464 " return 0;\n"
465 "}\n";
466
467 static char STUBS_HEADER[] =
468 "/* This file is autogenerated. */\n"
469 "passthrough:\n"
470 " call _FrameworkGetHook@4\n"
471 " test %eax, %eax\n"
472 " je .return\n"
473 " jmp *%eax\n"
474 ".return:\n"
475 " /* This will most likely corrupt the stack */\n"
476 " ret\n"
477 "\n";
478
479 static char HOOKS_HEADER[] =
480 "/* This file is autogenerated. */\n"
481 "#include <windows.h>\n"
482 "#include \"regtests.h\"\n"
483 "\n"
484 "API_DESCRIPTION ExternalDependencies[] =\n"
485 "{\n";
486
487 static char HOOKS_FOOTER[] =
488 "};\n"
489 "\n"
490 "#define ExternalDependencyCount %d\n"
491 "ULONG MaxExternalDependency = ExternalDependencyCount - 1;\n";
492
493 static char HELP[] =
494 "REGTESTS path file makefile [-e exestubfile]\n"
495 "REGTESTS -s stublistfile stubsfile hooksfile\n"
496 "\n"
497 " path Path to files\n"
498 " file Registration file to create\n"
499 " makefile Makefile to create\n"
500 " exestubfile Optional stub for running tests in the build environment\n"
501 " stublistfile File with descriptions of stubs\n"
502 " stubsfile File with stubs to create\n"
503 " hooksfile File with hooks to create\n";
504
505 #define INPUT_BUFFER_SIZE 255
506
507 void
508 write_stubs_header(FILE * out)
509 {
510 fputs(STUBS_HEADER, out);
511 }
512
513 void
514 write_hooks_header(FILE * out)
515 {
516 fputs(HOOKS_HEADER, out);
517 }
518
519 void
520 write_hooks_footer(FILE *hooks_out, unsigned long nr_stubs)
521 {
522 fprintf(hooks_out, HOOKS_FOOTER, nr_stubs);
523 }
524
525 char *
526 get_undecorated_name(char *buf,
527 char *decoratedname)
528 {
529 int start = 0;
530 int end = 0;
531
532 while (start < strlen(decoratedname) && decoratedname[start] == '@')
533 {
534 start++;
535 }
536 strcpy(buf, &decoratedname[start]);
537 end = strlen(buf) - 1;
538 while (end > 0 && isdigit(buf[end]))
539 {
540 end--;
541 }
542 if (buf[end] == '@')
543 {
544 buf[end] = 0;
545 }
546 return buf;
547 }
548
549 char *
550 get_forwarded_export(char *forwardedexport)
551 {
552 char buf[300];
553
554 if (forwardedexport == NULL)
555 {
556 strcpy(buf, "NULL");
557 }
558 else
559 {
560 sprintf(buf, "\"%s\"", forwardedexport);
561 }
562 return strdup(buf);
563 }
564
565 void
566 write_stub(FILE *stubs_out, FILE *hooks_out, char *dllname,
567 char *decoratedname_and_forward, unsigned int stub_index)
568 {
569 char buf[300];
570 char *p;
571 char *decoratedname = NULL;
572 char *forwardedexport = NULL;
573
574 p = strtok(decoratedname_and_forward, "=");
575 if (p != NULL)
576 {
577 decoratedname = p;
578
579 p = strtok(NULL, "=");
580 forwardedexport = p;
581 }
582 else
583 {
584 decoratedname = decoratedname_and_forward;
585 forwardedexport = decoratedname_and_forward;
586 }
587
588 fprintf(stubs_out, ".globl _%s\n", decoratedname);
589 fprintf(stubs_out, "_%s:\n", decoratedname);
590 fprintf(stubs_out, " pushl $%d\n", stub_index);
591 fprintf(stubs_out, " jmp passthrough\n");
592 fprintf(stubs_out, "\n");
593 forwardedexport = get_forwarded_export(forwardedexport);
594 fprintf(hooks_out, " {\"%s\", \"%s\", %s, NULL, NULL},\n",
595 dllname,
596 get_undecorated_name(buf, decoratedname),
597 forwardedexport);
598 free(forwardedexport);
599 }
600
601 void
602 create_stubs_and_hooks(
603 FILE *in,
604 FILE *stubs_out,
605 FILE *hooks_out)
606 {
607 char line[INPUT_BUFFER_SIZE];
608 char *s;
609 char *dllname;
610 char *decoratedname_and_forward;
611 int stub_index;
612
613 write_stubs_header(stubs_out);
614
615 write_hooks_header(hooks_out);
616
617 /*
618 * Scan the database. The database is a text file; each
619 * line is a record, which contains data for one stub.
620 * Each record has two columns:
621 *
622 * DLLNAME (e.g. ntdll.dll)
623 * DECORATED NAME (e.g. NtCreateProcess@32, @InterlockedIncrement@4 or printf)
624 */
625 stub_index = 0; /* First stub has index zero */
626
627 for (
628 ;
629 /* Go on until EOF or read zero bytes */
630 ((!feof(in)) && (fgets(line, sizeof line, in) != NULL));
631 /* Next stub index */
632 )
633 {
634 /*
635 * Remove, if present, the trailing LF.
636 */
637 if ((s = (char *) strchr(line,'\n')) != NULL)
638 {
639 *s = '\0';
640 }
641
642 /*
643 * Remove, if present, the trailing CR.
644 */
645 if ((s = (char *) strchr(line,'\r')) != NULL)
646 {
647 *s = '\0';
648 }
649
650 /*
651 * Skip comments (#) and empty lines.
652 */
653 s = & line[0];
654 if ((*s) != '#' && (*s) != '\0')
655 {
656 /* Extract the DLL name */
657 dllname = (char *) strtok(s, " \t");
658 if (dllname != NULL && strlen(dllname) > 0)
659 {
660 /*
661 * Extract the decorated function name and possibly forwarded export.
662 * Format:
663 * decoratedname=forwardedexport (no DLL name)
664 */
665 decoratedname_and_forward = (char *) strtok(NULL, " \t");
666 /* Extract the argument count */
667 write_stub(stubs_out, hooks_out, dllname, decoratedname_and_forward, stub_index);
668 stub_index++;
669 }
670 }
671 }
672
673 write_hooks_footer(hooks_out, stub_index);
674 }
675
676 int run_stubs(int argc,
677 char **argv)
678 {
679 FILE *in;
680 FILE *stubs_out;
681 FILE *hooks_out;
682
683 in = fopen(argv[2], "rb");
684 if (in == NULL)
685 {
686 perror("Failed to open stub description input file");
687 return 1;
688 }
689
690 stubs_out = fopen(argv[3], "wb");
691 if (stubs_out == NULL)
692 {
693 perror("Failed to open stubs output file");
694 return 1;
695 }
696
697 hooks_out = fopen(argv[4], "wb");
698 if (hooks_out == NULL)
699 {
700 perror("Failed to open hooks output file");
701 return 1;
702 }
703
704 create_stubs_and_hooks(in, stubs_out, hooks_out);
705
706 fclose(stubs_out);
707 fclose(hooks_out);
708
709 return 0;
710 }
711
712 int run_registrations(int argc,
713 char **argv)
714 {
715 char buf[MAX_PATH];
716 int i;
717
718 if (argc < 4)
719 {
720 puts(HELP);
721 return 1;
722 }
723
724 strcpy(buf, convert_path(argv[1]));
725 if (buf[strlen(buf)] != DIR_SEPARATOR_CHAR)
726 {
727 int i = strlen(buf);
728 buf[strlen(buf)] = DIR_SEPARATOR_CHAR;
729 buf[i + 1] = 0;
730 }
731 path = buf;
732 if (path[0] == 0)
733 {
734 printf("Missing path\n");
735 return 1;
736 }
737
738 file = convert_path(argv[2]);
739 if (file[0] == 0)
740 {
741 printf("Missing file\n");
742 return 1;
743 }
744
745 makefile = convert_path(argv[3]);
746 if (makefile[0] == 0)
747 {
748 printf("Missing makefile\n");
749 return 1;
750 }
751
752 exestubfile = NULL;
753 for (i = 4; i < argc; i++)
754 {
755 if (argv[i][0] == '-')
756 {
757 if (argv[i][1] == 'e')
758 {
759 exestubfile = convert_path(argv[++i]);
760 if (exestubfile[0] == 0)
761 {
762 printf("Missing exestubfile\n");
763 return 1;
764 }
765 }
766 else
767 {
768 printf("Unknown switch -%c\n", argv[i][1]);
769 return 1;
770 }
771 }
772 }
773
774
775 /* Registration file */
776 out = fopen(file, "wb");
777 if (out == NULL)
778 {
779 perror("Cannot create output file");
780 return 1;
781 }
782
783 write_line("/* This file is autogenerated. */");
784 write_line("");
785 write_line("typedef int (*TestRoutine)(int Command, char *Buffer);");
786 write_line("");
787
788 make_file_list(0);
789
790 write_line("");
791 write_line("extern void AddTest(TestRoutine Routine);");
792 write_line("");
793 write_line("void RegisterTests()");
794 write_line("{");
795
796 make_file_list(1);
797
798 write_line("}");
799
800 fclose(out);
801
802
803 /* Makefile */
804 out = fopen(makefile, "wb");
805 if (out == NULL)
806 {
807 perror("Cannot create output makefile");
808 return 1;
809 }
810
811 write_line("# This file is autogenerated.");
812 write_line("");
813 write_line("TESTS = \\");
814
815 make_file_list(2);
816
817 write_line("");
818
819 fclose(out);
820
821 /* Executable stubfile */
822 if (exestubfile != NULL)
823 {
824 if (write_file_if_changed(exestubfile, EXESTUB) != 0)
825 {
826 perror("Cannot create output executable stubfile");
827 return 1;
828 }
829 }
830
831 return 0;
832 }
833
834 int main(int argc,
835 char **argv)
836 {
837 if (argc < 2)
838 {
839 puts(HELP);
840 return 1;
841 }
842
843 if (strlen(argv[1]) > 1 && argv[1][0] == '-' && argv[1][1] == 's')
844 {
845 return run_stubs(argc, argv);
846 }
847 else
848 {
849 return run_registrations(argc, argv);
850 }
851 }