2004-10-24 Casper S. Hornstrup <chorns@users.sourceforge.net>
[reactos.git] / reactos / tools / regtests.c
index ea28f56..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
@@ -36,10 +36,10 @@ static FILE *out;
 static char *path;
 static char *file;
 static char *makefile;
-static char *umstubfile;
-static char *kmstubfile;
+static char *exestubfile;
 
-char* convert_path(char* origpath)
+static char*
+convert_path(char* origpath)
 {
    char* newpath;
    int i;
@@ -70,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];
@@ -84,7 +85,10 @@ static void write_line(char *line)
   n_out = fwrite(&buf[0], 1, strlen(buf), out);
 }
 
-static void change_extension(char *filenamebuffer, char *filename, char *newextension)
+static void
+change_extension(char *filenamebuffer,
+  char *filename,
+  char *newextension)
 {
   char *ptr;
 
@@ -108,61 +112,81 @@ static void change_extension(char *filenamebuffer, char *filename, char *newexte
     }
 }
 
+static void
+get_test_name(char *filename,
+  char *testname)
+{
+  int i;
+
+  strcpy(testname, filename);
+
+  i = strlen(testname);
+  while (i > 0 && testname[i] != '.')
+    {
+      i--;
+    }
+  if (i > 0)
+    {
+      testname[i] = 0;
+    }
+
+  /* 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] = '_';
+    }
+  i = 1;
+  while (i < strlen(testname))
+    {
+      testname[i] = tolower(testname[i]);
+      if (!((testname[i] >= 'a' && testname[i] <= 'z') ||
+        (testname[i] >= '0' && testname[i] <= '9')))
+        {
+          testname[i] = '_';
+        }
+      i++;
+    }
+}
+
 /*
  * filename - name of file to make registrations for
- * regtype  - type of registration (0 = prototype, 1 = call, 2 = makefile)
+ * type     - type of registration (0 = prototype, 1 = call, 2 = makefile)
  */
-void register_test(char *filename, int type)
+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, j;
-
-  strcpy(testname, filename);
+  int i;
 
-  i = strlen(testname);
-  while (i > 0 && testname[i] != '.')
+  i = strlen(filename);
+  while (i > 0 && filename[i] != '.')
     {
       i--;
     }
   if (i > 0)
     {
       memset(ext, 0, sizeof(ext));
-      strncpy(&ext[0], &testname[i], strlen(&testname[i]));
+      strncpy(&ext[0], &filename[i], strlen(&filename[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
-  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))
-    {
-      testname[j] = tolower(testname[j]);
-      if (!((testname[j] >= 'a' && testname[j] <= 'z') ||
-        (testname[j] >= '0' && testname[j] <= '9')))
-        {
-          testname[j] = '_';
-        }
-      j++;
-    }
+  memset(testname, 0, sizeof(testname));
+  get_test_name(filename, testname);
 
   if (type == 0)
     {
@@ -327,7 +351,8 @@ make_file_list (int type)
 #endif
 
 static int
-is_file_changed(char *filename, char *content)
+is_file_changed(char *filename,
+  char *content)
 {
   FILE *file;
   int size;
@@ -379,7 +404,8 @@ is_file_changed(char *filename, char *content)
 }
 
 static int
-write_file_if_changed(char *filename, char *content)
+write_file_if_changed(char *filename,
+  char *content)
 {
   FILE *file;
   int n;
@@ -402,99 +428,293 @@ write_file_if_changed(char *filename, char *content)
   return 0;
 }
 
-static char KMSTUB[] =
+static char EXESTUB[] =
   "/* This file is autogenerated. */\n"
   "\n"
-  "#include <roskrnl.h>\n"
-  "#include <../kmregtests/kmregtests.h>\n"
-  "\n"
-  "typedef int (*TestRoutine)(int Command, char *Buffer);\n"
-  "\n"
-  "extern void RegisterTests();\n"
-  "\n"
-  "static PDEVICE_OBJECT KMRegTestsDeviceObject = NULL;\n"
-  "static PFILE_OBJECT KMRegTestsFileObject = NULL;\n"
+  "#include \"regtests.h\"\n"
   "\n"
-  "void AddTest(TestRoutine Routine)\n"
+  "void\n"
+  "ConsoleWrite(char *Buffer)\n"
   "{\n"
-  "  PDEVICE_OBJECT DeviceObject;\n"
-  "  UNICODE_STRING DriverName;\n"
-  "  IO_STATUS_BLOCK IoStatus;\n"
-  "  NTSTATUS Status;\n"
-  "  KEVENT Event;\n"
-  "  PIRP Irp;\n"
-  "\n"
-  "  if (KMRegTestsDeviceObject == NULL)\n"
-  "    {\n"
-  "      RtlInitUnicodeString(&DriverName, L\"\\\\Device\\\\KMRegTests\");\n"
-  "         Status = IoGetDeviceObjectPointer(&DriverName, FILE_WRITE_ATTRIBUTES,\n"
-  "           &KMRegTestsFileObject, &KMRegTestsDeviceObject);\n"
-  "         if (!NT_SUCCESS(Status)) return;\n"
-  "       }\n"
-  "  KeInitializeEvent(&Event, NotificationEvent, FALSE);\n"
-  "  Irp = IoBuildDeviceIoControlRequest(IOCTL_KMREGTESTS_REGISTER,\n"
-  "       KMRegTestsDeviceObject, &Routine, sizeof(TestRoutine), NULL, 0, FALSE, &Event, &IoStatus);\n"
-  "  Status = IoCallDriver(KMRegTestsDeviceObject, Irp);\n"
+  "  printf(Buffer);\n"
   "}\n"
   "\n"
-  "void PrepareTests()\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 UMSTUB[] =
+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"
-  "\n"
   "#include <windows.h>\n"
-  "#define NTOS_MODE_USER\n"
-  "#include <ntos.h>\n"
   "#include \"regtests.h\"\n"
   "\n"
-  "PVOID\n"
-  "AllocateMemory(ULONG Size)\n"
-  "{\n"
-  "  return (PVOID) RtlAllocateHeap(RtlGetProcessHeap(), 0, Size);\n"
-  "}\n"
-  "\n"
-  "VOID\n"
-  "FreeMemory(PVOID Base)\n"
-  "{\n"
-  "  RtlFreeHeap(RtlGetProcessHeap(), 0, Base);\n"
-  "}\n"
+  "API_DESCRIPTION ExternalDependencies[] =\n"
+  "{\n";
+
+static char HOOKS_FOOTER[] =
+  "};\n"
   "\n"
-  "/* This function will be called several times */\n"
-  "void PrepareTests()\n"
-  "{\n"
-  "  static int testsRegistered = 0;\n"
-  "  if (testsRegistered == 0)\n"
-  "    {\n"
-  "         HANDLE hEvent;\n"
-  "         hEvent = OpenEventW(\n"
-  "        EVENT_ALL_ACCESS,\n"
-  "        FALSE,\n"
-  "        L\"WinRegTests\");\n"
-  "         if (hEvent != NULL)\n"
-  "           {\n"
-  "                 SetEvent(hEvent);\n"
-  "             CloseHandle(hEvent);\n"
-  "             testsRegistered = 1;\n"
-  "          InitializeTests();\n"
-  "          RegisterTests();\n"
-  "          PerformTests();\n"
-  "        }\n"
-  "    }\n"
-  "}\n";
+  "#define ExternalDependencyCount %d\n"
+  "ULONG MaxExternalDependency = ExternalDependencyCount - 1;\n";
 
 static char HELP[] =
-  "REGTESTS path file makefile [-u umstubfile] [-k kmstubfile]\n"
+  "REGTESTS path file makefile [-e exestubfile]\n"
+  "REGTESTS -s stublistfile stubsfile hooksfile\n"
   "\n"
-  "  path        Path to files\n"
-  "  file        Registration file to create\n"
-  "  makefile    Makefile to create\n"
-  "  umstubfile  Optional stub for running tests internal to a user-mode module\n"
-  "  kmstubfile  Optional stub for running tests internal to a kernel-mode module\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);
+}
 
-int main(int argc, char **argv)
+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);
+}
+
+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];
   int i;
