//=================================================================
//
//        eCosTest.cpp
//
//        Test class
//
//=================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998, 1999 Cygnus Solutions.
// All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//=================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     sdf
// Contributors:  sdf
// Date:          1999-04-01
// Description:   This class abstracts a test for use in the testing infrastructure
// Usage:
//
//####DESCRIPTIONEND####
///////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "eCosTest.h"
#include "Port.h"
#include "TestResource.h"
#include "eCosTestUtils.h"
#include "eCosTestSocket.h"

#define WF(n) (n+50)/1000,((n+50)%1000)/100     // Present n as whole and fractional part.  Round to nearest least significant digit
#define WFS   "%d.%d"                           // The format string to output the above

static int nAuxPort; //hack
static int nAuxListenSock; // hack


// Static data:
// Do not use spaces in image strings for consideration of sscanf
const CeCosTest::TargetInfo CeCosTest::arTargetInfo[1+CeCosTest::TargetTypeMax]={
    // Image                prefix           sim?   gdbcommand
    {"TX39-jmr3904",        "mips-tx39-elf", false,0}, // TX39_jmr3904,
    {"TX39-jmr3904-minsim", "mips-tx39-elf", true, "target sim --board=jmr3904pal"
                                                   " --memory-region 0xffff8000,0x900"
                                                   " --memory-region 0xffffe000,0x4"
                                                   " --memory-region 0xb2100000,0x4"},  
    {"TX39-jmr3904-sim",    "mips-tx39-elf", true, "target sim --board=jmr3904"
                                                   " --memory-region 0xffff8000,0x900"
                                                   " --memory-region 0xffffe000,0x4"
                                                   " --memory-region 0xb2100000,0x4"},  
    {"PowerPC-cogent",      "powerpc-eabi",  false,0}, // PowerPC_cogent,
    {"PowerPC-sim",         "powerpc-eabi",  true, "target sim"
                                                   " -o '/#address-cells 1'"
                                                   " -o '/openprom/init/register/pc 0x100'"
                                                   " -o '/iobus@0xf0000000/reg 0xf0000000 0x01000000'"
                                                   " -o '/iobus/pal@0xf0001000/reg 0xf0001000 32'"},  
    {"SPARClite-sim",       "sparclite-elf", true, "target sim -nfp -sparclite -dumbio"},  
    {"SPARClite-sleb",      "sparclite-elf", false,0}, // SPARClite_sleb,
    {"ARM-PID",             "arm-elf",       false,"set remotebinarydownload 0"}, 
    {"ARM-AEB",             "arm-elf",       false,"set remotebinarydownload 0"}, 
    {"MN10300-stdeval1",    "mn10300-elf",   false,0}, // MN10300_stdeval1,
    {"MN10300-minsim",      "mn10300-elf",   true, "target sim --board=stdeval1" 
                                                   /*" --memory-region 0x28400000,0x800"*/
                                                   /*" --memory-region 0x28401000,0x800"*/ 
                                                   " --memory-region 0x34004000,0x8"},  
    {"MN10300-stdeval1-sim","mn10300-elf",   true, "target sim --board=stdeval1" 
                                                   /*" --memory-region 0x28400000,0x800"*/
                                                   /*" --memory-region 0x28401000,0x800"*/ 
                                                   " --memory-region 0x34004000,0x8"},  
    {"I386-Linux",          "i686-pc-linux-gnu",true,0}, // I386_Linux
    {"Unknown",             "",              false, 0}};

const char * const CeCosTest::arResultImage[1+CeCosTest::StatusTypeMax]=
    {"NotStarted", "NoResult", "Inapplicable", "Pass", "DTimeout", "Timeout", "Cancelled", "Fail", "Unknown"};

const char CeCosTest::szGdbPrompt[]="(gdb) ";
const unsigned int CeCosTest::nGdbPromptLen=sizeof szGdbPrompt-1;
const CeCosTest::Callback CeCosTest::NoCallback;
CeCosTest *CeCosTest::pFirstInstance=0;
int CeCosTest::InstanceCount=0;
CeCosTestUtils::String CeCosTest::strLogFile;
const char * const CeCosTest::arServerStatusImage[1+CeCosTest::ServerStatusMax]={
    "Busy", "Ready", "Can't run", "Connection failed", "Locked", "Bad server status"};
const char * CeCosTest::ExecutionParameters::arRequestImage [1+ExecutionParameters::RequestTypeMax]={
    "Run", "Query", "Lock", "Unlock", "Stop", "Bad request" };

CeCosTest::AliveInfo CeCosTest::AliveInfoZero;

static bool CALLBACK IsCancelled(void *pThis)
{
    return CeCosTest::Cancelled==((CeCosTest *)pThis)->Status();
}

// Ctors and dtors:
CeCosTest::CeCosTest(const ExecutionParameters &e, const char *pszExecutable,const char *pszTitle):
    m_strTitle(pszTitle),
    m_pPort(0),
    m_Status(NotStarted),
    m_bSocketErrorOccurred(false),
    m_ep(e),
    m_pSock(0),
    m_bDownloading(false),
    m_nMaxInactiveTime(0),
    m_nDownloadTime(0),
    m_nTotalTime(0),
    m_nStrippedSize(0)
{
    SetExecutable (pszExecutable);

    Trace("%%%% Create instance exe count:=%d\n",InstanceCount+1);

    GetPath(m_strPath);

    ENTERCRITICAL;
        InstanceCount++;
        m_pNextInstance=pFirstInstance;
        if(m_pNextInstance){
            m_pNextInstance->m_pPrevInstance=this;
        }
        m_pPrevInstance=0;
        pFirstInstance=this;
    LEAVECRITICAL;

    memset(&AliveInfoZero,0,sizeof AliveInfoZero);
}

CeCosTest::~CeCosTest()
{
    Trace("CeCosTest::~CeCosTest(): Calling Cancel()\n");
    Cancel();
    CloseSocket();

    if(m_pPort){
        m_pPort->Release();
        m_pPort=0;
    }

    VTRACE("~CeCosTest(): EnterCritical and decrease instance count\n");
    ENTERCRITICAL;
        InstanceCount--;
        Trace("%%%% Destroy instance.  Instance count:=%d\n",InstanceCount);
        if(pFirstInstance==this){
            pFirstInstance=m_pNextInstance;
        }
        if(m_pPrevInstance){
            m_pPrevInstance->m_pNextInstance=m_pNextInstance;
        }
        if(m_pNextInstance){
            m_pNextInstance->m_pPrevInstance=m_pPrevInstance;
        }
    LEAVECRITICAL;
    Trace("~CeCosTest() - exiting\n");
}

// Run the test remotely (on host:port as given by the first argument)
bool CeCosTest::RunRemote (const char *pszRemoteHostPort,const CeCosTest::Callback &cb)
{
    m_Status=NotStarted;

    Trace("RunRemote\n");
    char *pszHostPort;
    if(0!=pszRemoteHostPort){
        // take a copy to avoid arg going out of scope in the caller    
        pszHostPort=new char [1+strlen(pszRemoteHostPort)];
        strcpy(pszHostPort,pszRemoteHostPort);
    } else {
        pszHostPort=0;
    }

    return 0!=RUNTHREAD (RemoteThreadFunc,pszHostPort,cb); 
}

// Run the test locally
bool CeCosTest::RunLocal (const Callback &cb)
{
    m_Status=NotStarted;
    m_tPrevSample=0;

    GetPath(m_strPath);
    bool rc=false;

    VTRACE("RunLocal()\n");

    if(0==CPort::Count(m_ep)){
        Log("Cannot run a %s "
            "test\n",Image(m_ep.Target())
            );
        // Need to perform these functions otherwise taken care of by SThreadFunc
        InvokeCallback (cb);
    } else {

        Trace("Run %s"
            " timeouts(active=%d elapsed=%d)\n",(const char *)m_strExecutable
            , ActiveTimeout(), ElapsedTimeout());
        VTRACE("RunLocal(): Starting thread for LocalThreadFunc()\n");
        rc=(0!=RUNTHREAD (LocalThreadFunc,0,cb));
    }
    return rc;
}

void CeCosTest::Cancel ()
{
    SetStatus(Cancelled);
}

CeCosTest::ServerStatus CeCosTest::Connect (CeCosTestUtils::String strHost,int port, CeCosTestSocket *&pSock, const ExecutionParameters &e,CeCosTestUtils::Duration dTimeout)
{
    // Find out whether this host is receptive
    ServerStatus s=CONNECTION_FAILED;
    pSock=new CeCosTestSocket(strHost,port,dTimeout);
    if(pSock->Ok() &&
        pSock->send(e.Marshall(), sizeof(ExecutionParameters::Data),"execution parameters") &&
        pSock->recv(&s, sizeof s,"ready status")){
        s=min(s,ServerStatusMax);
    }
    if(SERVER_READY!=s || ExecutionParameters::RUN!=e.Request()){
        delete pSock;
        pSock=0;
    }
    return s;
}

