2004-10-24 Casper S. Hornstrup <chorns@users.sourceforge.net>
[reactos.git] / reactos / tools / regtests.c
index e4477c9..bb29ac7 100755 (executable)
@@ -9,6 +9,7 @@
 #include <sys/stat.h>
 #include <stdlib.h>
 #include <string.h>
+#include <ctype.h>
 
 #ifdef WIN32
 #include <io.h>
 #include <dirent.h>
 #include <unistd.h>
 #endif
-#include <ctype.h>
-#ifndef WIN32
 #ifndef MAX_PATH
 #define MAX_PATH 260
 #endif
+#ifndef WIN32
 #define DIR_SEPARATOR_CHAR '/'
 #define DIR_SEPARATOR_STRING "/"
 #else
 static FILE *out;
 static char *path;
 static char *file;
+static char *makefile;
+static char *exestubfile;
 
-char* convert_path(char* origpath)
+static char*
+convert_path(char* origpath)
 {
    char* newpath;
    int i;
@@ -67,7 +70,8 @@ char* convert_path(char* origpath)
    return(newpath);
 }
 
-static void write_line(char *line)
+static void
+write_line(char *line)
 {
   int n_out;
   char buf[200];
@@ -81,13 +85,38 @@ static void write_line(char *line)
   n_out = fwrite(&buf[0], 1, strlen(buf), out);
 }
 
-void register_test(char *filename, int prototype)
+static void
+change_extension(char *filenamebuffer,
+  char *filename,
+  char *newextension)
 {
-  char ext[100];
-  char testname[100];
-  char call[100];
-  char regtest[100];
-  int i, j;
+  char *ptr;
+
+  if (newextension == NULL)
+    {
+      strcpy(filenamebuffer, filename);
+      return;
+    }
+
+  ptr = strrchr(filename, '.');
+  if (ptr != NULL)
+    {
+      strncpy(filenamebuffer, filename, ptr - filename);
+      filenamebuffer[ptr - filename] = 0;
+      strcat(filenamebuffer, newextension);
+    }
+  else
+    {
+      strcpy(filenamebuffer, filename);
+      strcat(filenamebuffer, newextension);
+    }
+}
+
+static void
+get_test_name(char *filename,
+  char *testname)
+{
+  int i;
 
   strcpy(testname, filename);
 
@@ -98,51 +127,84 @@ void register_test(char *filename, int prototype)
     }
   if (i > 0)
     {
-      memset(ext, 0, sizeof(ext));
-      strncpy(&ext[0], &testname[i], strlen(&testname[i]));
-
-      if ((strncmp(ext, ".c", 2) != 0) && (strncmp(ext, ".C", 2) != 0))
-        {
-          return;
-        }
-
       testname[i] = 0;
     }
-  else
-    {
-      return;
-    }
 
-  // Make a capital first letter and make all other letters lower case
+  /* Make a capital first letter and make all other letters lower case */
   testname[0] = toupper(testname[0]);
   if (!((testname[0] >= 'A' && testname[0] <= 'Z') ||
     (testname[0] >= '0' && testname[0] <= '9')))
     {
       testname[0] = '_';
     }
-  j = 1;
-  while (j < strlen(testname))
+  i = 1;
+  while (i < strlen(testname))
     {
-      testname[j] = tolower(testname[j]);
-      if (!((testname[j] >= 'a' && testname[j] <= 'z') ||
-        (testname[j] >= '0' && testname[j] <= '9')))
+      testname[i] = tolower(testname[i]);
+      if (!((testname[i] >= 'a' && testname[i] <= 'z') ||
+        (testname[i] >= '0' && testname[i] <= '9')))
         {
-          testname[j] = '_';
+          testname[i] = '_';
         }
-      j++;
+      i++;
     }
+}
 