@@ -505,7 +725,6 @@ int main(int argc, char **argv)
     return 1;
   }
 
-
   strcpy(buf, convert_path(argv[1]));
   if (buf[strlen(buf)] != DIR_SEPARATOR_CHAR)
     {
@@ -534,35 +753,25 @@ int main(int argc, char **argv)
       return 1;
     }
 
-  umstubfile = NULL;
-  kmstubfile = NULL;
+  exestubfile = NULL;
   for (i = 4; i < argc; i++)
     {
          if (argv[i][0] == '-')
            {
-             if (argv[i][1] == 'u')
-                   {
-              umstubfile = convert_path(argv[++i]);
-              if (umstubfile[0] == 0)
-                {
-                  printf("Missing umstubfile\n");
-                  return 1;
-                }
-                   }
-                 else if (argv[i][1] == 'k')
-                   {
-              kmstubfile = convert_path(argv[++i]);
-              if (kmstubfile[0] == 0)
-                {
-                  printf("Missing kmstubfile\n");
-                  return 1;
-                }
-                   }
-                 else
-                   {
-              printf("Unknown switch\n");
-              return 1;
-                   }
+        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;
+                   }
            }
         }
 
@@ -613,27 +822,34 @@ int main(int argc, char **argv)
 
   fclose(out);
 
-  /* User-mode stubfile */
-  if (umstubfile != NULL)
+  /* Executable stubfile */
+  if (exestubfile != NULL)
     {
-      if (write_file_if_changed(umstubfile, UMSTUB) != 0)
-        {
-          perror("Cannot create output user-mode stubfile");
+      if (write_file_if_changed(exestubfile, EXESTUB) != 0)
+       {
+          perror("Cannot create output executable stubfile");
           return 1;
         }
     }
 
-  /* Kernel-mode stubfile */
-  if (kmstubfile != NULL)
-    {
-      if (write_file_if_changed(kmstubfile, KMSTUB) != 0)
-        {
-          perror("Cannot create output kernel-mode stubfile");
-          return 1;
-        }
-    }
+  return 0;
+}
 
-  printf("Successfully generated regression test registrations.\n");
+int main(int argc,
+  char **argv)
+{
+  if (argc < 2)
+  {
+    puts(HELP);
+    return 1;
+  }
 
-  return 0;
+  if (strlen(argv[1]) > 1 && argv[1][0] == '-' && argv[1][1] == 's')
+    {
+      return run_stubs(argc, argv);
+    }
+  else
+    {
+      return run_registrations(argc, argv);
+    }
 }