// Initiate a connection to hostName:nPort and acquire the ready status
// The socket (m_pSock) is left open by Connect.in the case of success
bool CeCosTest::ConnectForExecution (CeCosTestUtils::String strHost,int port)
{
    bool bSchedule=(0==strHost.GetLength());
    int nChoices,nOrigChoices;
    CeCosTestUtils::Duration nDelay=500;
    const CeCosTestUtils::Time ft0=CeCosTestUtils::Time::Now();

    m_pSock=0;
    ServerStatus s=ServerStatusMax;
    int nChoice;
    CeCosTestUtils::String *arstrHostPort;
Retry:    
    if(bSchedule){
        LOCKRESOURCES;
        const CTestResource **ar=0;
        nChoices=CTestResource::GetMatches(m_ep,ar,/*bIgnoreLocking=*/true);
        nOrigChoices=nChoices;
        arstrHostPort=new CeCosTestUtils::String[nChoices];
        if(nChoices>0){
            Trace("ConnectForExecution: choices are:\n");
            for(int i=0;i<nChoices;i++){
                Trace("\t%s:%d\n",(const char *)ar[i]->Host(),ar[i]->Port());
                arstrHostPort[i].Format("%s:%d",(const char *)ar[i]->Host(),ar[i]->Port());
            }
        }
        delete [] ar;
        UNLOCKRESOURCES;
        if(0==nChoices){
            Log("No servers available to execute %s"
                " test\n",
                Image(m_ep.Target())
                );
            for(CTestResource *pResource=CTestResource::First();pResource;pResource=pResource->Next()){
                const char *pszStatus=CeCosTest::Image(pResource->Query());
                if(pResource->Sim()){
                    Log("  %10s %10s %20s Sim\n",
                        pResource->Host(),
                        pszStatus,
                        CeCosTest::Image(pResource->Target()));
                } else {
                    Log("  %10s %10s %20s %8s %d %c%c %s:%d\n",
                        pResource->Host(),
                        pszStatus,
                        CeCosTest::Image(pResource->Target()),
                        (const char *)pResource->DownloadPort(),
                        pResource->Baud(),
                        pResource->Control1(),
                        pResource->Control2(),
                        (const char *)pResource->ResetHost(),
                        pResource->ResetPort());
                }
            }
            goto Done;
        }
        nChoice=rand() % nChoices;
    } else {
        nChoices=1;
        arstrHostPort=new CeCosTestUtils::String[nChoices];
        arstrHostPort[0].Format("%s:%d",(const char *)strHost,port);
        nChoice=0; // Hobson's
    }

    for(;;) {
        
        m_strExecutionHostPort=arstrHostPort[nChoice];

        // Find somewhere to execute the test
        Trace("ConnectForExecution: chosen %s\n",(const char *)m_strExecutionHostPort);
        CeCosTestUtils::ParseHostPort(m_strExecutionHostPort, strHost, port);
        s=Connect(strHost,port,m_pSock,m_ep);
        Trace("Connect: %s:%d says %s\n",(const char *)strHost,port,Image(s));
        switch(s){
            case SERVER_READY:
                Trace("Connected to %s\n",(const char *)m_strExecutionHostPort);
                goto Done;
                break;
            case SERVER_LOCKED:
            case SERVER_BUSY:
                {
                    Trace("ConnectForExecution: waiting %d msec for %s... \n",nDelay,(const char *)m_strExecutionHostPort);
                    CeCosTestUtils::Sleep(nDelay);

                    if(Cancelled==m_Status){
                        Trace("ConnectForExecution : cancelled\n");
                        s=CONNECTION_FAILED;
                        goto Done;
                    }
                
                    if(bSchedule&&nChoices>1){
                        // Reselect, but avoid the one we've just chosen
                        int n;
                        do {
                            n=rand() % nChoices;
                        } while (n==nChoice);
                        nChoice=n;
                    } else {
                        // Choice remains the same
                        if(nDelay<20*1000){
                            nDelay+=rand() % 500;
                        }
                    }
                }
                continue;
            default:
                Trace("Connect to %s failed - status=%s\n",(const char *)m_strExecutionHostPort,Image(s));
                if(bSchedule){
                    if(nChoices>1){
                        // Remove this permanently from the possibilities to be considered
                        for(int i=nChoice;i<nChoices-1;i++){
                            arstrHostPort[i]=arstrHostPort[i+1];
                        }
                        nChoices--;
                        nChoice=rand() % nChoices;
                    } else {
                        // Start again
                        delete [] arstrHostPort;
                        // For the benefit of interactive users
                        fprintf(stderr,"Warning - could not connect to any test servers\n");
                        goto Retry;
                    }
                } else {
                    goto Done;
                }
        }
    } 
Done:    
    delete [] arstrHostPort;
    bool rc=(SERVER_READY==s);
    if(!rc){
        CloseSocket();
    }
    return rc;
}

// In this function we execute a test and can safely block doing so.
// pParam is a char * pointing to host:port (we delete it)
void CeCosTest::RemoteThreadFunc (void *pParam)
{
    CeCosTestUtils::String strHost;
    int port=0;           

    VTRACE("RemoteThreadFunc()\n");

    bool bSchedule=(0==pParam);

    if(!bSchedule){
        char *pszHostPort=(char *)pParam;
        VTRACE("RemoteThreadFunc() : Parsing hostPort\n");
        bool b=CeCosTestUtils::ParseHostPort(pszHostPort,strHost,port);
        assert(b);
        VTRACE("Deleting pszHostPort\n");
        delete [] pszHostPort;
    }

        
    if(ConnectForExecution(strHost,port)){
        //m_pSock->SetSocketOptions();

        if(Sim()){

            // Open the file early to avoid setting up a connection pointlessly
            FILE *f1=fopen(m_strExecutable,"rb");
            if(0==f1){
                Log("Failed to open %s - %s\n",(const char *)m_strExecutable,strerror(errno));
            } else {
                // send file size
                if(m_pSock->sendInteger(m_nFileSize)&&m_nFileSize>0){
                    int nBufSize=min(10000,m_nFileSize);
                    char *buf=new char[nBufSize];
                    Trace("Sending [%d bytes]\n", m_nFileSize);
                    int nToSend=m_nFileSize;
                    while (nToSend>0){
                        int nRead=fread( buf, 1, nBufSize, f1);
                        if(nRead<=0){
                            Log("Failure reading %s - %s\n",(const char *)m_strExecutable,strerror(errno));
                            break;
                        }
                        if(!send( buf, nRead, "executable")){
                            Log("Failure sending %s - %s\n",(const char *)m_strExecutable,(const char *)m_pSock->SocketErrString());
                            break;
                        }
                        nToSend-=nRead;
                    }
                    if(nToSend>0){
                        Trace("done [%d bytes sent]\n",m_nFileSize-nToSend);
                        Log("Failed to transmit %s - %d/%d bytes sent\n",(const char *)m_strExecutable,m_nFileSize-nToSend,m_nFileSize);
                    } else {
                        Trace("done\n");
                    }
                    delete [] buf;
                }
                fclose(f1);
                f1=0;

                WaitForRemoteCompletion();
                CloseSocket();
            }
        } else {
            CeCosTestUtils::String strHostPort;
            // Big timeout here because we have to wait for the target to be reset
            if(!m_pSock->recvString(strHostPort,"Port name",120*1000)){
                Log("Failed to read host:port from server - %s\n",(const char *)m_pSock->SocketErrString());
            } else if(!CeCosTestUtils::IsLegalHostPort(strHostPort)){
                Log("%s\n",(const char *)strHostPort);
            } else {
                TRACE("Instructed to use %s\n",(const char *)strHostPort);
                m_pPort=new CPort(Target(), strHostPort, 0);
                RunLocal();
                delete m_pPort;
                m_pPort=0;
            }
            // Tell the server we're through
            char cAck=123;
            m_pSock->send(&cAck,1,"Terminating ack");
        }
    } else {
        Log(0==strHost.GetLength()?"Failed to connect to any host":
            "Failed to connect to %s:%d\n",(const char *)strHost,port);
        LOCKRESOURCES;
        const CTestResource **ar=0;
        int nChoices=CTestResource::GetMatches(m_ep,ar,/*bIgnoreLocking=*/true);
        if(nChoices>0){
            for(int i=0;i<nChoices;i++){
                const CTestResource *pResource=ar[i];
                Log ("   %s:%d %s\n",
                    (const char *)pResource->Host(),
                    pResource->Port(),
                    Image(pResource->Query()));
            }
        } else {
            LogString("No hosts available to execute this test\n");
        }
        UNLOCKRESOURCES;
        delete [] ar;
    }

    m_bSocketErrorOccurred=false;   
    TRACE("RemoteThreadFunc(): Exiting\n");
}