-  if (prototype)
+/*
+ * filename - name of file to make registrations for
+ * type     - type of registration (0 = prototype, 1 = call, 2 = makefile)
+ */
+static void
+register_test(char *filename,
+  int type)
+{
+  char ext[100];
+  char testname[100];
+  char call[100];
+  char regtest[100];
+  char filenamebuffer[MAX_PATH];
+  int i;
+
+  i = strlen(filename);
+  while (i > 0 && filename[i] != '.')
+    {
+      i--;
+    }
+  if (i > 0)
+    {
+      memset(ext, 0, sizeof(ext));
+      strncpy(&ext[0], &filename[i], strlen(&filename[i]));
+
+      if ((strncmp(ext, ".c", 2) != 0) && (strncmp(ext, ".C", 2) != 0))
+        {
+          return;
+        }
+    }
+  else
+    {
+      return;
+    }
+
+  memset(testname, 0, sizeof(testname));
+  get_test_name(filename, testname);
+
+  if (type == 0)
     {
       sprintf(regtest, "extern int %sTest(int Command, char *Buffer);", testname);
       write_line(regtest);
     }
-  else
+  else if (type == 1)
     {
       sprintf(call, "%sTest", testname);
       sprintf(regtest, "  AddTest((TestRoutine)%s);", call);
       write_line(regtest);
     }
+  else if (type == 2)
+    {
+         change_extension(filenamebuffer, filename, ".o");
+      sprintf(regtest, "%s \\", filenamebuffer);
+      write_line(regtest);
+    }
 }
 
 #ifdef WIN32
@@ -150,14 +212,14 @@ void register_test(char *filename, int prototype)
 /* Win32 version */
 
 static void
-make_file_list (int prototype)
+make_file_list (int type)
 {
   struct _finddata_t f;
   int findhandle;
   char searchbuf[MAX_PATH];
 
   strcpy(searchbuf, path);
-  strcpy(searchbuf, "*.*");
+  strcat(searchbuf, "*.*");
   findhandle =_findfirst(searchbuf, &f);
   if (findhandle != -1)
     {
@@ -169,7 +231,7 @@ make_file_list (int prototype)
               continue;
            }
 
-          register_test(f.name, prototype);
+          register_test(f.name, type);
        }
       while (_findnext(findhandle, &f) == 0);
       _findclose(findhandle);
@@ -180,7 +242,7 @@ make_file_list (int prototype)
 
 /* Linux version */
 static void
-make_file_list (int prototype)
+make_file_list (int type)
 {
   DIR *dirp;
   struct dirent *entry;
@@ -225,7 +287,7 @@ make_file_list (int prototype)
               continue;
            }
 
-          register_test(entry->d_name, prototype);
+          register_test(entry->d_name, type);
          }
       }
       closedir(dirp);
@@ -273,7 +335,7 @@ make_file_list (int prototype)
               continue;
            }
 
-          register_test(entry->d_name, prototype);
+          register_test(entry->d_name, type);
         }
       closedir(dirp);
     }
@@ -288,22 +350,381 @@ make_file_list (int prototype)
 
 #endif
 
