/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 *
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

/*
 * Test pthread interaction with native Win32 threads
 *
 */


#include <afs/param.h>
#include <afs/stds.h>

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

extern "C" {
#include <pthread.h>
#include <assert.h>
#include <rx/rx_queue.h>
}


#define nTESTS         3
#define nPASSES_MAX  256


/*
 * Test1 - Tests the main thread calling pthread_self()
 *
 */

BOOL Test1 (void)
{
   BOOL rc = TRUE;
   printf ("Begin Test1:\n");

   PVOID hSelf;
   if ((hSelf = pthread_self()) == NULL)
      rc = FALSE;

   printf ("   1: pthread_self() on main thread returned 0x%08lX\n", hSelf);

   printf ("--> Test1 %s\n", (rc) ? TEXT("succeeded") : TEXT("FAILED"));
   return rc;
}


/*
 * Test2 - Tests a Win32 thread's ability to join onto a pthread
 *
 */
#define TEST2_RETURNVALUE 'Test'

DWORD WINAPI Test2_Thread1 (LPVOID lp)
{
   printf ("   1-1: waiting for thread 2 to begin\n");

   while (!*(volatile DWORD*)lp)
      ;

   printf ("   1-1: calling pthread_join (0x%08lX)\n", *(DWORD*)lp);

   DWORD status;
   int rc;
   if ((rc = pthread_join ((pthread_t)(*(DWORD*)lp), (void**)&status)) != 0)
      {
      printf ("   1-1: pthread_join failed; rc=%lu\n", rc);
      return 0;
      }

   if (status != TEST2_RETURNVALUE)
      {
      printf ("   1-1: pthread_join succeeded with wrong status (rc=%lu)\n", status);
      return 0;
      }

   printf ("   1-1: pthread_join succeeded; terminating\n");
   return 1;
}

extern "C" void *Test2_Thread2 (void *pdwCommonParam)
{
   printf ("   1-2: signalling that thread 2 began\n");
   *(volatile DWORD*)pdwCommonParam = (DWORD)pthread_self();
   Sleep (1500);

   printf ("   1-2: terminating\n");
   return (void*)TEST2_RETURNVALUE;
}

BOOL Test2 (void)
{
   BOOL rc = TRUE;
   printf ("Begin Test2:\n");

   DWORD dwCommonParam = 0;
   DWORD idThread;

   printf ("   1: creating win32 thread\n");
   HANDLE hThread1 = CreateThread (0, 0, Test2_Thread1, (LPVOID)&dwCommonParam, 0, &idThread);
   Sleep(500);

   printf ("   1: creating pthread thread\n");
   pthread_t hThread2;
   pthread_create (&hThread2, NULL, Test2_Thread2, (void *)&dwCommonParam);
   Sleep(500);

   printf ("   1: blocking until win32 thread (thread1) terminates...\n");

   WaitForSingleObject (hThread1, INFINITE);
   GetExitCodeThread (hThread1, (ULONG*)&rc);

   printf ("   1: detected thread1 termination\n");

   printf ("--> Test2 %s\n", (rc) ? TEXT("succeeded") : TEXT("FAILED"));
   return rc;
}


/*
 * Test3 - Makes sure the pthread library recognizes when native threads die
 *
 */

DWORD WINAPI Test3_Thread1 (LPVOID lp)
{
   LONG *pdw = (LONG*)lp;

   // We're awake. Do something pthready.
   //
   pthread_t pMe;
   if ((pMe = pthread_self()) == NULL)
      printf ("   3-1: could not get a pthread_self!\n");
   else
      printf ("   3-1: ready\n");

   InterlockedIncrement (pdw);

   // Now wait until we see the signal to die.
   //
   while (*(volatile LONG*)pdw != 5)
      ;

   printf ("   3-1: terminating\n");
   InterlockedIncrement (pdw);
   return 1;
}


DWORD WINAPI Test3_Thread2 (LPVOID lp)
{
   LONG *pdw = (LONG*)lp;

   // We're awake. Do something pthready.
   //
   pthread_t pMe;
   if ((pMe = pthread_self()) == NULL)
      printf ("   3-2: could not get a pthread_self!\n");
   else
      printf ("   3-2: ready\n");

   InterlockedIncrement (pdw);

   // Now wait until we see the signal to die.
   //
   while (*(volatile LONG*)pdw != 7)
      ;

   printf ("   3-2: terminating\n");
   InterlockedIncrement (pdw);
   return 1;
}


extern "C" void *Test3_Thread3 (void *pdwParam)
{
   LONG *pdw = (LONG*)pdwParam;

   // We're awake.
   //
   printf ("   3-3: ready\n");
   InterlockedIncrement (pdw);

   // Now wait until we see the signal to die.
   //
   while (*(volatile LONG*)pdw != 9)
      ;

   printf ("   3-3: terminating\n");
   InterlockedIncrement (pdw);
   return (void*)1;
}


extern "C" void *Test3_Thread4 (void *pdwParam)
{
   LONG *pdw = (LONG*)pdwParam;

   // We're awake.
   //
   printf ("   3-4: ready\n");
   InterlockedIncrement (pdw);

   // Now wait until we see the signal to die.
   //
   while (*(volatile LONG*)pdw != 11)
      ;

   printf ("   3-4: terminating\n");
   InterlockedIncrement (pdw);
   return (void*)1;
}