// function run by thread to execute a test locally
void CeCosTest::LocalThreadFunc (void *pParam)
{
    pParam; // prevent compiler warnings
    Trace("LocalThreadFunc - target=%s\n",Image(Target()));
    // Acquire a port (our caller may have done this for us)
    VTRACE("LocalThreadFunc():Tring to acquire a port\n");
    if(0==m_pPort){
        for(;;){
            m_pPort=CPort::GetPort(m_ep);
            if(m_pPort||Cancelled==Status()){
                break;
            }
            CeCosTestUtils::Sleep(2000);
            Trace("Waiting for a port\n");
        }
    }
    VTRACE("\nPort acquired!\n");

    if(Cancelled!=Status()){
        // This means we have acquired a local port 
        bool bTargetReady=false;
        if(Sim()||'\0'==*(m_pPort->ResetHost())){
            bTargetReady=true;
        } else {
            for(int nTry=1;nTry<=3;nTry++){
                int nErr;
                if(m_pPort->Reset(nErr, m_strOutput)){
                    if(0==m_strOutput.GetLength()){
                        Log("Could not reset target (board silent after power on) [attempt %d/3]\n",nTry);
                    } else {
                        bTargetReady=true;
                        break;
                    }
                } else {
                    Log("Could not reset target [attempt %d/3] - rc=%d\n",nTry,nErr);
                }
            }
        }
        // we may proceed to execute the test
        if(bTargetReady){
            SetStatus(NotStarted);
            if(!Sim() /*&& NOTIMEOUT==m_ep.ElapsedTimeout()*/){
                // No elapsed timeout given - calculate from knowledge of executable size and baud rate
                // 10 baud ~= 1 byte/sec, but we halve this to account for download in hex :-(
                // This means that a 200k executable is given ~100 seconds
                // In any case the whole thing is an overestimate [we should use the stripped size]
                // We use a minimum of 30 seconds and add 50% for safety
                int nSpeed=((0==m_pPort->Baud()?9600:m_pPort->Baud())/10)/2; // Hex
                nSpeed/=2; // Safety
                m_ep.SetElapsedTimeout (1000*max(40,(m_nStrippedSize/nSpeed)));
                Trace("Timeout<-%d\n",1000*max(40,(m_nStrippedSize/nSpeed)));
            }
            if(NOTIMEOUT==m_ep.ActiveTimeout()){
                m_ep.SetActiveTimeout(1000*(Sim()?300:30));
            }

            {
                // Construct commands for gdb
                int i=0;
                CeCosTestUtils::String arstrGdbCmds[14];
    
                // Tell gdb its paper size :-)
                arstrGdbCmds[i++]="set height 0";
                arstrGdbCmds[i++]="set remotedebug 0";
                
                if(arTargetInfo[Target()].pszGdbcmd){
                    arstrGdbCmds[i++]=arTargetInfo[Target()].pszGdbcmd;
                }

                if(Sim()){
                   arstrGdbCmds[i++]="load";
                } else {
                    // Standard incantations for hardware:
                    if(0==strchr(m_pPort->Name(),':')){
                        arstrGdbCmds[i++].Format("set remotebaud %d",m_pPort->Baud());
                    }
                    arstrGdbCmds[i++].Format("target remote %s",m_pPort->Name());
                    #ifdef _WIN32
                    // pass port name to GDB in lower case
                    arstrGdbCmds[i-1].MakeLower();
                    #endif
                    arstrGdbCmds[i++]="load";
                }
                arstrGdbCmds[i++]="break cyg_test_exit";
                arstrGdbCmds[i++]="break cyg_assert_fail";
                if(/*start hack*/BreakpointsOperational()/*end hack*/){
                    arstrGdbCmds[i++]="break cyg_test_init";
                }
                
                if(Sim()){
                    arstrGdbCmds[i++]="run";
                    switch(m_ep.Target()){
                        case TX39_minsim:
                            arstrGdbCmds[i++]="set cyg_test_is_simulator=2";
                            break;
                        case I386_Linux:
                            arstrGdbCmds[i++]="set cyg_test_is_simulator=3";
                            break;
                        default:
                            arstrGdbCmds[i++]="set cyg_test_is_simulator=1";
                            break;
                    }
                } else {
                    arstrGdbCmds[i++]="cont"; // run the program
                }

                if(/*start hack*/BreakpointsOperational()/*end hack*/){
                    arstrGdbCmds[i++]="cont"; // continue from cyg_test_init breakpoint
                }
                // arstrGdbCmds[i++]="bt"; // stack traceback
                // run/cont command must be the last (logic in DriveGdb)
                assert(i<sizeof arstrGdbCmds/sizeof arstrGdbCmds[0]);

                // Invoke gdb:
                CeCosTestUtils::String strGdb(arTargetInfo[Target()].pszPrefix);
                strGdb+="-gdb";
                Trace("Calling RunGdb\n");
                
                /*
                for(int nAttempt=0;nAttempt<3;nAttempt++){
                    RunGdb(strGdb,arstrGdbCmds);
                    if(NotStarted!=Status()){
                        break;
                    }
                    m_Status=NotStarted;
                }
                */
                RunGdb(strGdb,arstrGdbCmds);

            }
        }
    }
    if(0!=m_pPort){
        m_pPort->Release();
        m_pPort=0;
    }
}

void CeCosTest::LogResult()
{
    const char *psz=ResultString();
    Trace("%s\n",psz);
    ENTERCRITICAL;
        if(strLogFile.GetLength()>0){
            FILE *f=fopen(strLogFile,"at"); // Append, create if necessary, text mode
            if(f){
                fprintf(f,"%s\n",psz);
                fclose(f);
            } else {
                puts(psz);
            }
        } else {
            puts(psz);
            fflush(stdout);
        }
    LEAVECRITICAL;
}

void CeCosTest::SetLogFile (const char *pszFile)
{
    strLogFile=pszFile;
}

void CeCosTest::SetStatus (StatusType status)
{
    ENTERCRITICAL;
        if((int)status>(int)m_Status){
            Trace("Status <- %s\n",Image(status));
            m_Status=status;
        }
    LEAVECRITICAL;
}

bool CeCosTest::WaitForAllInstances(int nPoll,CeCosTestUtils::Duration nTimeout)
{
    CeCosTestUtils::Time t0=CeCosTestUtils::Time::Now();
    while(InstanceCount>0){
        CeCosTestUtils::Sleep(nPoll);
        if(NOTIMEOUT!=nTimeout && CeCosTestUtils::Time::Now()-t0>nTimeout){
            return false;
        }
    }
    return true;
}

void CeCosTest::DeleteAllInstances()
{
    while(pFirstInstance){
        delete pFirstInstance;
    }
}

void CeCosTest::CancelAllInstances()
{
    ENTERCRITICAL;
        for(CeCosTest *pTest=pFirstInstance;pTest;pTest=pTest->m_pNextInstance){
            pTest->Cancel();
        }
    LEAVECRITICAL;
}

// The same format is used for sscanf as for Format (which is like printf), so restrict to the format specifiers
// the former is happy with.  In particular, do not use %-3s etc...

static const char *arpszFormat[2]=
{
    // For hardware:
    // 1999-01-15 17:24:36 Fireblade:5002 MN10300 sin.exe 219k/134k Pass sin Elapsed: download=106.3/117.0 Total=107.6 Max inactive=1.0/300.0    
    "%04d-%02d-%02d %02d:%02d:%02d "                   // CeCosTestUtils::Time
    "%15s "                                            // Execution host:port
    "%16s "                                             // Target
    "%30s "                                            // Executable tail
    "%11s "                                            // Result
    "%dk/%dk "                                         // Sizes
    "Elapsed: D=" WFS "/" WFS " Total=" WFS " "        // Times
    "E=" WFS "/" WFS " "
    "\"%s\"",                                          // Title

    // For sim:
    // 1999-01-15 17:24:36 Fireblade:5002 MN10300 sin.exe 219k Pass sin Cpu: Total=106.3 Max inactive=1.0/300.0    
    "%04d-%02d-%02d %02d:%02d:%02d "                   // CeCosTestUtils::Time
    "%15s "                                            // Execution host:port
    "%16s "                                             // Target
    "%30s "                                            // Executable tail
    "%11s "                                            // Result
    "%dk/%dk "                                         // Sizes
    "Cpu: Total=" WFS " " "E=" WFS "/" WFS " "         // Times
    "\"%s\""                                           // Title
    };