+static int
+is_file_changed(char *filename,
+  char *content)
+{
+  FILE *file;
+  int size;
+  int n;
+  char *filecontent;
+
+  file = fopen(filename, "rb");
+  if (file == NULL)
+    {
+       return 1;
+     }
+
+  fseek(file, 0, SEEK_END);
+  size = ftell(file);
+  fseek(file, 0, SEEK_SET);
+  if (size <= 0)
+    {
+      fclose(file);
+         return 1;
+    }
+  filecontent = malloc(size);
+  if (filecontent == NULL)
+    {
+      fclose(file);
+         return 1;
+    }
+
+  n = fread(filecontent, 1, size, file);
+
+  if (n != strlen(content))
+    {
+         free(filecontent);
+      fclose(file);
+         return 1;
+    }
+
+  if (strcmp(content, filecontent) != 0)
+    {
+         free(filecontent);
+      fclose(file);
+         return 1;
+    }
+
+  free(filecontent);
+
+  fclose(file);
+
+  return 0;
+}
+
+static int
+write_file_if_changed(char *filename,
+  char *content)
+{
+  FILE *file;
+  int n;
+
+  if (is_file_changed(filename, content) == 0)
+    {
+         return 0;
+    }
+
+  file = fopen(filename, "wb");
+  if (file == NULL)
+    {
+       return 1;
+     }
+
+  n = fwrite(content, 1, strlen(content), file);
+
+  fclose(file);
+
+  return 0;
+}
+
+static char EXESTUB[] =
+  "/* This file is autogenerated. */\n"
+  "\n"
+  "#include \"regtests.h\"\n"
+  "\n"
+  "void\n"
+  "ConsoleWrite(char *Buffer)\n"
+  "{\n"
+  "  printf(Buffer);\n"
+  "}\n"
+  "\n"
+  "int\n"
+  "mainCRTStartup(HANDLE hInstance,\n"
+  "  HANDLE hPrevInstance,\n"
+  "  LPSTR lpszCmdParam,\n"
+  "  int nCmdShow)\n"
+  "{\n"
+  "  InitializeTests();\n"
+  "  RegisterTests();\n"
+  "  SetupOnce();\n"
+  "  PerformTests(ConsoleWrite, NULL);\n"
+  "  _ExitProcess(0);\n"
+  "  return 0;\n"
+  "}\n";
+
+static char STUBS_HEADER[] =
+  "/* This file is autogenerated. */\n"
+  "passthrough:\n"
+  "  call _FrameworkGetHook@4\n"
+  "  test %eax, %eax\n"
+  "  je .return\n"
+  "  jmp *%eax\n"
+  ".return:\n"
+  "  /* This will most likely corrupt the stack */\n"
+  "  ret\n"
+  "\n";
+
+static char HOOKS_HEADER[] =
+  "/* This file is autogenerated. */\n"
+  "#include <windows.h>\n"
+  "#include \"regtests.h\"\n"
+  "\n"
+  "API_DESCRIPTION ExternalDependencies[] =\n"
+  "{\n";
+
+static char HOOKS_FOOTER[] =
+  "};\n"
+  "\n"
+  "#define ExternalDependencyCount %d\n"
+  "ULONG MaxExternalDependency = ExternalDependencyCount - 1;\n";
+
 static char HELP[] =
-  "REGTESTS  path file\n"
+  "REGTESTS path file makefile [-e exestubfile]\n"
+  "REGTESTS -s stublistfile stubsfile hooksfile\n"
   "\n"
