/* Copyright (C) 2003 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#include <ndb_global.h>
#include <my_pthread.h>

#include "NDBT.hpp"
#include "NDBT_Test.hpp"

#include <PortDefs.h>

#include <getarg.h>
#include <time.h>

// No verbose outxput

NDBT_Context::NDBT_Context(Ndb_cluster_connection& con)
  : m_cluster_connection(con)
{
  tab = NULL;
  suite = NULL;
  testcase = NULL;
  ndb = NULL;
  records = 1;
  loops = 1;
  stopped = false;
  remote_mgm ="";
  propertyMutexPtr = NdbMutex_Create();
  propertyCondPtr = NdbCondition_Create();
}

 
char * NDBT_Context::getRemoteMgm() const {
  return remote_mgm;
} 
void NDBT_Context::setRemoteMgm(char * mgm) {
  remote_mgm = strdup(mgm);
} 


NDBT_Context::~NDBT_Context(){
  NdbCondition_Destroy(propertyCondPtr);
  NdbMutex_Destroy(propertyMutexPtr);
}

const NdbDictionary::Table* NDBT_Context::getTab(){
  assert(tab != NULL);
  return tab;
}

NDBT_TestSuite* NDBT_Context::getSuite(){ 
  assert(suite != NULL);
  return suite;
}

NDBT_TestCase* NDBT_Context::getCase(){ 
  assert(testcase != NULL);
  return testcase;
}

int NDBT_Context::getNumRecords() const{ 
  return records;   
}

int NDBT_Context::getNumLoops() const{ 
  return loops;
}

int NDBT_Context::getNoOfRunningSteps() const {
  return testcase->getNoOfRunningSteps();
  
}
int NDBT_Context::getNoOfCompletedSteps() const {
  return testcase->getNoOfCompletedSteps();
}


Uint32 NDBT_Context::getProperty(const char* _name, Uint32 _default){ 
  Uint32 val;
  NdbMutex_Lock(propertyMutexPtr);
  if(!props.get(_name, &val))
    val = _default;
  NdbMutex_Unlock(propertyMutexPtr);
  return val;
}

bool NDBT_Context::getPropertyWait(const char* _name, Uint32 _waitVal){ 
  bool result;
  NdbMutex_Lock(propertyMutexPtr);
  Uint32 val =! _waitVal;
  
  while((!props.get(_name, &val) || (props.get(_name, &val) && val != _waitVal)) &&  
	!stopped)
    NdbCondition_Wait(propertyCondPtr,
		      propertyMutexPtr);
  result = (val == _waitVal);
  NdbMutex_Unlock(propertyMutexPtr);
  return stopped;
}

const char* NDBT_Context::getProperty(const char* _name, const char* _default){ 
  const char* val;
  NdbMutex_Lock(propertyMutexPtr);
  if(!props.get(_name, &val))
    val = _default;
  NdbMutex_Unlock(propertyMutexPtr);
  return val;
}

const char* NDBT_Context::getPropertyWait(const char* _name, const char* _waitVal){ 
  const char* val;
  NdbMutex_Lock(propertyMutexPtr);
  while(!props.get(_name, &val) && (strcmp(val, _waitVal)==0))
    NdbCondition_Wait(propertyCondPtr,
		      propertyMutexPtr);

  NdbMutex_Unlock(propertyMutexPtr);
  return val;
}

void  NDBT_Context::setProperty(const char* _name, Uint32 _val){ 
  NdbMutex_Lock(propertyMutexPtr);
  const bool b = props.put(_name, _val, true);
  assert(b == true);
  NdbMutex_Unlock(propertyMutexPtr);
}
void
NDBT_Context::decProperty(const char * name){
  NdbMutex_Lock(propertyMutexPtr);
  Uint32 val = 0;
  if(props.get(name, &val)){
    assert(val > 0);
    props.put(name, (val - 1), true);
  }
  NdbCondition_Broadcast(propertyCondPtr);
  NdbMutex_Unlock(propertyMutexPtr);
}
void
NDBT_Context::incProperty(const char * name){
  NdbMutex_Lock(propertyMutexPtr);
  Uint32 val = 0;
  props.get(name, &val);
  props.put(name, (val + 1), true);
  NdbCondition_Broadcast(propertyCondPtr);
  NdbMutex_Unlock(propertyMutexPtr);
}

void  NDBT_Context::setProperty(const char* _name, const char* _val){ 
  NdbMutex_Lock(propertyMutexPtr);
  const bool b = props.put(_name, _val);
  assert(b == true);
  NdbMutex_Unlock(propertyMutexPtr);
}

void NDBT_Context::stopTest(){ 
  NdbMutex_Lock(propertyMutexPtr);
  g_info << "|- stopTest called" << endl;
  stopped = true;
  NdbCondition_Broadcast(propertyCondPtr);
  NdbMutex_Unlock(propertyMutexPtr);
}

bool NDBT_Context::isTestStopped(){ 
  NdbMutex_Lock(propertyMutexPtr);
  bool val = stopped;
  NdbMutex_Unlock(propertyMutexPtr);
  return val;
}

void NDBT_Context::wait(){
  NdbMutex_Lock(propertyMutexPtr);
  NdbCondition_Wait(propertyCondPtr,
		    propertyMutexPtr);
  NdbMutex_Unlock(propertyMutexPtr);
}

void NDBT_Context::wait_timeout(int msec){
  NdbMutex_Lock(propertyMutexPtr);
  NdbCondition_WaitTimeout(propertyCondPtr,
			   propertyMutexPtr,
			   msec);
  NdbMutex_Unlock(propertyMutexPtr);
}

void NDBT_Context::broadcast(){
  NdbMutex_Lock(propertyMutexPtr);
  NdbCondition_Broadcast(propertyCondPtr);
  NdbMutex_Unlock(propertyMutexPtr);
}

Uint32 NDBT_Context::getDbProperty(const char*){ 
  abort();
  return 0;
}

bool NDBT_Context::setDbProperty(const char*, Uint32){ 
  abort();
  return true;
}

void NDBT_Context::setTab(const NdbDictionary::Table* ptab){ 
  assert(ptab != NULL);
  tab = ptab;
}

void NDBT_Context::setSuite(NDBT_TestSuite* psuite){ 
  assert(psuite != NULL);
  suite = psuite;
}

void NDBT_Context::setCase(NDBT_TestCase* pcase){ 
  assert(pcase != NULL);
  testcase = pcase;
}

void NDBT_Context::setNumRecords(int _records){ 
  records = _records;
  
}

void NDBT_Context::setNumLoops(int _loops){ 
  loops = _loops;
}

NDBT_Step::NDBT_Step(NDBT_TestCase* ptest, const char* pname, 
		     NDBT_TESTFUNC* pfunc): name(pname){
  assert(pfunc != NULL);
  func = pfunc;
  testcase = ptest;
  step_no = -1;
}

int NDBT_Step::execute(NDBT_Context* ctx) {
  assert(ctx != NULL);

  int result;  

  g_info << "  |- " << name << " started [" << ctx->suite->getDate() << "]" 
	 << endl;
  
  result = setUp(ctx->m_cluster_connection);
  if (result != NDBT_OK){
    return result;
  }

  result = func(ctx, this);

  if (result != NDBT_OK) {
    g_err << "  |- " << name << " FAILED [" << ctx->suite->getDate() 
	   << "]" << endl;
  }	 
   else {
    g_info << "  |- " << name << " PASSED [" << ctx->suite->getDate() << "]"
	   << endl;
  }
  
  tearDown();
  
  return result;
}

void NDBT_Step::setContext(NDBT_Context* pctx){
  assert(pctx != NULL);
  m_ctx = pctx;
}

NDBT_Context* NDBT_Step::getContext(){
  assert(m_ctx != NULL);
  return m_ctx;
}

NDBT_NdbApiStep::NDBT_NdbApiStep(NDBT_TestCase* ptest, 
			   const char* pname, 
			   NDBT_TESTFUNC* pfunc)
  : NDBT_Step(ptest, pname, pfunc),
    ndb(NULL) {
}


int
NDBT_NdbApiStep::setUp(Ndb_cluster_connection& con){
  ndb = new Ndb(&con, "TEST_DB" );
  ndb->init(1024);
  
  int result = ndb->waitUntilReady(300); // 5 minutes
  if (result != 0){
    g_err << name << ": Ndb was not ready" << endl;
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

void 
NDBT_NdbApiStep::tearDown(){
  delete ndb;
  ndb = NULL;
}

Ndb* NDBT_NdbApiStep::getNdb(){ 
  assert(ndb != NULL);
  return ndb;
}


NDBT_ParallelStep::NDBT_ParallelStep(NDBT_TestCase* ptest, 
				     const char* pname, 
				     NDBT_TESTFUNC* pfunc)
  : NDBT_NdbApiStep(ptest, pname, pfunc) {
}
NDBT_Verifier::NDBT_Verifier(NDBT_TestCase* ptest, 
			     const char* pname, 
			     NDBT_TESTFUNC* pfunc)
  : NDBT_NdbApiStep(ptest, pname, pfunc) {
}
NDBT_Initializer::NDBT_Initializer(NDBT_TestCase* ptest, 
				   const char* pname, 
				   NDBT_TESTFUNC* pfunc)
  : NDBT_NdbApiStep(ptest, pname, pfunc) {
}
NDBT_Finalizer::NDBT_Finalizer(NDBT_TestCase* ptest, 
			       const char* pname, 
			       NDBT_TESTFUNC* pfunc)
  : NDBT_NdbApiStep(ptest, pname, pfunc) {
}

NDBT_TestCase::NDBT_TestCase(NDBT_TestSuite* psuite, 
			     const char* pname, 
			     const char* pcomment) : 
  name(strdup(pname)) ,
  comment(strdup(pcomment)),
  suite(psuite)
{
  _name.assign(pname);
  _comment.assign(pcomment);
  name= _name.c_str();
  comment= _comment.c_str();
  assert(suite != NULL);
}

NDBT_TestCaseImpl1::NDBT_TestCaseImpl1(NDBT_TestSuite* psuite, 
				       const char* pname, 
				       const char* pcomment) : 
  NDBT_TestCase(psuite, pname, pcomment){

  numStepsOk = 0;
  numStepsFail = 0;
  numStepsCompleted = 0;
  waitThreadsMutexPtr = NdbMutex_Create();
  waitThreadsCondPtr = NdbCondition_Create();
}

NDBT_TestCaseImpl1::~NDBT_TestCaseImpl1(){
  NdbCondition_Destroy(waitThreadsCondPtr);
  NdbMutex_Destroy(waitThreadsMutexPtr);
  size_t i;
  for(i = 0; i < initializers.size();  i++)
    delete initializers[i];
  initializers.clear();
  for(i = 0; i < verifiers.size();  i++)
    delete verifiers[i];
  verifiers.clear();
  for(i = 0; i < finalizers.size();  i++)
    delete finalizers[i];
  finalizers.clear();
  for(i = 0; i < steps.size();  i++)
    delete steps[i];
  steps.clear();
  results.clear();
  for(i = 0; i < testTables.size();  i++)
    delete testTables[i];
  testTables.clear();
  for(i = 0; i < testResults.size();  i++)
    delete testResults[i];
  testResults.clear();

}

int NDBT_TestCaseImpl1::addStep(NDBT_Step* pStep){
  assert(pStep != NULL);
  steps.push_back(pStep);
  pStep->setStepNo(steps.size());
  int res = NORESULT;
  results.push_back(res);
  return 0;
}

int NDBT_TestCaseImpl1::addVerifier(NDBT_Verifier* pVerifier){
  assert(pVerifier != NULL);
  verifiers.push_back(pVerifier);
  return 0;
}

int NDBT_TestCaseImpl1::addInitializer(NDBT_Initializer* pInitializer){
  assert(pInitializer != NULL);
  initializers.push_back(pInitializer);
  return 0;
}

int NDBT_TestCaseImpl1::addFinalizer(NDBT_Finalizer* pFinalizer){
  assert(pFinalizer != NULL);
  finalizers.push_back(pFinalizer);
  return 0;
}

void NDBT_TestCaseImpl1::addTable(const char* tableName, bool isVerify) {
  assert(tableName != NULL);
  const NdbDictionary::Table* pTable = NDBT_Tables::getTable(tableName);
  assert(pTable != NULL);
  testTables.push_back(pTable);
  isVerifyTables = isVerify;
}

bool NDBT_TestCaseImpl1::tableExists(NdbDictionary::Table* aTable) {
  for (unsigned i = 0; i < testTables.size(); i++) {
    if (strcasecmp(testTables[i]->getName(), aTable->getName()) == 0) {
      return true;
    }
  }
  return false;
}

bool NDBT_TestCaseImpl1::isVerify(const NdbDictionary::Table* aTable) {
  if (testTables.size() > 0) {
    int found = false;
    // OK, we either exclude or include this table in the actual test
    for (unsigned i = 0; i < testTables.size(); i++) {
      if (strcasecmp(testTables[i]->getName(), aTable->getName()) == 0) {
	// Found one!
	if (isVerifyTables) {
	  // Found one to test
	  found = true;
	} else {
	  // Skip this one!
	  found = false;
	}
      }
    } // for
    return found;
  } else {
    // No included or excluded test tables, i.e., all tables should be      
    // tested
    return true;
  }
  return true;
}

void  NDBT_TestCase::setProperty(const char* _name, Uint32 _val){ 
  const bool b = props.put(_name, _val);
  assert(b == true);
}

void  NDBT_TestCase::setProperty(const char* _name, const char* _val){ 
  const bool b = props.put(_name, _val);
  assert(b == true);
}


void *
runStep(void * s){
  assert(s != NULL);
  NDBT_Step* pStep = (NDBT_Step*)s;
  NDBT_Context* ctx = pStep->getContext();
  assert(ctx != NULL);
   // Execute function
  int res = pStep->execute(ctx);
  if(res != NDBT_OK){
    ctx->stopTest();
  }
  // Report 
  NDBT_TestCaseImpl1* pCase = (NDBT_TestCaseImpl1*)ctx->getCase();
  assert(pCase != NULL);
  pCase->reportStepResult(pStep, res);
  return NULL;
}

extern "C" 
void *
runStep_C(void * s)
{
  runStep(s);
  return NULL;
}


void NDBT_TestCaseImpl1::startStepInThread(int stepNo, NDBT_Context* ctx){  
  NDBT_Step* pStep = steps[stepNo];
  pStep->setContext(ctx);
  char buf[16];
  BaseString::snprintf(buf, sizeof(buf), "step_%d", stepNo);
  NdbThread* pThread = NdbThread_Create(runStep_C,
					(void**)pStep,
					65535,
					buf, 
					NDB_THREAD_PRIO_LOW);
  threads.push_back(pThread);
}

void NDBT_TestCaseImpl1::waitSteps(){
  NdbMutex_Lock(waitThreadsMutexPtr);
  while(numStepsCompleted != steps.size())
    NdbCondition_Wait(waitThreadsCondPtr,
		     waitThreadsMutexPtr);

  unsigned completedSteps = 0;  
  unsigned i;
  for(i=0; i<steps.size(); i++){
    if (results[i] != NORESULT){
      completedSteps++;
      if (results[i] == NDBT_OK)
	numStepsOk++;
      else
	numStepsFail++;
    }       
  }
  assert(completedSteps == steps.size());
  assert(completedSteps == numStepsCompleted);
  
  NdbMutex_Unlock(waitThreadsMutexPtr);
  void *status;
  for(i=0; i<steps.size();i++){
    NdbThread_WaitFor(threads[i], &status);
    NdbThread_Destroy(&threads[i]);   
  }
  threads.clear();
}


int
NDBT_TestCaseImpl1::getNoOfRunningSteps() const {
  return steps.size() - getNoOfCompletedSteps();
}

int 
NDBT_TestCaseImpl1::getNoOfCompletedSteps() const {
  return numStepsCompleted;
}

void NDBT_TestCaseImpl1::reportStepResult(const NDBT_Step* pStep, int result){
  NdbMutex_Lock(waitThreadsMutexPtr);
  assert(pStep != NULL);
  for (unsigned i = 0; i < steps.size(); i++){
    if(steps[i] != NULL && steps[i] == pStep){
      results[i] = result;
      numStepsCompleted++;
    }
  }
  if(numStepsCompleted == steps.size()){
    NdbCondition_Signal(waitThreadsCondPtr);
  }
  NdbMutex_Unlock(waitThreadsMutexPtr);
}


int NDBT_TestCase::execute(NDBT_Context* ctx){
  int res;

  ndbout << "- " << name << " started [" << ctx->suite->getDate()
	 << "]" << endl;

  ctx->setCase(this);

  // Copy test case properties to ctx
  Properties::Iterator it(&props);
  for(const char * key = it.first(); key != 0; key = it.next()){

    PropertiesType pt;
    const bool b = props.getTypeOf(key, &pt);
    assert(b == true);
    switch(pt){
    case PropertiesType_Uint32:{
      Uint32 val;
      props.get(key, &val);
      ctx->setProperty(key, val);
      break;
    }
    case PropertiesType_char:{
      const char * val;
      props.get(key, &val);
      ctx->setProperty(key, val);
      break;
    }
    default:
      abort();
    }
  }

  // start timer so that we get a time even if
  // test case consist only of initializer
  startTimer(ctx);
  
  if ((res = runInit(ctx)) == NDBT_OK){
    // If initialiser is ok, run steps
    
    res = runSteps(ctx);
    if (res == NDBT_OK){
      // If steps is ok, run verifier
      res = runVerifier(ctx);
    } 
    
  }

  stopTimer(ctx);
  printTimer(ctx);

  // Always run finalizer to clean up db
  runFinal(ctx); 

  if (res == NDBT_OK) {
    ndbout << "- " << name << " PASSED [" << ctx->suite->getDate() << "]" 
	   << endl;
  }
  else {
    ndbout << "- " << name << " FAILED [" << ctx->suite->getDate() << "]" 
	   << endl;
  }
  return res;
}

void NDBT_TestCase::startTimer(NDBT_Context* ctx){
  timer.doStart();
}

void NDBT_TestCase::stopTimer(NDBT_Context* ctx){
  timer.doStop();
}

void NDBT_TestCase::printTimer(NDBT_Context* ctx){
  if (suite->timerIsOn()){
    g_info << endl; 
    timer.printTestTimer(ctx->getNumLoops(), ctx->getNumRecords());
  }
}

int NDBT_TestCaseImpl1::runInit(NDBT_Context* ctx){
  int res = NDBT_OK;
  for (unsigned i = 0; i < initializers.size(); i++){
    initializers[i]->setContext(ctx);
    res = initializers[i]->execute(ctx);
    if (res != NDBT_OK)
      break;
  }
  return res;
}

int NDBT_TestCaseImpl1::runSteps(NDBT_Context* ctx){
  int res = NDBT_OK;

  // Reset variables
  numStepsOk = 0;
  numStepsFail = 0;
  numStepsCompleted = 0;
  unsigned i;
  for (i = 0; i < steps.size(); i++)
    startStepInThread(i, ctx);
  waitSteps();

  for(i = 0; i < steps.size(); i++)
    if (results[i] != NDBT_OK)
      res = NDBT_FAILED;
  return res;
}

int NDBT_TestCaseImpl1::runVerifier(NDBT_Context* ctx){
  int res = NDBT_OK;
  for (unsigned i = 0; i < verifiers.size(); i++){
    verifiers[i]->setContext(ctx);
    res = verifiers[i]->execute(ctx);
    if (res != NDBT_OK)
      break;
  }
  return res;
}

int NDBT_TestCaseImpl1::runFinal(NDBT_Context* ctx){
  int res = NDBT_OK;
  for (unsigned i = 0; i < finalizers.size(); i++){
    finalizers[i]->setContext(ctx);
    res = finalizers[i]->execute(ctx);
    if (res != NDBT_OK)
      break;
  }
  return res;
}


void NDBT_TestCaseImpl1::saveTestResult(const NdbDictionary::Table* ptab, 
					int result){
  testResults.push_back(new NDBT_TestCaseResult(ptab->getName(), 
						result,
						timer.elapsedTime()));
}

void NDBT_TestCaseImpl1::printTestResult(){

  char buf[255];
  ndbout << name<<endl;

  for (unsigned i = 0; i < testResults.size(); i++){
    NDBT_TestCaseResult* tcr = testResults[i];
    const char* res;
    if (tcr->getResult() == NDBT_OK)
      res = "OK";
    else if (tcr->getResult() == NDBT_FAILED)
      res = "FAIL";
    else if (tcr->getResult() == FAILED_TO_CREATE)
      res = "FAILED TO CREATE TABLE";
    else if (tcr->getResult() == FAILED_TO_DISCOVER)
      res = "FAILED TO DISCOVER TABLE";
    BaseString::snprintf(buf, 255," %-10s %-5s %-20s", tcr->getName(), res, tcr->getTimeStr());
    ndbout << buf<<endl;    
  }
}





NDBT_TestSuite::NDBT_TestSuite(const char* pname):name(pname){
   numTestsOk = 0;
   numTestsFail = 0;
   numTestsExecuted = 0;
   records = 0;
   loops = 0;
   createTable = true;
}


NDBT_TestSuite::~NDBT_TestSuite(){
  for(unsigned i=0; i<tests.size(); i++){
    delete tests[i];
  }
  tests.clear();
}

void NDBT_TestSuite::setCreateTable(bool _flag){
  createTable = _flag;
}

bool NDBT_TestSuite::timerIsOn(){
  return (timer != 0);
}

int NDBT_TestSuite::addTest(NDBT_TestCase* pTest){
  assert(pTest != NULL);
  tests.push_back(pTest);
  return 0;
}

int NDBT_TestSuite::executeAll(Ndb_cluster_connection& con,
			       const char* _testname){

  if(tests.size() == 0)
    return NDBT_FAILED;
  Ndb ndb(&con, "TEST_DB");
  ndb.init(1024);

  int result = ndb.waitUntilReady(500); // 5 minutes
  if (result != 0){
    g_err << name <<": Ndb was not ready" << endl;
    return NDBT_FAILED;
  }

  ndbout << name << " started [" << getDate() << "]" << endl;

  testSuiteTimer.doStart();

  for (int t=0; t < NDBT_Tables::getNumTables(); t++){
    const NdbDictionary::Table* ptab = NDBT_Tables::getTable(t);
    ndbout << "|- " << ptab->getName() << endl;
    execute(con, &ndb, ptab, _testname);
  }
  testSuiteTimer.doStop();
  return reportAllTables(_testname);
}

int 
NDBT_TestSuite::executeOne(Ndb_cluster_connection& con,
			   const char* _tabname, const char* _testname){
  
  if(tests.size() == 0)
    return NDBT_FAILED;
  Ndb ndb(&con, "TEST_DB");
  ndb.init(1024);

  int result = ndb.waitUntilReady(300); // 5 minutes
  if (result != 0){
    g_err << name <<": Ndb was not ready" << endl;
    return NDBT_FAILED;
  }

  ndbout << name << " started [" << getDate() << "]" << endl;

  const NdbDictionary::Table* ptab = NDBT_Tables::getTable(_tabname);
  if (ptab == NULL)
    return NDBT_FAILED;

  ndbout << "|- " << ptab->getName() << endl;

  execute(con, &ndb, ptab, _testname);

  if (numTestsFail > 0){
    return NDBT_FAILED;
  }else{
    return NDBT_OK;
  }
}

int 
NDBT_TestSuite::executeOneCtx(Ndb_cluster_connection& con,
			   const NdbDictionary::Table *ptab, const char* _testname){
  
  testSuiteTimer.doStart(); 
  
  do{
    if(tests.size() == 0)
      break;

    Ndb ndb(&con, "TEST_DB");
    ndb.init(1024);

    int result = ndb.waitUntilReady(300); // 5 minutes
    if (result != 0){
      g_err << name <<": Ndb was not ready" << endl;
      break;
    }

    ndbout << name << " started [" << getDate() << "]" << endl;
    ndbout << "|- " << ptab->getName() << endl;

    for (unsigned t = 0; t < tests.size(); t++){

      if (_testname != NULL && 
	      strcasecmp(tests[t]->getName(), _testname) != 0)
        continue;

      tests[t]->initBeforeTest();
    
      ctx = new NDBT_Context(con);
      ctx->setTab(ptab);
      ctx->setNumRecords(records);
      ctx->setNumLoops(loops);
      if(remote_mgm != NULL)
        ctx->setRemoteMgm(remote_mgm);
      ctx->setSuite(this);
    
      result = tests[t]->execute(ctx);
      if (result != NDBT_OK)
        numTestsFail++;
      else
        numTestsOk++;
      numTestsExecuted++;

    delete ctx;
  }

    if (numTestsFail > 0)
      break;
  }while(0);

  testSuiteTimer.doStop();
  int res = report(_testname);
  return NDBT_ProgramExit(res);
}

void NDBT_TestSuite::execute(Ndb_cluster_connection& con,
			     Ndb* ndb, const NdbDictionary::Table* pTab, 
			     const char* _testname){
  int result; 

 
  for (unsigned t = 0; t < tests.size(); t++){

    if (_testname != NULL && 
	strcasecmp(tests[t]->getName(), _testname) != 0)
      continue;

    if (tests[t]->isVerify(pTab) == false) {
      continue;
    }

    tests[t]->initBeforeTest();

    NdbDictionary::Dictionary* pDict = ndb->getDictionary();
    const NdbDictionary::Table* pTab2 = pDict->getTable(pTab->getName());
    if (createTable == true){

      if(pTab2 != 0 && pDict->dropTable(pTab->getName()) != 0){
	numTestsFail++;
	numTestsExecuted++;
	g_err << "ERROR0: Failed to drop table " << pTab->getName() << endl;
	tests[t]->saveTestResult(pTab, FAILED_TO_CREATE);
	continue;
      }
      
      if(NDBT_Tables::createTable(ndb, pTab->getName()) != 0){
	numTestsFail++;
	numTestsExecuted++;
	g_err << "ERROR1: Failed to create table " << pTab->getName()
              << pDict->getNdbError() << endl;
	tests[t]->saveTestResult(pTab, FAILED_TO_CREATE);
	continue;
      }
      pTab2 = pDict->getTable(pTab->getName());
    } else if(!pTab2) {
      pTab2 = pTab;
    } 
    
    ctx = new NDBT_Context(con);
    ctx->setTab(pTab2);
    ctx->setNumRecords(records);
    ctx->setNumLoops(loops);
    if(remote_mgm != NULL)
      ctx->setRemoteMgm(remote_mgm);
    ctx->setSuite(this);
    
    result = tests[t]->execute(ctx);
    tests[t]->saveTestResult(pTab, result);
    if (result != NDBT_OK)
      numTestsFail++;
    else
      numTestsOk++;
    numTestsExecuted++;

    if (result == NDBT_OK && createTable == true){
      pDict->dropTable(pTab->getName());
    }
    
    delete ctx;
  }
}




int 
NDBT_TestSuite::report(const char* _tcname){
  int result;
  ndbout << "Completed " << name << " [" << getDate() << "]" << endl;
  printTestCaseSummary(_tcname);
  ndbout << numTestsExecuted << " test(s) executed" << endl;
  ndbout << numTestsOk << " test(s) OK" 
	 << endl;
  if(numTestsFail > 0)
    ndbout << numTestsFail << " test(s) failed"
	   << endl;
  testSuiteTimer.printTotalTime();
  if (numTestsFail > 0 || numTestsExecuted == 0){
    result = NDBT_FAILED;
  }else{
    result = NDBT_OK;
  }
  return result;
}

void NDBT_TestSuite::printTestCaseSummary(const char* _tcname){
  ndbout << "= SUMMARY OF TEST EXECUTION ==============" << endl;
  for (unsigned t = 0; t < tests.size(); t++){
    if (_tcname != NULL && 
	strcasecmp(tests[t]->getName(), _tcname) != 0)
      continue;

    tests[t]->printTestResult();
  }
  ndbout << "==========================================" << endl;
}

int NDBT_TestSuite::reportAllTables(const char* _testname){
  int result;
  ndbout << "Completed running test [" << getDate() << "]" << endl;
  const int totalNumTests = numTestsExecuted;
  printTestCaseSummary(_testname);
  ndbout << numTestsExecuted<< " test(s) executed" << endl;
  ndbout << numTestsOk << " test(s) OK("
	 <<(int)(((float)numTestsOk/totalNumTests)*100.0) <<"%)" 
	 << endl;
  if(numTestsFail > 0)
    ndbout << numTestsFail << " test(s) failed("
	   <<(int)(((float)numTestsFail/totalNumTests)*100.0) <<"%)" 
	   << endl;
  testSuiteTimer.printTotalTime();
  if (numTestsExecuted > 0){
    if (numTestsFail > 0){
      result = NDBT_FAILED;
    }else{
      result = NDBT_OK;
    }
  } else {
    result = NDBT_FAILED;
  }
  return result;
}

int NDBT_TestSuite::execute(int argc, const char** argv){
  int res = NDBT_FAILED;
  /* Arguments:
       Run only a subset of tests
       -n testname Which test to run
       Recommendations to test functions:
       --records Number of records to use(default: 10000)
       --loops Number of loops to execute in the test(default: 100)

       Other parameters should:
       * be calculated from the above two parameters 
       * be divided into different test cases, ex. one testcase runs
         with FragmentType = Single and another perfoms the same 
         test with FragmentType = Large
       * let the test case iterate over all/subset of appropriate parameters
         ex. iterate over FragmentType = Single to FragmentType = AllLarge

       Remeber that the intention is that it should be _easy_ to run 
       a complete test suite without any greater knowledge of what 
       should be tested ie. keep arguments at a minimum
  */
  int _records = 1000;
  int _loops = 5;
  int _timer = 0;
  char * _remote_mgm =NULL;
  char* _testname = NULL;
  const char* _tabname = NULL;
  int _print = false;
  int _print_html = false;

  int _print_cases = false;
  int _verbose = false;