bool CeCosTest::Value (
    const char *pszStr, 
    struct tm &t,
    bool &bSim,
    StatusType &status,
    TargetType &target,
    CeCosTestUtils::String &strExecutionHostPort,
    CeCosTestUtils::String &strExecutableTail,
    CeCosTestUtils::String &strTitle,

    int &nFileSize,
    CeCosTestUtils::Duration &nTotalTime,
    CeCosTestUtils::Duration &nMaxInactiveTime,
    CeCosTestUtils::Duration &nDownloadTime,
    CeCosTestUtils::Duration &nElapsedTimeout,
    CeCosTestUtils::Duration &nActiveTimeout,
    
    int &nDownloadedSize)
{
    bSim=(0!=strstr(pszStr,"Cpu:"));
    int nLen=strlen(pszStr);
    strExecutionHostPort.SetLength(nLen);
    CeCosTestUtils::String strTarget;
    strTarget.SetLength(nLen);
    CeCosTestUtils::String strStatus;
    strStatus.SetLength(nLen);
    strExecutableTail.SetLength(nLen);
    strTitle.SetLength(nLen);

    nFileSize=nTotalTime=nMaxInactiveTime=nDownloadTime=nElapsedTimeout=nActiveTimeout=nDownloadedSize=0;
    
    int nTotalTimeFrac=0;
    int nMaxInactiveTimeFrac=0;
    int nActiveTimeoutFrac=0;
    int nDownloadTimeFrac=0;
    int nElapsedTimeoutFrac=0;

    static CeCosTestUtils::String arstrFormat[2];
    CeCosTestUtils::String &strFormat=arstrFormat[bSim];
    if(0==strFormat.GetLength()){
        // Construct a version of the format string sans length attributes for %s items
        const char *c=arpszFormat[bSim];
        char *d=strFormat.GetBuffer(strlen(arpszFormat[bSim]));
        while(*c){
            if('%'==c[0] && isdigit(c[1])){
                *d++='%';
                do {
                    c++;
                } while (isdigit(*c));
            }
            *d++=*c++;
        }
        *d='\0';
        strFormat.ReleaseBuffer();
    }
    if(bSim){
        sscanf(pszStr,
            strFormat,
            &t.tm_year,&t.tm_mon,&t.tm_mday,
            &t.tm_hour,&t.tm_min,&t.tm_sec,         // CeCosTestUtils::Time of day
            strExecutionHostPort.GetBuffer(),       // Execution host:port
            strTarget.GetBuffer(),                  // Target
            strExecutableTail.GetBuffer(),          // Executable
            strStatus.GetBuffer(),                  // Result
            &nDownloadedSize,&nFileSize,            // Sizes
            &nTotalTime,&nTotalTimeFrac,            // Times
            &nMaxInactiveTime,&nMaxInactiveTimeFrac,
            &nActiveTimeout,&nActiveTimeoutFrac,
            strTitle.GetBuffer()                    // Title
            );
    } else {
        sscanf(pszStr,
            strFormat,
            &t.tm_year,&t.tm_mon,&t.tm_mday,
            &t.tm_hour,&t.tm_min,&t.tm_sec,         // CeCosTestUtils::Time of day
            strExecutionHostPort.GetBuffer(),       // Execution host:port
            strTarget.GetBuffer(),                  // Target
            strExecutableTail.GetBuffer(),          // Executable
            strStatus.GetBuffer(),                  // Result
            &nDownloadedSize,&nFileSize,            // Sizes
            &nDownloadTime,&nDownloadTimeFrac,      // Times
            &nElapsedTimeout,&nElapsedTimeoutFrac,
            &nTotalTime,&nTotalTimeFrac,
            &nMaxInactiveTime,&nMaxInactiveTimeFrac,
            &nActiveTimeout,&nActiveTimeoutFrac,
            strTitle.GetBuffer()                    // Title
            );
    }

    // Hack
    TargetType tt=FromStr(strExecutionHostPort);
    if(TargetTypeMax!=tt){
        CeCosTestUtils::String str(pszStr);
        char *c=str.GetBuffer();
        c[21]='x';
        c[22]=':';
        c[23]='0';
        bool rc=Value(
            c,
            t,
            bSim,
            status,
            target,
//          exetype,
            strExecutionHostPort,
            strExecutableTail,
            strTitle,
            nFileSize,
            nTotalTime,
            nMaxInactiveTime,
            nDownloadTime,
            nElapsedTimeout,
            nActiveTimeout,
            nDownloadedSize);
        strExecutionHostPort="";
        return rc;          
    }
    // end hack
    
    const char *c1=strchr(pszStr,'"');
    if(c1){
        c1++;
        const char *c2=strchr(c1+1,'"');
        if(c2){
            strTitle.SetLength(c2-c1);
            strncpy(strTitle.GetBuffer(),c1,c2-c1)[c2-c1]='\0';
        }
    }

    nTotalTime=nTotalTime*1000+nTotalTimeFrac*100;
    nMaxInactiveTime=nMaxInactiveTime*1000+nMaxInactiveTimeFrac*100;
    nActiveTimeout=nActiveTimeout*1000+nActiveTimeoutFrac*100;
    nDownloadTime=nDownloadTime*1000+nDownloadTimeFrac*100;
    nElapsedTimeout=nElapsedTimeout*1000+nElapsedTimeoutFrac*100;
    
    strExecutionHostPort.ReleaseBuffer();
    target=FromStr(strTarget);
    strTarget.ReleaseBuffer();
    strExecutableTail.ReleaseBuffer();
    status=StatusTypeValue(strStatus);
    strStatus.ReleaseBuffer();
    strTitle.ReleaseBuffer();

    nFileSize*=1024;
    nDownloadedSize*=1024;
    t.tm_year-=1900;
    t.tm_mon--;
    return t.tm_year>=0 && t.tm_year<=200 && t.tm_mon>=0 && t.tm_mon<=11 && t.tm_mday>=1 && t.tm_mday<=31 && t.tm_hour>=0 && t.tm_hour<=23 && t.tm_min>=0 && t.tm_min<=59 && t.tm_sec>=0 && t.tm_sec<=59 &&
        status!=StatusTypeMax && target!=TargetTypeMax 
        //&& exetype!=ExecutionParameters::ExecutableTypeMax
        ;
}

const char * const CeCosTest::ResultString() const
{
    CeCosTestUtils::String strTitle(m_strTitle);
    CeCosTestUtils::String strExecutionHostPort(m_strExecutionHostPort);
    
    if(0==strTitle.GetLength()){
        GetSimpleHostName(strTitle);
        strTitle+=':';
        strTitle+=m_strExecutable; 
    }

    if(0==strExecutionHostPort.GetLength()){
        GetSimpleHostName(strExecutionHostPort);
        strExecutionHostPort+=":0";
    }

    ENTERCRITICAL;
        time_t ltime;
        time(&ltime);
        struct tm *now=localtime( &ltime );
    
        if(Sim()){
            m_strResultString.Format(
                arpszFormat[1],
                1900+now->tm_year,1+now->tm_mon,now->tm_mday,
                now->tm_hour,now->tm_min,now->tm_sec,               // CeCosTestUtils::Time of day
                (const char *)strExecutionHostPort,               // Execution host:port
                Image(Target()),                                    // Target
                ExecutableTail(),                                   // Executable
                Image(Status()),                                    // Result
                m_nStrippedSize/1024,m_nFileSize/1024,          // Sizes
                WF(m_nTotalTime),                                   // Times
                WF(m_nMaxInactiveTime),WF(ActiveTimeout()),
                (const char *)strTitle                              // Title
                );
        } else {
            m_strResultString.Format(
                arpszFormat[0],
                1900+now->tm_year,1+now->tm_mon,now->tm_mday,
                now->tm_hour,now->tm_min,now->tm_sec,               // CeCosTestUtils::Time of day
                (const char *)strExecutionHostPort,               // Execution host:port
                Image(Target()),                                    // Target
                ExecutableTail(),                                   // Executable
                Image(Status()),                                    // Result
                m_nStrippedSize/1024,m_nFileSize/1024,          // Sizes
                WF(m_nDownloadTime),WF(ElapsedTimeout()),WF(m_nTotalTime),// Times
                WF(m_nMaxInactiveTime),WF(ActiveTimeout()),
                (const char *)strTitle                              // Title
                );
        }
        if(m_strOutput.GetLength()>0){                            
            m_strResultString+='\n';
            m_strResultString+=m_strOutput;
        }
    LEAVECRITICAL;
    return m_strResultString;
}

static void CALLBACK AcceptThreadFuncCallback(CeCosTest*pTest,void *pParam)
{
    pParam; // prevent compiler warnings
    VTRACE("AcceptThreadFuncCallback() - Deleting pTest\n");
    unlink(pTest->Executable());
    delete pTest;
    VTRACE("AcceptThreadFuncCallback() - pTest deleted\n");
}

