/*
 *
 * Copyright (c) 2004
 * John Maddock
 *
 * Use, modification and distribution are subject to the 
 * Boost Software License, Version 1.0. (See accompanying file 
 * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 *
 */
 
 /*
  *   LOCATION:    see http://www.boost.org for most recent version.
  *   FILE         static_mutex_test.cpp
  *   VERSION      see <boost/version.hpp>
  *   DESCRIPTION: test program for boost::static_mutex.
  */

#include <iostream>
#include <boost/regex/pending/static_mutex.hpp>
#include <boost/thread/thread.hpp>
#include <boost/timer.hpp>

//
// we cannot use the regular Boost.Test in here: it is not thread safe
// and calls to BOOST_TEST will eventually crash on some compilers 
// (Borland certainly) due to race conditions inside the Boost.Test lib.
//
#define BOOST_TEST(pred) if(!(pred)) failed_test(__FILE__, __LINE__, BOOST_STRINGIZE(pred));

int total_failures = 0;
void failed_test(const char* file, int line, const char* pred)
{
   static boost::static_mutex mut = BOOST_STATIC_MUTEX_INIT ;
   boost::static_mutex::scoped_lock guard(mut);
   ++total_failures;
   std::cout << "Failed test in \"" << file << "\" at line " << line << ": " << pred << std::endl;
}

void print_cycles(int c)
{
   static boost::static_mutex mut = BOOST_STATIC_MUTEX_INIT ;
   boost::static_mutex::scoped_lock guard(mut);
   std::cout << "Thread exited after " << c << " cycles." << std::endl;
}

bool sufficient_time()
{
   // return true if enough time has passed since the tests began:
   static boost::static_mutex mut = BOOST_STATIC_MUTEX_INIT ;
   boost::static_mutex::scoped_lock guard(mut);
   static boost::timer t;
   // is 10 seconds enough?
   return t.elapsed() >= 10.0;
}

// define three trivial test proceedures:
bool t1()
{
   static boost::static_mutex mut = BOOST_STATIC_MUTEX_INIT ;
   static int has_lock = 0;
   static int data = 10000;

   boost::static_mutex::scoped_lock guard(mut);
   BOOST_TEST(++has_lock == 1);
   BOOST_TEST(guard.locked());
   BOOST_TEST(guard);
   bool result = (--data > 0) ? true : false;
   BOOST_TEST(--has_lock == 0);
   return result;
}

bool t2()
{
   static boost::static_mutex mut = BOOST_STATIC_MUTEX_INIT ;
   static int has_lock = 0;
   static int data = 10000;

   boost::static_mutex::scoped_lock guard(mut, false);
   BOOST_TEST(0 == guard.locked());
   BOOST_TEST(!guard);
   guard.lock();
   BOOST_TEST(++has_lock == 1);
   BOOST_TEST(guard.locked());
   BOOST_TEST(guard);
   bool result = (--data > 0) ? true : false;
   BOOST_TEST(--has_lock == 0);
   guard.unlock();
   BOOST_TEST(0 == guard.locked());
   BOOST_TEST(!guard);
   return result;
}

bool t3()
{
   static boost::static_mutex mut = BOOST_STATIC_MUTEX_INIT ;
   static int has_lock = 0;
   static int data = 10000;

   boost::static_mutex::scoped_lock guard(mut);
   BOOST_TEST(++has_lock == 1);
   BOOST_TEST(guard.locked());
   BOOST_TEST(guard);
   bool result = (--data > 0) ? true : false;
   BOOST_TEST(--has_lock == 0);
   return result;
}

// define their thread procs:
void thread1_proc()
{
   int cycles = 0;
   while(!sufficient_time())
   {
      t1();
      t2();
      ++cycles;
   }
   print_cycles(cycles);
}

void thread2_proc()
{
   int cycles = 0;
   while(!sufficient_time())
   {
      t2();
      t3();
      ++cycles;
   }
   print_cycles(cycles);
}

void thread3_proc()
{
   int cycles = 0;
   while(!sufficient_time())
   {
      t1();
      t3();
      ++cycles;
   }
   print_cycles(cycles);
}

// make sure that at least one of our test proceedures 
// is called during program startup:
struct startup1
{
   startup1()
   {
      t1();
   }
   ~startup1()
   {
      t1();
   }
};

startup1 up1;

int main()
{
   BOOST_TEST(0 != &up1);
   
   boost::thread thrd1(&thread1_proc);
   boost::thread thrd2(&thread1_proc);
   boost::thread thrd3(&thread2_proc);
   boost::thread thrd4(&thread2_proc);
   boost::thread thrd5(&thread3_proc);
   boost::thread thrd6(&thread3_proc);

   thrd1.join();
   thrd2.join();
   thrd3.join();
   thrd4.join();
   thrd5.join();
   thrd6.join();

   return total_failures;
}