#ifndef DBUG_OFF
  const char *debug_option= 0;
#endif

  struct getargs args[] = {
    { "print", '\0', arg_flag, &_print, "Print execution tree", "" },
    { "print_html", '\0', arg_flag, &_print_html, "Print execution tree in html table format", "" },
    { "print_cases", '\0', arg_flag, &_print_cases, "Print list of test cases", "" },
    { "records", 'r', arg_integer, &_records, "Number of records", "records" },
    { "loops", 'l', arg_integer, &_loops, "Number of loops", "loops" },
    { "testname", 'n', arg_string, &_testname, "Name of test to run", "testname" },
    { "remote_mgm", 'm', arg_string, &_remote_mgm, 
      "host:port to mgmsrv of remote cluster", "host:port" },
    { "timer", 't', arg_flag, &_timer, "Print execution time", "time" },
#ifndef DBUG_OFF
    { "debug", 0, arg_string, &debug_option,
      "Specify debug options e.g. d:t:i:o,out.trace", "options" },
#endif
    { "verbose", 'v', arg_flag, &_verbose, "Print verbose status", "verbose" }
  };
  int num_args = sizeof(args) / sizeof(args[0]);
  int optind = 0;

  if(getarg(args, num_args, argc, argv, &optind)) {
    arg_printusage(args, num_args, argv[0], "tabname1 tabname2 ... tabnameN\n");
    return NDBT_WRONGARGS;
  }