bool CeCosTest::RunAgent(int nTcpPort)
{
    bool bLocked=false;
    char szMyname[256];
    gethostname(szMyname,sizeof szMyname);

    nAuxPort=nTcpPort+3000;//hack
    nAuxListenSock=CeCosTestSocket::Listen(nAuxPort);//hack
    if(-1!=nAuxListenSock){
        // Create socket
        int nSock = CeCosTestSocket::Listen(nTcpPort);
        int nLastClient=0;
        int nRejectionCount=0;
        if (-1!=nSock) {
            for (;;) {
                CeCosTestSocket *pSock=new CeCosTestSocket(nSock); // AcceptThreadFunc deletes if not deleted below
                //pSock->SetSocketOptions();
                ExecutionParameters::Data buf;
                // Read the execution parameters
                if(!pSock->recv(buf, sizeof buf)){
                    // Socket error on the recv - nothing much we can do
                    TRACE("RunAgent : could not read execution parameters\n");
                    delete pSock;
                    pSock=0;
                } else {
                    const ExecutionParameters e(buf);
                    bool bNuisance=false;
                    ServerStatus s;
                    CPort *pPort=0;
                    if(!e.IsValid()){
                        // Looks like a confused client ...
                        TRACE("Bad target value %8x read from client\n",e.Target());
                        s=SERVER_CANT_RUN;
                    } else if(0==CPort::Count(e)){
                        // No chance of running this test
                        TRACE("Cannot run a %s test from this server\n",Image(e.Target()));
                        s=SERVER_CANT_RUN;
                    } else {
                        switch(e.Request()) {
                            case ExecutionParameters::LOCK:
                                if(bLocked){
                                    s=SERVER_BUSY;
                                } else {
                                    bLocked=true;
                                    s=SERVER_LOCKED;
                                }
                                break;
                            case ExecutionParameters::UNLOCK:
                                if(bLocked){
                                    bLocked=false;
                                    s=SERVER_READY;
                                } else {
                                    s=SERVER_BUSY;
                                }
                                break;
                            case ExecutionParameters::QUERY:
                            case ExecutionParameters::RUN:
                                if (bLocked) {
                                    s=SERVER_LOCKED;
                                } else {
                                    pPort=CPort::GetPort(e);
                                    if(0==pPort){
                                        // We must disappoint our client
                                        nRejectionCount++;
                                        s=SERVER_BUSY;
                                    /*
                                    } else if(nLastClient==pSock->Client() && nRejectionCount>10) {
                                        // Don't answer the phone to a nuisance caller
                                        s=SERVER_BUSY;
                                        bNuisance=true;
                                        nRejectionCount--;  
                                        pPort->Release();
                                    */
                                    } else {
                                        s=SERVER_READY;
                                        nRejectionCount=0;
                                        nLastClient=pSock->Client();
                                    }
                                }
                                break;
                            case ExecutionParameters::STOP:
                                s=SERVER_READY;
                                break;
                            default:
                                s=SERVER_CANT_RUN;
                        }
                    }

                    #ifndef VERBOSE
                    if(ExecutionParameters::QUERY!=e.Request())
                    #endif
                    TRACE("RunAgent : %s request tActive=%d tElapsed=%d Target=%s Reply status=%s Nuisance=%d\n",
                        e.Image(e.Request()),
                        e.ActiveTimeout(),e.ElapsedTimeout(),Image(e.Target()),
                        Image(s),
                        bNuisance);
                
                    bool bSendok=pSock->send (&s, sizeof s);
                
                    VTRACE("RunAgent() : Send complete = rc was <%d>\n", bSendok);

                    if(SERVER_READY==s && bSendok && ExecutionParameters::RUN==e.Request()){

                        // Create a new class instance
                        // AcceptThreadFunc deletes the instance and closes new_sock
                        // RunLocal, called by AcceptThreadFunc, releases the port
                        // We dream up a temporary name for the executable
                        // No need for meaningful callback, but must run asynchronously
                        CeCosTest *pTest=new CeCosTest(e,tmpnam(0));
                        pTest->m_pSock=pSock;
                        pTest->m_strExecutionHostPort.Format("%s:%d",szMyname,nTcpPort);
                        pTest->m_pPort=pPort;

                        pTest->RUNTHREAD(AcceptThreadFunc,0,Callback(AcceptThreadFuncCallback,0)); 
                        // AcceptThreadFunc deletes pSock

                    } else {
                        delete pSock;
                        pSock=0;
                        if(pPort){
                            pPort->Release();
                            pPort=0;
                        }
                        if(CeCosTest::ExecutionParameters::STOP==e.Request()){
                            CancelAllInstances();
                            WaitForAllInstances(1000,20*1000);
                            break;
                        }
                    }
                }
            }
            CeCosTestSocket::CloseSocket (nSock);
        }
        VTRACE("RunAgent(): returning false\n");
        CeCosTestSocket::CloseSocket(nAuxListenSock);
    }
    return false;
}

CeCosTest::TargetType CeCosTest::TargetTypeValue(const char * pszStr)
{
    // Allow underscores to be equivalent to hyphens
    CeCosTestUtils::String str(pszStr);
    for(char *c=str.GetBuffer();*c;c++){
        if('_'==*c){
            *c='-';
        }
    }
    int t;
    for(t=0;t<TargetTypeMax;t++){
        if(0==strnccmp(Image((TargetType)t),str)){
            break;
        }
    }
    return (TargetType)t;
}

CeCosTest::StatusType CeCosTest::StatusTypeValue(const char * pszStr)
{
    for(int i=0;i<StatusTypeMax;i++){
        StatusType t=(StatusType)i;
        if(0==strnccmp(Image(t),pszStr)){
            return t;
        }
    }
    return StatusTypeMax;
}

void CeCosTest::SendKeepAlives(bool &b)
{
    VTRACE("Sending keepalives...\n");
    while (!b){
        CeCosTestUtils::Sleep(1000);
        AliveInfo ai;
        ai.Downloading=m_bDownloading;
        ai.Status=m_Status;
        ai.Number=1; // ensure non-zero
        send(&ai,sizeof ai); 
    }
    VTRACE("Done sending keepalives...\n");
}

void CeCosTest::WaitForRemoteCompletion()
{
    CeCosTestUtils::Time ft0=CeCosTestUtils::Time::Now();
    StatusType rPrev=StatusTypeMax;
    Trace("Waiting for result\n");

    for(;;){
        AliveInfo ai;
        if(!recv(&ai,sizeof ai,"result")){
            Trace("Receive failed\n");
            return;
        }
        if(0==memcmp(&ai,&AliveInfoZero,sizeof ai)){
            break;
        }
        CeCosTestUtils::Duration d=CeCosTestUtils::Time::Now()-ft0;
        if(ai.Downloading){
            Trace("Downloading : t=" WFS "\n",WF(d));
        } else {
            StatusType r=ai.Status;
            if(r!=rPrev){
                ft0=CeCosTestUtils::Time::Now();
                rPrev=r;
            }
            Trace("Status=%s t=" WFS "\n",Image(r),WF(d));
        }
        CeCosTestUtils::Sleep(1000);
        if(Cancelled==m_Status){
            Trace("Cancelled\n");
            return;
        }
    }
    VTRACE("Result ready\n");
    recvResult();
    VTRACE("WaitForRemoteCompletion: Sending an ack\n");
    send("",1); // send an ack [n'importe quoi]
}


void CeCosTest::ConnectSocketToSerialThreadFunc(void *pParam)
{
    int *arInfo=(int *)pParam;
    int nAuxListenSock=arInfo[0];
    bool *pbStop=(bool *)arInfo[1];

    TRACE("ConnectSocketToSerialThreadFunc sock=%d\n",nAuxListenSock);
    {

        #define MAX_CMD_LEN 128
        typedef struct ser_filter_state {
            bool null_filter;
            int cmd_i;
            int cmd_flag;
            char cmd[MAX_CMD_LEN];

        } ser_filter_state_t;

        ser_filter_state_t state;
        state.null_filter = 0;
        state.cmd_i = 0;
        state.cmd_flag = 0;

        extern bool CALLBACK
        serial_filter(void*& pBuf,
                      unsigned int& nRead,
                      CeCosTestSerial& serial,
                      CeCosTestSocket& socket,
                      void* pParem);
    
        ConnectSocketToSerial(nAuxListenSock, m_pPort->Name(), m_pPort->Baud(), &serial_filter, 0, &state, pbStop);
    }
    //ConnectSocketToSerial (nAuxListenSock, m_pPort->Name(), m_pPort->Baud(), 0,0,0, pbStop);
}

static bool CALLBACK DerefBool(void *pParam)
{
    return *(bool *)pParam;
}