-  "  path          Path to files\n"
-  "  file          File to create\n";
+  "  path         Path to files\n"
+  "  file         Registration file to create\n"
+  "  makefile     Makefile to create\n"
+  "  exestubfile  Optional stub for running tests in the build environment\n"
+  "  stublistfile File with descriptions of stubs\n"
+  "  stubsfile    File with stubs to create\n"
+  "  hooksfile    File with hooks to create\n";
+
+#define INPUT_BUFFER_SIZE 255
+
+void
+write_stubs_header(FILE * out)
+{
+  fputs(STUBS_HEADER, out);
+}
+
+void
+write_hooks_header(FILE * out)
+{
+  fputs(HOOKS_HEADER, out);
+}
+
+void
+write_hooks_footer(FILE *hooks_out, unsigned long nr_stubs)
+{
+  fprintf(hooks_out, HOOKS_FOOTER, nr_stubs);
+}
+
+char *
+get_symbolname(char *decoratedname)
+{
+  char buf[300];
+
+  if (decoratedname[0] == '@')
+    return strdup(decoratedname);
+  strcpy(buf, "_");
+  strcat(buf, decoratedname);
+  return strdup(buf);
+}
+
+char *
+get_undecorated_name(char *buf,
+  char *decoratedname)
+{
+  int start = 0;
+  int end = 0;
+
+  while (start < strlen(decoratedname) && decoratedname[start] == '@')
+    {
+      start++;
+    }
+  strcpy(buf, &decoratedname[start]);
+  end = strlen(buf) - 1;
+  while (end > 0 && isdigit(buf[end]))
+    {
+      end--;
+    }
+  if (buf[end] == '@')
+    {
+      buf[end] = 0;
+    }
+  return buf;
+}
+
+char *
+get_forwarded_export(char *forwardedexport)
+{
+  char buf[300];
+
+  if (forwardedexport == NULL)
+    {
+      strcpy(buf, "NULL");
+    }
+  else
+    {
+      sprintf(buf, "\"%s\"", forwardedexport);
+    }
+  return strdup(buf);
+}
+
+void
+write_stub(FILE *stubs_out, FILE *hooks_out, char *dllname,
+  char *decoratedname_and_forward, unsigned int stub_index)
+{
+  char buf[300];
+  char *p;
+  char *decoratedname = NULL;
+  char *forwardedexport = NULL;
+  char *symbolname = NULL;
+
+  p = strtok(decoratedname_and_forward, "=");
+  if (p != NULL)
+    {
+      decoratedname = p;
+
+      p = strtok(NULL, "=");
+      forwardedexport = p;
+    }
+  else
+    {
+      decoratedname = decoratedname_and_forward;
+      forwardedexport = decoratedname_and_forward;
+    }
+
+  symbolname = get_symbolname(decoratedname);
+  fprintf(stubs_out, ".globl %s\n", symbolname);
+  fprintf(stubs_out, "%s:\n", symbolname);
+  free(symbolname);
+  fprintf(stubs_out, "  pushl $%d\n", stub_index);
+  fprintf(stubs_out, "  jmp passthrough\n");
+  fprintf(stubs_out, "\n");
+  forwardedexport = get_forwarded_export(forwardedexport);
+  fprintf(hooks_out, "  {\"%s\", \"%s\", %s, NULL, NULL},\n",
+    dllname,
+    get_undecorated_name(buf, decoratedname),
+    forwardedexport);
+  free(forwardedexport);
+}
 