#ifndef DBUG_OFF
  if (debug_option)
    DBUG_PUSH(debug_option);
#endif

  // Check if table name is supplied
  if (argv[optind] != NULL)
    _tabname = argv[optind];

  if (_print == true){
    printExecutionTree();
    return 0;
  }

  if (_print_html == true){
    printExecutionTreeHTML();
    return 0;
  }

  if (_print_cases == true){
    printCases();
    return NDBT_ProgramExit(NDBT_FAILED);
  }

  if (_verbose)
    setOutputLevel(2); // Show g_info
  else 
   setOutputLevel(0); // Show only g_err ?

  remote_mgm = _remote_mgm;
  records = _records;
  loops = _loops;
  timer = _timer;

  Ndb_cluster_connection con;
  if(con.connect(12, 5, 1))
  {
    return NDBT_ProgramExit(NDBT_FAILED);
  }
  
  if(optind == argc){
    // No table specified
    res = executeAll(con, _testname);
  } else {
    testSuiteTimer.doStart(); 
    for(int i = optind; i<argc; i++){
      executeOne(con, argv[i], _testname);
    }
    testSuiteTimer.doStop();
    res = report(_testname);
  }
  
  return NDBT_ProgramExit(res);
}
 


void NDBT_TestSuite::printExecutionTree(){
  ndbout << "Testsuite: " << name << endl;
  for (unsigned t = 0; t < tests.size(); t++){
    tests[t]->print();
    ndbout << endl;
  } 
}