// Function called (on a separate thread) to process a successful connection
void CeCosTest::AcceptThreadFunc(void *pParam)
{
    pParam; // prevent compiler warnings
    if(Sim()){
        //m_pSock->SetSocketOptions();
        int n;
        if(m_pSock->recvInteger(n,"file size")){
            m_nFileSize=n;
            // Read file from the socket
            bool bCanRun=true;
            Trace("AcceptThreadFunc file size=%d reading...\n",m_nFileSize);
            FILE *f2;
            f2=fopen(m_strExecutable,"wb");
            if(0==f2){
                Log("Could not create %s - %s\n",(const char *)m_strExecutable,strerror(errno));
                bCanRun=false;
            }
            unsigned int nBufSize=min(10000,m_nFileSize);
            char *buf=new char[nBufSize];
            unsigned int nWritten=0;
            unsigned int nRead=0;
            while(nRead<m_nFileSize){
                int nToRead=min(nBufSize,m_nFileSize-nRead);
                if(!recv( buf, nToRead, "executable")){
                    break;
                }
                nRead+=nToRead;
                if(0!=f2){
                    const char *c=buf;
                    while(nToRead>0){
                        int w=fwrite(c,1,nToRead,f2);
                        if(-1==w){
                            Log("Write error on %s - %s\n",(const char *)m_strExecutable,strerror(errno));
                            bCanRun=false;
                            break;
                        }
                        nWritten+=w;
                        c+=w;
                        nToRead-=w;
                    }
                }
            }
            delete [] buf;
            Trace("Accept - done reading [%d bytes read, %d bytes written]\n",nRead,nWritten);
            if(0!=f2){
                fclose(f2);
                chmod(m_strExecutable,00700); // user read, write and execute
            }
            if(0!=f2 && m_nFileSize!=nWritten){
                Log("Failed to create %s correctly [%d/%d bytes written]\n",(const char *)m_strExecutable, nWritten, m_nFileSize);
                bCanRun=false;
            }
            SetExecutable(m_strExecutable); // to set stripped length and title
            if(bCanRun){
                bool b=false;
                if(RunLocal(Callback(0,&b))){
                    SendKeepAlives(b);
                }
            }
            send(&AliveInfoZero,sizeof AliveInfoZero); // send a zero byte
            unlink(m_strExecutable);
        }
        sendResult();
        char c;
        m_pSock->recv(&c,1); // receive an ack
    } else { // HW
        
        bool bTargetReady=false;
        if('\0'==*(m_pPort->ResetHost())){
            bTargetReady=true;
TRACE("No reset possible\n");
        } else {
TRACE("Attempting to reset target\n");
            for(int nTry=1;nTry<=3;nTry++){
                int nErr;
                if(m_pPort->Reset(nErr, m_strOutput)){
                    if(0==m_strOutput.GetLength()){
                        Log("Could not reset target (board silent after power on) [attempt %d/3]\n",nTry);
                    } else {
                        bTargetReady=true;
                        break;
                    }
                } else {
                    Log("Could not reset target [attempt %d/3] - rc=%d\n",nTry,nErr);
                }
            }
        }

TRACE("bTargetReady=%d\n",bTargetReady);
        
        char cAck;
        int dTimeout=m_ep.ElapsedTimeout()+max(3*m_ep.ActiveTimeout(),15*60*1000);
        if(bTargetReady){
            if(CeCosTestUtils::IsLegalHostPort(m_pPort->Name())){
TRACE("Sending %s\n",(const char *)m_pPort->Name());
                m_pSock->sendString(m_pPort->Name(),"Port name");
                m_pSock->recv(&cAck,1,"Terminating ack",dTimeout);
TRACE("Terminating ack=%d\n",cAck);
            } else {
                CeCosTestUtils::String strHostPort;
                char *pszMe=strHostPort.GetBuffer(256);
                gethostname(pszMe,256);
                sprintf(pszMe+strlen(pszMe),":%d",nAuxPort);
                strHostPort.ReleaseBuffer();

TRACE("Using %s\n",(const char *)strHostPort);

                if(m_pSock->sendString(strHostPort)){

                    // This Boolean signifies that the serial<-->tcp/ip conversation is done.  It may be set
                    // on completion of the ConnectSocketToSerial thread (which is why we pass it to runthread)
                    // and also set by us to *cause* the thread to complete.

                    bool bConnectSocketToSerialThreadDone=false; // Indication of termination of ConnectSocketToSerial thread
                    bool bStop=false; // Used to tap ConnectSocketToSerial thread on the shoulder
                    int arInfo[2]={nAuxListenSock,(int)&bStop};

                    RUNTHREAD(ConnectSocketToSerialThreadFunc,(void *)arInfo,Callback(0,&bConnectSocketToSerialThreadDone)); 
                
                    // Wait for either client or the ConnectSocketToSerial thread to finish.
                    m_pSock->recv(&cAck,1,"Terminating ack",dTimeout,DerefBool,&bConnectSocketToSerialThreadDone);
TRACE("Terminating ack=%d\n",cAck);
                    if(!bConnectSocketToSerialThreadDone){
                        // Tap ConnectSocketToSerial thread on the shoulder
                        TRACE("Waiting for ConnectSocketToSerial thread to terminate...\n");
                        bStop=true;
                        CeCosTestUtils::WaitFor(bConnectSocketToSerialThreadDone,0x7fffffff);
                    }
                    TRACE("ConnectSocketToSerial thread terminated...\n");

                }
            }
        } else {
TRACE("Sending '%s'\n",(const char *)m_strOutput);
            m_pSock->sendString(m_strOutput);
            m_pSock->recv(&cAck,1,"Terminating ack",dTimeout);
TRACE("Terminating ack=%d\n",cAck);
        }

    }
    delete m_pSock;
    m_pSock=0;
    // Can't delete "this" pointer here (need to allow LogResult to operate) - allow callback to do it
}

bool CeCosTest::send(const void *pData,unsigned int nLength,const char *pszMsg,CeCosTestUtils::Duration dTimeout)
{
    return m_pSock->send(pData,nLength,pszMsg,dTimeout,IsCancelled,this);
}

bool CeCosTest::recv(const void *pData,unsigned int nLength,const char *pszMsg,CeCosTestUtils::Duration dTimeout)
{
    return m_pSock->recv(pData,nLength,pszMsg,dTimeout,IsCancelled,this);
}

void CeCosTest::Log(const char * pszFormat, ...)
{
    va_list args;
    va_start(args, pszFormat);
    CeCosTestUtils::String str;
    str.vFormat(pszFormat,args);
    va_end(args);
    LogString(str);
}

void CeCosTest::LogString(const char *psz)
{
    if(*psz){
        Trace("%s",psz);
        ENTERCRITICAL;
            if(0==strLogFile.GetLength()){
                // Interactive mode
                fputs(psz,stdout);
                fflush(stdout);
            }
            m_strOutput+=psz;
        LEAVECRITICAL;
    }
}

bool CeCosTest::sendResult(CeCosTestUtils::Duration dTimeout)
{
    bool rc=false;
    int nSize=sizeof(Result)+m_strOutput.GetLength(); // A result has space for one byte in its definition
    Result *r=(Result *)malloc(nSize);
    if(m_pSock->sendInteger(nSize,"result size",dTimeout)){
        memcpy(r->buf,m_ep.Marshall(),sizeof(ExecutionParameters::Data));
        strcpy(r->szOutput,m_strOutput);
        r->m_Status=m_Status;
        r->m_nDownloadTime=m_nDownloadTime;
        r->m_nTotalTime=m_nTotalTime;
        r->m_nMaxInactiveTime=m_nMaxInactiveTime;
        r->m_nStrippedSize=m_nStrippedSize;

        rc=send(r,nSize,"result");
    }
    free(r);
    return rc;
}

bool CeCosTest::recvResult(CeCosTestUtils::Duration dTimeout)
{
    bool rc=false;
    int nSize;
    if(m_pSock->recvInteger(nSize),"result size",dTimeout){
        Result *r=(Result *)malloc(nSize);
        // Populate with "safe" default values
        memset(r,0,nSize);
        if(recv(r,nSize,"result")){
            m_ep=ExecutionParameters(r->buf);
            m_Status=(StatusType)min(r->m_Status,StatusTypeMax);
            m_nDownloadTime=r->m_nDownloadTime;
            m_nTotalTime=r->m_nTotalTime;
            m_nMaxInactiveTime=r->m_nMaxInactiveTime;
            m_nStrippedSize=r->m_nStrippedSize;
            rc=true;
        }
        LogString(r->szOutput);
        free(r);
    }
    return rc;
}

THREAD_ID CeCosTest::RunThread(void (CeCosTest::*pThreadFunc)(void *), void *pParam, const Callback &cb)
{
    VTRACE("Entered RunThread()\n");
    // Do not spawn a thread while in possession of a mutex
    ENTERCRITICAL;
        Trace("csl=%d\n",CeCosTestUtils::m_nCriticalSectionLock);
        assert(1==CeCosTestUtils::m_nCriticalSectionLock);
    LEAVECRITICAL;
    THREAD_ID hThread;
    SetPath(m_strPath);
    if(0==cb.m_pProc && 0==cb.m_pParam){
        Trace("RunThread: - blocking call\n");
        // No callback, so no need to create a thread:
        (this->*pThreadFunc)(pParam);
        hThread=(THREAD_ID)-1;
    } else {
        ThreadInfo *pInfo=new ThreadInfo; // SThreadFunc will delete
        pInfo->pTest=this;
        pInfo->pFunc=pThreadFunc;
        pInfo->pParam=pParam; // Param to pass to pThreadFunc
        pInfo->cb=cb;
#ifdef _WIN32
        DWORD dwID;
        hThread=pInfo->hThread=CreateThread (0,0,SThreadFunc, pInfo, 0, &dwID);
        Trace("RunThread: - non-blocking call (new thread=%x)\n",dwID);
#else
        VTRACE("RunThread():Calling pthread_create()\n");
        int pc_rc, pd_rc;
        pc_rc = pthread_create(&pInfo->hThread, NULL, SThreadFunc, pInfo);
        Trace("RunThread: - non-blocking call (new thread=%x)\n",pInfo->hThread);
        VTRACE("RunThread(): pthread_create() returned <%d>\n", pc_rc);
        if (pc_rc != 0) {
            VTRACE("RunThread(): pthread_create failed\n");
            perror("RunThread(): pthread_create failed with : ");
            hThread=pInfo->hThread=0;
        } else {

            VTRACE("RunThread(): Calling pthread_detach\n");
            pd_rc = pthread_detach(pInfo->hThread);
            VTRACE("RunThread(): pthread_detach returned <%d>\n", pd_rc);

            if (pd_rc != 0) {
                Trace("RunThread(): pthread_detach failed\n");
                perror("RunThread(): pthread_detach failed with : ");
                hThread=pInfo->hThread=0;
            }
        }
        VTRACE("RunThread(): returned from pthread calls - exiting RunThread()\n");
#endif
    }
    return hThread;
}