extern "C" void *Test3_ThreadCount (void *pdwParam)
{
   volatile LONG *pdw = (LONG*)pdwParam;

   pthread_t pMe;
   if ((pMe = pthread_self()) == NULL)
      return (void*)0;

   try {
      struct rx_queue *pNow = (rx_queue*)pMe;

      for (struct rx_queue *pWalk = pNow->next; pWalk != pNow; pWalk = pWalk->next)
         ++(*pdw);

    } catch(...) {
       *pdw = 0;
    }

   return (void*)*pdw;
}

size_t Test3_CountActiveQueue (void)
{
   DWORD dwArg = 0;
   pthread_t pThread;
   pthread_create (&pThread, NULL, Test3_ThreadCount, (void *)&dwArg);

   DWORD status;
   int rc;
   if ((rc = pthread_join (pThread, (void**)&status)) != 0)
      {
      printf ("   3: counter: pthread_join failed; rc=%lu\n", rc);
      return 0;
      }

   return dwArg;
}

BOOL Test3 (void)
{
   BOOL rc = TRUE;
   printf ("Begin Test3:\n");

   // Find the size of the active queue
   //
   size_t cInitialQueueSize;
   if ((cInitialQueueSize = Test3_CountActiveQueue()) == 0)
      {
      printf ("   3: unable to determine active queue size\n");
      rc = FALSE;
      }
   else
      {
      // Start two Win32 threads and two pthreads. We'll use a single, common
      // parameter for each thread: dwSignal. Each thread will increment the
      // signal param when it's ready for us to go; once that's done, a
      // thread will kill itself and inc the signal *again* each die we
      // increment the signal:
      //
      //    starts at 0
      //    thread 1 wakes up and incs signal to 1
      //    thread 2 wakes up and incs signal to 2
      //    thread 3 wakes up and incs signal to 3
      //    thread 4 wakes up and incs signal to 4
      //    we notice signal is 4, and we increment signal to 5
      //    thread 1 notices signal is 5, incs to 6 and dies
      //    we notice signal is 6, and we increment signal to 7
      //    thread 1 notices signal is 7, incs to 8 and dies
      //    we notice signal is 8, and we increment signal to 9
      //    thread 1 notices signal is 9, incs to 10 and dies
      //    we notice signal is 10, and we increment signal to 11
      //    thread 1 notices signal is 11, incs to 12 and dies
      //    we notice signal is 12 and finish our tests
      //
      volatile LONG dwSignal = 0;
      printf ("   3: creating test threads\n");

      DWORD idThread;
      CreateThread (0, 0, Test3_Thread1, (LPVOID)&dwSignal, 0, &idThread);
      Sleep (500);

      CreateThread (0, 0, Test3_Thread2, (LPVOID)&dwSignal, 0, &idThread);
      Sleep (500);

      pthread_attr_t attr;
      attr.is_joinable = PTHREAD_CREATE_DETACHED;

      pthread_t hThread;
      pthread_create (&hThread, &attr, Test3_Thread3, (void *)&dwSignal);
      Sleep (500);

      pthread_create (&hThread, &attr, Test3_Thread4, (void *)&dwSignal);
      Sleep (500);

      // Count the active queue again as soon as we're sure all the threads
      // are ready for us to do so.
      //
      while (dwSignal != 4)
         ;

      size_t cQueueSize;
      if ((cQueueSize = Test3_CountActiveQueue()) == 0)
         {
         printf ("   3: unable to determine active queue size\n");
         rc = FALSE;
         }
      else if (cQueueSize != cInitialQueueSize+4)
         {
         printf ("   3: error: active queue wasn't increased by 4\n");
         rc = FALSE;
         }
      else while (rc && (dwSignal < 12))
         {
         LONG dwTarget = dwSignal +2;
         InterlockedIncrement ((LONG*)&dwSignal);

         // wait for a thread to notice the change and kill itself
         while (dwSignal != dwTarget)
            ;
         Sleep (500); // give a little time for cleanup after thread incremented

         if ((cQueueSize = Test3_CountActiveQueue()) == 0)
            {
            printf ("   3: unable to determine active queue size (dwSignal=%ld)\n", dwSignal);
            rc = FALSE;
            }
         else if (cQueueSize != (cInitialQueueSize + 4 - ((dwTarget -4) /2)))
            {
            printf ("   3: active queue size did not diminish (dwSignal=%ld)\n", dwSignal);
            rc = FALSE;
            }
         }
      }

   printf ("--> Test3 %s\n", (rc) ? TEXT("succeeded") : TEXT("FAILED"));
   return rc;
}


/*
 * main - Runs all tests (unless the command-line says otherwise)
 *
 */
int main(int argc, char **argv)
{
   DWORD iRunTest[nPASSES_MAX];
   for (size_t ii = 0; ii < nPASSES_MAX; ++ii)
      iRunTest[ii] = (ii < nTESTS) ? (ii+1) : 0;

   ii = 0;
   for (--argc,++argv; argc; --argc,++argv)
      {
      DWORD iTest;
      if ( ((iTest = (size_t)atol (*argv)) > 0) && (iTest <= nTESTS) )
         {
         if (ii == 0)
            memset (iRunTest, 0x00, sizeof(iRunTest));
         if (ii < nPASSES_MAX)
            iRunTest[ ii++ ] = iTest;
         }
      }

   BOOL rc = TRUE;

   for (ii = 0; ii < nPASSES_MAX; ++ii)
      {
      if (iRunTest[ ii ] == 0)
         break;
      else switch (iRunTest[ ii ])
         {
         case 1: if (!Test1()) rc = FALSE;  break;
         case 2: if (!Test2()) rc = FALSE;  break;
         case 3: if (!Test3()) rc = FALSE;  break;
         }
      }

   return rc;
}