void NDBT_TestSuite::printExecutionTreeHTML(){
  ndbout << "<tr>" << endl;
  ndbout << "<td><h3>" << name << "</h3></td>" << endl;
  ndbout << "</tr>" << endl;
  for (unsigned t = 0; t < tests.size(); t++){
    tests[t]->printHTML();
    ndbout << endl;
  } 

}

void NDBT_TestSuite::printCases(){
  ndbout << "# Testsuite: " << name << endl;
  ndbout << "# Number of tests: " << tests.size() << endl; 
  for (unsigned t = 0; t < tests.size(); t++){
    ndbout << name << " -n " << tests[t]->getName() << endl;
  } 
}

const char* NDBT_TestSuite::getDate(){
  static char theTime[128];
  struct tm* tm_now;
  time_t now;
  now = time((time_t*)NULL);
#ifdef NDB_WIN32
  tm_now = localtime(&now);
#else
  tm_now = gmtime(&now);
#endif
  
  BaseString::snprintf(theTime, 128,
	   "%d-%.2d-%.2d %.2d:%.2d:%.2d",
	   tm_now->tm_year + 1900, 
	   tm_now->tm_mon + 1, 
	   tm_now->tm_mday,
	   tm_now->tm_hour,
	   tm_now->tm_min,
	   tm_now->tm_sec);

  return theTime;
}