THREADFUNC CeCosTest::SThreadFunc (void *pParam)
{
    VTRACE("SThreadFunc()\n");
    ThreadInfo *pInfo=(ThreadInfo*)pParam;
    #ifdef _WIN32
    __try {
        // Call what we are instructed to (e.g. LocalThreadFunc):
        (pInfo->pTest->*pInfo->pFunc)(pInfo->pParam);
    }
    __except ( CeCosTestUtils::FilterFunction(GetExceptionInformation() )) 
    { 
        TRACE("Handling exception...\n");
    }
    #else
    try {
        // Call what we are instructed to (e.g. LocalThreadFunc):
        (pInfo->pTest->*pInfo->pFunc)(pInfo->pParam);
    }
    catch (...){
        TRACE("Exception caught!!!\n");
    }
    #endif

    // Call the Callback:
    VTRACE("SThreadFunc(): Calling InvokeCallback()\n");
    pInfo->pTest->InvokeCallback(pInfo->cb);
    // No more references to pInfo->pTest from now on...
    VTRACE("SThreadFunc(): deleting (ThreadInfo)pInfo\n");
    #ifdef _WIN32
    CloseHandle(pInfo->hThread);
    #endif
    delete pInfo; 
    TRACE("Thread exiting\n");
    return 0;
}

// Call the given Callback function with the given argument:
void CeCosTest::InvokeCallback(const CeCosTest::Callback &cb)
{
    if(cb.m_pProc){
        cb.m_pProc (this, cb.m_pParam);
    } else if (cb.m_pParam) {
        // No function - just a flag:
        *(char *)cb.m_pParam=1;
    }
}

// This function may be run as a thread (win32) or called (unix)
void CeCosTest::DriveGdb(void *pParam)
{
    const CeCosTestUtils::String *arstrCmd=(const CeCosTestUtils::String *)pParam;
    int nCmdIndex=0;

    SetStatus(NotStarted);

    m_nMaxInactiveTime=0;
    m_nTotalTime=0;
    m_nDownloadTime=0;

    m_bDownloading=!Sim();

    m_tBase=GdbTime();
    m_tBase0=GdbTime();
    m_tWallClock0=CeCosTestUtils::Time::Now();
    
    TRACE("DriveGdb()\n");

    bool bBlockingReads;
    int nLastGdbInst=0;

    #ifdef _WIN32
    bBlockingReads=true;
    #else 
    bBlockingReads=false;
    #endif

    // Loop until 1 of:
    //      1. Timeout detected
    //      2. Gdb process is dead
    //      3. At a gdb prompt and no more commands to send to gdb
    //      4. Pipe read failure
    //      5. Pipe write failure
    do {
        CeCosTestUtils::String str;
        int readrc=ReadPipe(m_rPipeHandle,str,bBlockingReads);
        #ifdef _WIN32
        VTRACE("Blocking read returned %d\n",readrc);
        #endif
        switch(readrc){
            case 0:
                CeCosTestUtils::Sleep(250); // only unix will execute this
                break;
            case -1:
                goto Done; // case 4
                break;
            default:
                LogTimeStampedOutput(str);
                
                if(m_strOutput.GetLength()>20000){
                    LogString("\n>>>> Infra FAIL\n*** too much output ***\n>>>>\n");
                    SetStatus(Fail);
                    goto Done;
                }
                
                if(strstr(m_strOutput,Sim()?"Starting program: ":"Continuing.")){
                    SetStatus(NoResult);
                }
                
                m_tBase=GdbTime();
                if(!BreakpointsOperational() && strstr(m_strOutput,"EXIT:")){
                    goto Done;
                }
                
                if(AtGdbPrompt()){
                    m_tBase=GdbTime();
                    VTRACE("DriveGdb(): Got gdb prompt\n");
                    // gdb's output included one or more prompts
                    // Send another command along
                    if(0==arstrCmd[nCmdIndex].GetLength()){
                        // Nothing further to say to gdb - exit
                        VTRACE("DriveGdb():No more commands to send gdb - exit\n");
                        goto Done; // case 3
                    } else {
                        CeCosTestUtils::String strCmd(arstrCmd[nCmdIndex++]);
                        
                        // If at a prompt and we can see a GDB instruction, send it down
                        const char *pszGdbcmd=strstr(nLastGdbInst+(const char *)m_strOutput,"GDB:");
                        if(pszGdbcmd){
                            pszGdbcmd+=4;
                            char cTerm;
                            if('<'==*pszGdbcmd){
                                cTerm='>';
                                pszGdbcmd++;
                            } else {
                                cTerm='\n';
                            }

                            const char *c=strchr(pszGdbcmd,cTerm);
                            if(c){
                                int nLen=c-pszGdbcmd;
                                nLastGdbInst=c+1-(const char *)m_strOutput;
                                char *buf=strCmd.GetBuffer(nLen);
                                strncpy(buf,pszGdbcmd,nLen);
                                buf[nLen]='\0';
                                nCmdIndex--; // undo increment above
                            }
                        }
                        strCmd+='\n';
                        LogString(strCmd);

                        if(!WritePipe(m_wPipeHandle,strCmd)){
                            Trace("Writepipe returned error\n");
                            goto Done; // case 5
                        }                        

                        if(0==strcmp(strCmd,"run\n")||0==strcmp(strCmd,"cont\n")){
                            m_tBase=GdbTime();
                            m_bDownloading=false;
                        }
                    }
                }
                break;
        }

        if (Sim() && GdbTime()-m_tBase>PRIORITYLATCH){
            VTRACE("DriveGdb(): Calling LowerGdbPriority()\n");
            LowerGdbPriority();
        }
    } while (GdbProcessAlive() && CheckForTimeout()); // cases 2 and 1
    
Done:
    
    Trace("DriveGdb() - done\n");

    Suck();
    if(GdbProcessAlive() && AtGdbPrompt()){
        LogString("bt\n");
		WritePipe(m_wPipeHandle,"bt\n");
        Suck();
    }


    // Read anything gdb has to say [within limits]
    Suck();

    // order is important in case (for example to cater for cases 
    // in which FAIL follows PASS in output)
    static const char *arpszKeepAlive[]={"FAIL:","NOTAPPLICABLE:", "PASS:"}; 

    SetStatus(NoResult);
    for(int i=0;i<sizeof arpszKeepAlive/sizeof arpszKeepAlive[0];i++){
        const char *pszInfo=strstr(m_strOutput,arpszKeepAlive[i]);
        if(0!=pszInfo){
            Trace("DriveGdb: saw '%s'\n",arpszKeepAlive[i]);

            // Reset the active timeout base if we see one of these:
            switch(pszInfo[0]){
                case 'F': // fail
                    SetStatus(Fail);
                    break;
                case 'N': // notapplicable
                    SetStatus(Inapplicable);
                    break;
                case 'P': // pass
                    SetStatus(Pass);
                    break;
            }
        }
    }

    // Certain output spells failure...
    if(CeCosTest::Fail!=Status()){
        if(OutputContains("cyg_assert_fail ("))
            SetStatus(Fail);
    } else {
        static const char *arpszSignals[]={"SIGBUS", "SIGSEGV", "SIGILL", "SIGFPE", "SIGSYS", "SIGTRAP"};
        for(int i=0;i<sizeof arpszSignals/sizeof arpszSignals[0];i++){
            CeCosTestUtils::String str1,str2;
            str1.Format("signal %s",arpszSignals[i]);
            str2.Format("handle %s nostop",arpszSignals[i]);
            if(OutputContains(str1)&&!OutputContains(str2)){
                SetStatus(Fail);
                break;
            }
        }
    }

    // Check for expect: strings
    static const char szExpect[]="EXPECT:";
    static const int  nExpectLen=sizeof szExpect-1;
    for(const char*c=strstr(m_strOutput,szExpect);c;c=strstr(c,szExpect)){
        c+=nExpectLen;
        if('<'==*c){
            c++;
            const char *d=strchr(c,'>');
            if(d){
                unsigned int nLen=d-c;
                do {
                    d++;
                } while (isspace(*d));
                // Skip timestamp
                if('<'==*d){
                    d=strchr(d,'>');
                    if(0==d){
                        continue;
                    }
                    do {
                        d++;
                    } while (isspace(*d));
                }
                // Now d points to output of length nLen expected to compare equal to that at c
                if(strlen(d)>=nLen && 0!=strncmp(c,d,nLen) && '\n'==d[nLen]) {
                    LogString("EXPECT:<> failure\n");
                    SetStatus(Fail);
                }
            }
        }
    }

    m_nTotalTime=CeCosTestUtils::Time::Now()-m_tWallClock0;
    Trace("Exiting DriveGdb()\n");

}




