/*

 * 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;

}