-int main(int argc, char **argv)
+void
+create_stubs_and_hooks(
+  FILE *in,
+  FILE *stubs_out,
+  FILE *hooks_out)
+{
+  char line[INPUT_BUFFER_SIZE];
+  char *s;
+  char *dllname;
+  char *decoratedname_and_forward;
+  int stub_index;
+
+  write_stubs_header(stubs_out);
+
+  write_hooks_header(hooks_out);
+
+       /*
+        * Scan the database. The database is a text file; each
+   * line is a record, which contains data for one stub.
+        * Each record has two columns:
+        *
+        * DLLNAME (e.g. ntdll.dll)
+        * DECORATED NAME (e.g. NtCreateProcess@32, @InterlockedIncrement@4 or printf)
+        */
+  stub_index = 0; /* First stub has index zero */
+
+       for (
+    ;
+               /* Go on until EOF or read zero bytes */
+               ((!feof(in)) && (fgets(line, sizeof line, in) != NULL));
+               /* Next stub index */
+         )
+       {
+               /*
+                * Remove, if present, the trailing LF.
+                */
+               if ((s = (char *) strchr(line,'\n')) != NULL)
+                 {
+                         *s = '\0';
+           }
+
+               /*
+                * Remove, if present, the trailing CR.
+                */
+               if ((s = (char *) strchr(line,'\r')) != NULL)
+                 {
+                         *s = '\0';
+           }
+
+               /*
+                * Skip comments (#) and empty lines.
+                */
+               s = & line[0];
+               if ((*s) != '#' && (*s) != '\0')
+               {
+        /* Extract the DLL name */
+        dllname = (char *) strtok(s, " \t");
+        if (dllname != NULL && strlen(dllname) > 0)
+          {
+                         /*
+             * Extract the decorated function name and possibly forwarded export.
+             * Format:
+             *   decoratedname=forwardedexport (no DLL name)
+             */
+                 decoratedname_and_forward = (char *) strtok(NULL, " \t");
+                           /* Extract the argument count */
+                           write_stub(stubs_out, hooks_out, dllname, decoratedname_and_forward, stub_index);
+            stub_index++;
+          }
+               }
+       }
+
+  write_hooks_footer(hooks_out, stub_index);
+}
+
+int run_stubs(int argc,
+  char **argv)
+{
+  FILE *in;
+  FILE *stubs_out;
+  FILE *hooks_out;
+
+       in = fopen(argv[2], "rb");
+       if (in == NULL)
+       {
+               perror("Failed to open stub description input file");
+               return 1;
+       }
+
+       stubs_out = fopen(argv[3], "wb");
+       if (stubs_out == NULL)
+       {
+               perror("Failed to open stubs output file");
+               return 1;
+       }
+
+       hooks_out = fopen(argv[4], "wb");
+       if (hooks_out == NULL)
+       {
+               perror("Failed to open hooks output file");
+               return 1;
+       }
+
+  create_stubs_and_hooks(in, stubs_out, hooks_out);
+
+  fclose(stubs_out);
+  fclose(hooks_out);
+
+  return 0;
+}
+
+int run_registrations(int argc,
+  char **argv)
 {
   char buf[MAX_PATH];
-  if (argc < 2)
+  int i;
+
+  if (argc < 4)
   {
     puts(HELP);
     return 1;
   }
 
-
   strcpy(buf, convert_path(argv[1]));
   if (buf[strlen(buf)] != DIR_SEPARATOR_CHAR)
     {
@@ -325,10 +746,41 @@ int main(int argc, char **argv)
       return 1;
     }
 
+  makefile = convert_path(argv[3]);
+  if (makefile[0] == 0)
+    {
+      printf("Missing makefile\n");
+      return 1;
+    }
+
+  exestubfile = NULL;
+  for (i = 4; i < argc; i++)
+    {
+         if (argv[i][0] == '-')
+           {
+        if (argv[i][1] == 'e')
+                   {
+            exestubfile = convert_path(argv[++i]);
+            if (exestubfile[0] == 0)
+              {
+                printf("Missing exestubfile\n");
+                return 1;
+              }
+                   }
+                 else
+                   {
+                printf("Unknown switch -%c\n", argv[i][1]);
+                return 1;
+                   }
+           }
+        }
+
+
+  /* Registration file */
   out = fopen(file, "wb");
   if (out == NULL)
     {
-       perror("Cannot open output file");
+       perror("Cannot create output file");
        return 1;
      }
 
@@ -337,7 +789,7 @@ int main(int argc, char **argv)
   write_line("typedef int (*TestRoutine)(int Command, char *Buffer);");
   write_line("");
 
-  make_file_list(1);
+  make_file_list(0);
 
   write_line("");
   write_line("extern void AddTest(TestRoutine Routine);");
@@ -345,13 +797,59 @@ int main(int argc, char **argv)
   write_line("void RegisterTests()");
   write_line("{");
 
-  make_file_list(0);
+  make_file_list(1);
 
   write_line("}");
 
   fclose(out);
 
+
+  /* Makefile */
+  out = fopen(makefile, "wb");
+  if (out == NULL)
+    {
+       perror("Cannot create output makefile");
+       return 1;
+     }
+
+  write_line("# This file is autogenerated.");
+  write_line("");
+  write_line("TESTS = \\");
+
+  make_file_list(2);
+
+  write_line("");
+
+  fclose(out);
+
+  /* Executable stubfile */
+  if (exestubfile != NULL)
+    {
+      if (write_file_if_changed(exestubfile, EXESTUB) != 0)
+       {
+          perror("Cannot create output executable stubfile");
+          return 1;
+        }
+    }
+
   return 0;
 }
 
-/* EOF */
+int main(int argc,
+  char **argv)
+{
+  if (argc < 2)
+  {
+    puts(HELP);
+    return 1;
+  }
+
+  if (strlen(argv[1]) > 1 && argv[1][0] == '-' && argv[1][1] == 's')
+    {
+      return run_stubs(argc, argv);
+    }
+  else
+    {
+      return run_registrations(argc, argv);
+    }
+}