CeCosTestUtils::Time CeCosTest::GdbTime()
{
    return Sim()?GdbCpuTime():CeCosTestUtils::Time::Now();
}

bool CeCosTest::CheckForTimeout()
{
    bool rc=false;
    if(TimeOut!=m_Status && DownloadTimeOut!=m_Status){
        CeCosTestUtils::Duration &dTime=m_bDownloading?m_nDownloadTime:m_nMaxInactiveTime;
        dTime=max(dTime,GdbTime()-m_tBase);
        CeCosTestUtils::Duration dTimeout=m_bDownloading?ElapsedTimeout():ActiveTimeout();
        if(dTimeout!=NOTIMEOUT && dTime>dTimeout) {
            Log("\n*** Timeout - %s time " WFS " exceeds limit of " WFS "\n",
                m_bDownloading?"download":"max inactive",WF(dTime),WF(dTimeout));
            SetStatus(m_bDownloading?DownloadTimeOut:TimeOut);
        } else if(CeCosTestUtils::Time::Now()-m_tWallClock0>max(3*dTimeout,15*60*1000)){
            Log("\n*** Timeout - total time " WFS " exceeds limit of " WFS "\n",
                WF(CeCosTestUtils::Time::Now()-m_tWallClock0),WF(max(3*dTimeout,15*60*1000)));
            SetStatus(m_bDownloading?DownloadTimeOut:TimeOut);
        } else {
            rc=true;
        }
    }
    return rc;
}

void CeCosTest::Trace(const char * pszFormat, ...)
{
    if(CeCosTestUtils::IsTracingEnabled()){
        va_list marker;
        va_start (marker, pszFormat);
        CeCosTestUtils::String str;
        str.vFormat(pszFormat,marker);
        va_end (marker);
        if(m_strExecutable.GetLength()>0){
            CeCosTestUtils::Trace("[%s] %s",ExecutableTail(),(const char *)str);
        } else {
            CeCosTestUtils::Trace("%s",(const char *)str);
        }
    }
}

const char * const CeCosTest::Title() const { 
    return m_strTitle;
}


void CeCosTest::SetExecutable(const char *pszExecutable)
{
    m_strOutput="";
    m_strResultString="";
    struct stat buf;
    m_strExecutable=pszExecutable;
    if(0==stat(m_strExecutable,&buf)){
        m_nFileSize=buf.st_size;
        CeCosTestUtils::String strSize(arTargetInfo[m_ep.Target()].pszPrefix);
        strSize+="-size ";
        strSize+=CygPath(Executable());

        FILE *f=POPEN(strSize,"r");
        if(f){
            char buf[256];
            fgets(buf,sizeof buf-1,f);
            fgets(buf,sizeof buf-1,f);
            PCLOSE(f);
            const char *c=buf;
            while(*c && isspace(*c))c++;
            m_nStrippedSize=atoi(c);
            Trace("size1=%d\n",atoi(c));
            while(isdigit(*c))c++;
            while(*c && isspace(*c))c++;
            Trace("size2=%d\n",atoi(c));
            m_nStrippedSize+=atoi(c);
            if(0==m_nStrippedSize){
                m_nStrippedSize=m_nFileSize;
            }
        } else {
            m_nStrippedSize=m_nFileSize;
        }
    } else {
        m_nFileSize=0;
    }
}

void CeCosTest::GetSimpleHostName(CeCosTestUtils::String &str)
{
    str.SetLength(256);
    if(0!=gethostname(str.GetBuffer(),256)){
        str="unknown";
    } else {
        // Remove all after a '.'
        const char *c=strchr(str,'.');
        if(c){
            str.SetLength(c-(const char *)str);
        }
    }
    str.ReleaseBuffer();
}

void CeCosTest::SetTimeouts(CeCosTestUtils::Duration dActive, CeCosTestUtils::Duration dElapsed)
{
    m_ep.SetActiveTimeout (dActive);
    m_ep.SetElapsedTimeout(dElapsed);
}

void CeCosTest::CloseSocket (){
    delete m_pSock;
    m_pSock=0;
}

bool CeCosTest::AtGdbPrompt()
{
    return
        m_strOutput.GetLength()>=nGdbPromptLen && 
		0==strcmp((const char *)m_strOutput+m_strOutput.GetLength()-nGdbPromptLen,szGdbPrompt);
}


bool CeCosTest::Suck(CeCosTestUtils::Duration d)
{
    // Read until:
    //     8k read
    //     timeout elapsed
    //     gdb prompt reached
    //     pipe error
    CeCosTestUtils::Time t0=CeCosTestUtils::Time::Now();
    int nLength=0;
    while(nLength<8192 && m_rPipeHandle && !AtGdbPrompt() && CeCosTestUtils::Time::Now()-t0<d){
       	CeCosTestUtils::String str;
        int n=ReadPipe(m_rPipeHandle,str);
        if(n>0){
    		LogTimeStampedOutput(str);
            nLength+=n;
        } else if (n<0) {
            break;
        }
	}
    return nLength>0;
}

void CeCosTest::LogTimeStampedOutput(const char *psz)
{
    CeCosTestUtils::String str(psz);
    // Timestamp the output at each '\n'
    int nLen=m_strOutput.GetLength();
    char cPrev=(0==nLen?'\0':((const char *)m_strOutput)[nLen-1]);

    char *c=str.GetBuffer();
    const char *d=c;
    while(*c){
        if('\n'==cPrev){
            char cSav=*c;
            *c='\0';
            LogString(d);
            CeCosTestUtils::Duration &dTime=m_bDownloading?m_nDownloadTime:m_nMaxInactiveTime;
            dTime=max(dTime,GdbTime()-m_tBase);

            CeCosTestUtils::String strTime;
            strTime.Format("<" WFS "/" WFS ">\t",WF(GdbTime()-m_tBase0), WF(GdbTime()-m_tBase));
            //strTime.Format("<%03d.%d> ",t/1000,(t%1000)/100);
            LogString(strTime);
            *c=cSav;
            d=c;
        }
        cPrev=*c;
        c++;
    }
    LogString(d);
    str.ReleaseBuffer();

}

#ifdef _WIN32
BOOL WINAPI HandlerRoutine(
  DWORD dwCtrlType   //  control signal type
)
{
    return TRUE;
}
#endif


bool CeCosTest::InteractiveGdb(const CeCosTestUtils::String &strHost,int nPort,char **argv)
{
    bool rc=false;
    Log("Waiting to connect to a server...\n");
    if(ConnectForExecution(strHost,nPort)){
        Log("Connected\n");
        CeCosTestUtils::String strHostPort;
        // Big timeout here because we have to wait for the target to be reset
        if(!m_pSock->recvString(strHostPort,"Port name",120*1000)){
            Log("Failed to read host:port from server - %s\n",(const char *)m_pSock->SocketErrString());
        } else if(!CeCosTestUtils::IsLegalHostPort(strHostPort)){
            Log("%s\n",(const char *)strHostPort);
        } else {
            Log("Use %s\n",(const char *)strHostPort);
            CeCosTestUtils::String strGdb(arTargetInfo[Target()].pszPrefix);
            strGdb+="-gdb";
            #ifdef _WIN32
            SetConsoleCtrlHandler(HandlerRoutine,TRUE);
            int n=_spawnvp(_P_WAIT,strGdb,argv);
            if(-1==n){
                Log("Failed to spawn %s\n",(const char *)strGdb);
            } else {
                rc=(0==n);
            }
            SetConsoleCtrlHandler(HandlerRoutine,FALSE);
            #else
            int pid=fork();
            switch(pid){
                case -1:
                    fprintf(stderr,"fork failed\n");
                    pid=0;
                    break;  
                case 0:
    		        // Process is created (we're the child)
                    execvp(strGdb,argv);
                    Log("Error invoking gdb - %s\n",strerror(errno));
                    exit(1);
                    break;
                default:
    		        // Process is created (we're the parent)
                    {
                        int stat;
                        waitpid(pid,&stat,0);
                        rc=(0==stat);
                    }
                    break;
             }
            #endif
        }
        Log("Gdb terminated\n");
        // Tell the server we're through
        char cAck=123;
        m_pSock->send(&cAck,1,"Terminating ack");
    } else {
        Log("Failed to connect to a server...\n");
    }
    return rc;
}


// Convert a path to something a cygwin tool will understand.  Used when invoking -size and -gdb

CeCosTestUtils::String CeCosTest::CygPath (const char *pszPath)

{

    #ifdef _WIN32

    CeCosTestUtils::String str;

    char *buf=str.GetBuffer(MAX_PATH);

    char *pszFname;

    if(::GetFullPathName(pszPath,MAX_PATH,1+buf, &pszFname)){

        ::GetShortPathName(1+buf,1+buf,MAX_PATH); // ignore errors

        buf[0]='/';

		buf[2]=buf[1];

        buf[1]='/';

        for(int i=3;buf[i];i++){

            if('\\'==buf[i]){

                buf[i]='/';

            }

        }

        str.ReleaseBuffer();

        return str;

    }

    #endif

    return pszPath;

}