void NDBT_TestCaseImpl1::printHTML(){

  ndbout << "<tr><td>&nbsp;</td>" << endl;
  ndbout << "<td name=tc>" << endl << name << "</td><td width=70%>" 
	 << comment << "</td></tr>" << endl;  
}

void NDBT_TestCaseImpl1::print(){
  ndbout << "Test case: " << name << endl;
  ndbout << "Description: "<< comment << endl;

  ndbout << "Parameters: " << endl;

  Properties::Iterator it(&props);
  for(const char * key = it.first(); key != 0; key = it.next()){
    PropertiesType pt;
    const bool b = props.getTypeOf(key, &pt);
    assert(b == true);
    switch(pt){
    case PropertiesType_Uint32:{
      Uint32 val;
      props.get(key, &val);
      ndbout << "      " << key << ": " << val << endl;
      break;
    }
    case PropertiesType_char:{
      const char * val;
      props.get(key, &val);
      ndbout << "    " << key << ": " << val << endl;
      break;
    }
    default:
      abort();
    }  
  }
  unsigned i; 
  for(i=0; i<initializers.size(); i++){
    ndbout << "Initializers[" << i << "]: " << endl;
    initializers[i]->print();
  }
  for(i=0; i<steps.size(); i++){
    ndbout << "Step[" << i << "]: " << endl;
    steps[i]->print();
  }
  for(i=0; i<verifiers.size(); i++){
    ndbout << "Verifier[" << i << "]: " << endl;
    verifiers[i]->print();
  }
  for(i=0; i<finalizers.size(); i++){
    ndbout << "Finalizer[" << i << "]: " << endl;
    finalizers[i]->print();
  }
      
}

void NDBT_Step::print(){
  ndbout << "      "<< name << endl;

}

void
NDBT_Context::sync_down(const char * key){
  Uint32 threads = getProperty(key, (unsigned)0);
  if(threads){
    decProperty(key);
  }
}

void
NDBT_Context::sync_up_and_wait(const char * key, Uint32 value){
  setProperty(key, value);
  getPropertyWait(key, (unsigned)0);
}

template class Vector<NDBT_TestCase*>;
template class Vector<NDBT_TestCaseResult*>;
template class Vector<NDBT_Step*>;
template class Vector<NdbThread*>;
template class Vector<NDBT_Verifier*>;
template class Vector<NDBT_Initializer*>;
template class Vector<NDBT_Finalizer*>;
template class Vector<const NdbDictionary::Table*>;