(file) Return to Run.cpp CVS log (file) (dir) Up to [OMI] / omi / nits / base

File: [OMI] / omi / nits / base / Run.cpp (download)
Revision: 1.1, Mon Apr 20 17:19:53 2015 UTC (9 years, 2 months ago) by krisbash
Branch: MAIN
CVS Tags: OMI_1_0_8_2, OMI_1_0_8_1, HEAD
OMI 1.0.8-1

//*****************************************************************************
//  Copyright (C) 2007 Microsoft Corporation
//  All rights reserved.
//*****************************************************************************

#include "Run.h"
#ifdef _MSC_VER
    #include <windows.h>
    #include <strsafe.h>
#else
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
#endif
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <pal/strings.h>
#include <pal/file.h>

using namespace std;
TestSystem::Run g_run;

NITS_EXPORT bool NitsParseArgument(_In_z_ char *str)
{
    wchar_t* wide = ConvertStringToW(str);
    bool result = g_run.ParseArgument(wide);
    delete [] wide;
    return result;
}

NITS_EXPORT int NitsExecuteRun()
{
    return g_run.Execute();
}

NITS_EXPORT int NitsCheckVersion(int version)
{
    if (TestSystem::SharedMemoryVersion == version)
    {
        return 1;
    }

    Tprintf(PAL_T("ERROR: Shared memory version mismatch. Please unload and update NITS binaries.\n"));
    return 0;
}

class BSTRBuffer
{
public:
    BSTRBuffer(_In_z_ const PAL_Char *text)
    {
        unsigned count = (unsigned)Tcslen(text);
 
        unsigned available = (sizeof(m_data) - sizeof(unsigned))/sizeof(PAL_Char) - 1;
        if (count > available)
        {
            count = available;
        }

        PAL_Char *string = GetBSTR();
        memcpy(string, text, count * sizeof(PAL_Char));
        string[count] = L'\0';        
        *reinterpret_cast<unsigned *>(m_data) = count * sizeof(PAL_Char);
    }

    PAL_Char *GetBSTR()
    {    
        return reinterpret_cast<PAL_Char *>(m_data + sizeof(unsigned));
    }
private:
    char m_data[256];
};

using namespace std;
using namespace TestSystem;

bool InstallInjector()
{
#ifdef _MSC_VER
	/* Check for existence of nitsinj.dll in system32. */
    char path[MAX_PATH];
    unsigned length = GetSystemDirectoryA(path, MAX_PATH);

    strcpy_s(path + length, MAX_PATH - length, "\\nitsinj.dll");

    HANDLE file = CreateFileA(path, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (file == INVALID_HANDLE_VALUE)
    {
        Tprintf(PAL_T("ERROR: Injector module nitsinj.dll is not present in system32!\n"));
        return false;
    }

    CloseHandle(file);

    strcpy_s(path + length, MAX_PATH - length, "\\nitsdll.dll");
    file = CreateFileA(path, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (file == INVALID_HANDLE_VALUE)
    {
        Tprintf(PAL_T("ERROR: Framework module nitsdll.dll is not present in system32!\n"));
        return false;
    }

    CloseHandle(file);

    /* Set registry values for AppInit_DLLs. */

    /* Open HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN */
    HKEY key;
    
    RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WSMAN", 0, KEY_SET_VALUE, &key);

    /* Set NitsInjector (REG_SZ) to nitsinj.dll */
    RegSetValueEx(key, L"NitsInjector", 0, REG_SZ, (const BYTE *)L"nitsinj.dll", sizeof(L"nitsinj.dll"));

    RegCloseKey(key);
#else        
    FILE* fp = File_Open(CONFIG_TMPDIR "/NitsInstalled", "wb");    
    if(fp)
    {
        File_Close(fp);
    }
    else
    {
        Tprintf(PAL_T("ERROR: NITS could not be installed\n"));
        return false;
    }
#endif

    /* In case this was set, clear it. */
    GetGlobals().m_unload = 0;

    Tprintf(PAL_T("WARNING: Pre-existing processes are not affected by injector. To fix this, reboot.\n"));

    return true;

}

void RemoveInjector()
{
#ifdef _MSC_VER
    /* Remove registry value NitsInstalled. */

    /* Open HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows */
    HKEY key;           
    RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WSMAN", 0, KEY_SET_VALUE, &key);

    /* Remove value NitsInstalled. */    
    RegDeleteValue(key, L"NitsInjector");

    RegCloseKey(key);
#else
    int result = File_Remove(CONFIG_TMPDIR "/NitsInstalled");
    if(result != 0)
    {
        Tprintf(PAL_T("ERROR:- NITS could not be uninstalled\n"));
    }
#endif
    /* When the test run starts, make all the injectors unload. */
    GetGlobals().m_unload = 1;
}


FixtureInvocation::FixtureInvocation(_In_opt_ FixtureInvocation *parentFixtureInvocationParam, 
                                    _In_ BodyProc funcParam, 
                                    _In_ void *selfContextParam,
                                    TestFixtureType fixtureTypeParam):
    parentFixtureInvocation(parentFixtureInvocationParam),
    func(funcParam),
    selfContext(selfContextParam),
    fixtureType(fixtureTypeParam),
    executedInCurrentIteration(false)
{
}

void FixtureInvocation::Execute()
{
    if(executedInCurrentIteration)
    {
        Tprintf(PAL_T("ERROR: Internal logic error, we are executing the same fixture again during fault simulation iteration; Quitting.\n"));
        throw Exception();
    }

    executedInCurrentIteration = true;

    (*func)(selfContext);
}

Run *Run::s_run = NULL;

Run &Run::GetInstance()
{
    if (s_run == NULL)
    {
        throw Exception();
    }

    return *s_run;
}

bool Run::IsValid()
{
    return s_run != NULL;
}

Param::Param(_In_z_ const PAL_Char *name,
             _In_z_ const PAL_Char *value)
    : m_name(Copy(name)),
      m_value(Copy(value))
{
}

Param::~Param()
{
    delete [] m_name;
    delete [] m_value;
}

Module::Module(_In_z_ const PAL_Char *name)
    : m_name(Copy(name)),
      m_library(NULL)
{
}

void Module::Unload()
{
    if(m_library)
    {
        Shlib_Close(m_library);
        m_library = NULL;
    }
}

Module::~Module()
{
    delete [] m_name;

    Unload();

    for (DWORD i = 0; i < m_runs.size(); i++)
    {
        delete [] m_runs[i];
    }
}

void Module::SetLibrary(_In_ Shlib *lib)
{
    if (m_library != NULL)
    {
        throw Exception();
    }

    m_library = lib;
}

void Module::AddTest(_In_ Test *test)
{
    m_tests.push_back(test);
}

void Module::AddRun(_In_z_ const PAL_Char *command)
{
    m_runs.push_back(Copy(command));
}

void Module::RunMatchingTests(_In_z_ const PAL_Char * testNameSubstring)
{
    for (size_t i = 0; i < m_tests.size(); i++)
    {
        /* TODO/FIXME - support real wildcards, not just a substring match */
        if (Tcsstr(m_tests[i]->GetName(), testNameSubstring))
        {
            Run::GetInstance().ExecuteTest(m_tests[i], NULL);
        }
    }
}

void Module::RunTests(size_t start)
{
    for (size_t i = start; i < m_tests.size(); i++)
    {
        Run::GetInstance().ExecuteTest(m_tests[i], NULL);
    }
}

size_t Module::Find(_In_z_ const PAL_Char *test) const
{
    for (size_t i = 0; i < m_tests.size(); i++)
    {
        if (EqualsCI(m_tests[i]->GetName(), test))
        {
            return i;
        }
    }

    Tprintf(PAL_T("%T%T%T"), PAL_T("ERROR: Invalid test name '"), test, PAL_T("'\n"));
    return m_tests.size();
}

void Module::RunModuleLevelTests(bool runCleanup)
{            
    // We want to run module level tests once only if
    // 1. we are in isolation mode and we are child process
    // 2. we are in non-isolation mode in which case we will run other tests in the parent process and so we should run module setup here
    if(!(Run::GetInstance().IsChild()))
        return;

    // Find and run moduleSetups if any exist    
    for (size_t i = 0; i < m_tests.size(); i++)
    {
        Test *currentTest = m_tests[i];
        if(currentTest->IsNewInterfaceTest())
        {
            struct RegistrationInfo *r = (struct RegistrationInfo *) (currentTest->m_registration);
            if(ModuleSetupFixture == r->fixtureType)
            {
                if(!runCleanup)
                {
                    currentTest->m_cleanupMode = TestSystem::Test::DeferredCleanup;
                    // running ModuleSetups in isolation doesnt make any sense since 
                    // they are run for the module once anyways and any other test which 
                    // will run in isolation will get its ModuleSetup run in the child process
                    currentTest->m_isolation = false;
                    Run::GetInstance().ExecuteTest(currentTest, NULL, r->fixtureType);
                }
                else
                {
                    Run::GetInstance().ExecuteDeferredCleanup(currentTest);
                }
            }
        }                
    }
}

void Module::Execute()
{
    // Find and run moduleSetups if any exist    
    RunModuleLevelTests(false);

    //Iterate through the list of commands.
    //Parse each command and run relevant tests.
    for (DWORD i = 0; i < m_runs.size(); i++)
    {
        PAL_Char *command = m_runs[i];
        if (command == NULL)
        {
            //Run all tests.
            RunTests(0);
        }
        else if (StartsWith(command, PAL_T("+")))
        {
            //Search for a test and run subsequent tests.
            RunTests(Find(command + 1));
        }
        else if (StartsWith(command, PAL_T("*")))
        {
            RunMatchingTests(command + 1);
        }
        else
        {
            //Run a specific test and/or variation.
            PAL_Char *slash = Tcschr(command, PAL_T('/'));
            PAL_Char *choices = NULL;
            if (slash)
            {
                *slash = L'\0';
                choices = slash + 1;
            }

            size_t i = Find(command);
            if (i < m_tests.size())
            {
                 Run::GetInstance().ExecuteTest(m_tests[i], choices);
            }
        }
    }

    // TODO:Find and run moduleCleanups if any exist
    RunModuleLevelTests(true);
}

Run::Run()
    : m_currentTest(NULL),
      m_loadingModule(NULL),
      m_modules(NULL),
      m_params(NULL),
      m_pause(false),
      m_reset(false),
      m_debugger(NULL),
      m_binaryFilter(NULL),
      m_binaryTarget(NULL),
      m_testFilter(NULL),
      m_isolation(false),
      m_child(false),
      m_fault(false),
      m_finished(false),
      m_statistics(NULL),
      m_enabled(true),
      m_flaky(false),
      m_simMode(Manual),
      m_exclusions(NULL),
      m_exclusionsCount(0),
      m_runningFaultInjectionLoop(false),
      m_nextFixtureInvocationToExecute(0)
#ifdef _MSC_VER
      ,
      m_logger(NULL),
      m_loggerHandle(0),
      m_testCase(NULL),
      m_testCaseRepro(NULL),
      m_loggerTestResult(WTTLOG_TESTCASE_RESULT_PASS)
#endif
{
    if (s_run != NULL)
    {
        throw Exception();
    }

    s_run = this;

    m_modules = new vector<Module *>;
    m_params = new vector<Param *>;
    m_bpFault = new vector<int>;
    if (m_modules == NULL || m_params == NULL || m_bpFault == NULL)
    {
        throw Exception();
    }

    memset(&m_config, 0, sizeof(Configuration));
    memset(&m_translatedTestFilter, 0, sizeof(m_translatedTestFilter));
}

Run::~Run()
{
    ClearTestVariationNodes();    

    ClearFixtureInvocations();

    for (DWORD i = 0; i < Modules().size(); i++)
    {
        delete Modules()[i];
    }

    m_loadingModule = NULL;

    for (DWORD i = 0; i < Params().size(); i++)
    {
        delete Params()[i];
    }

    delete m_modules;
    delete m_params;
    delete m_bpFault;
    delete [] m_debugger;
    delete [] m_binaryFilter;
    delete [] m_binaryTarget;
    delete [] m_testFilter;
    delete [] m_wtt;

    s_run = NULL;
}

const PAL_Char *Run::GetParam(_In_z_ const PAL_Char *name) const
{
    for (DWORD i = 0; i < Params().size(); i++)
    {
        if (Tcscasecmp(Params()[i]->m_name, name) == 0)
        {
            return Params()[i]->m_value;
        }
    }

    return NULL;
}

void Run::AddModule(_In_z_ const PAL_Char *name)
{
    Module *p = new Module(name);
    if (p == NULL)
    {
        throw Exception();
    }
    try
    {
        Modules().push_back(p);
    }
    catch(...)
    {
        delete p;
        throw;
    }
    m_loadingModule = p;
}

bool Run::AddParam(_In_z_ const PAL_Char *name, _In_z_ const PAL_Char *value)
{
    for (DWORD i = 0; i < Params().size(); i++)
    {
        if (Equals(Params()[i]->m_name, name))
        {
            Tprintf(PAL_T("%T%T%T"), PAL_T("ERROR: Test parameter '"), name, PAL_T("' defined multiple times.\n"));
            return false;
        }
    }

    Param *p = new Param(name, value);
    if (p == NULL)
    {
        throw Exception();
    }

    Params().push_back(p);
    return true;
}

bool Run::AddRun(_In_z_ const PAL_Char *name, _In_z_ const PAL_Char *test)
{
    if (name[0] == '\0')
        return true;

    //Find out if this module is already loaded.
    Module *match = NULL;
    for (DWORD i = 0; i < Modules().size(); i++)
    {
        if (Equals(Modules()[i]->GetName(), name))
        {
            match = Modules()[i];
            break;
        }
    }
    
    if (match == NULL)
    {
        AddModule(name);
        match = m_loadingModule;

        /* Set m_runLock so that NitsIsActivated() returns true in here. This
         * prevents the test code from thinking that NITS is inactive during
         * initialization. */
        System &system = System::GetInstance();
        Globals &globals = system.GetGlobals();
        bool setRunLock = (Atomic_CompareAndSwap(&globals.m_runLock, 0, 1) == 0);

        Shlib *lib = NULL;

        lib = Shlib_Open_Injected(name, NitsReservedCallSite());

        if (setRunLock)
        {
            Atomic_CompareAndSwap(&globals.m_runLock, 1, 0);
        }


        if(!lib)
        {
            PAL_Char *err;
            err = Shlib_Err(); 
            Tprintf(PAL_T("%T%T%T%T%T"), PAL_T("ERROR: Could not load test module. File='"), tcs(name), PAL_T("', Reason="), tcs(err), PAL_T("\n"));
            if (err)
                Shlib_FreeErr(err);
            return false;
        }

        match->SetLibrary(lib);
    }

    match->AddRun(test);
    return true;
}

void Run::DumpPipe()
{
    System &system = System::GetInstance();
    Globals &globals = system.GetGlobals();
    globals.LockPipe();
    PAL_Char *globalPipe = globals.GetPipe();
    PipeOutputType globalPipeOutputType = globals.GetPipeOutputType();

    static ofstream s_log("nits.log");    
    if(globalPipeOutputType == InfoMessage)
    {
        char buf[Globals::PipeSize];
#ifdef _MSC_VER
        WideCharToMultiByte(CP_THREAD_ACP, 0, 
            globalPipe, (int)Tcslen(globalPipe) + 1, buf, sizeof(buf), NULL, NULL);
#else
        #ifdef CONFIG_ENABLE_WCHAR
            int convertCount = wcstombs(buf, globalPipe, Globals::PipeSize);
        #else
            int convertCount = Strlen(globalPipe);
            Strlcpy(buf, globalPipe, convertCount + 1);
        #endif
        buf[convertCount] = PAL_T('\0');
#endif

        s_log << buf;
                  
        Tprintf(globalPipe);
        fflush(stdout);
#ifdef _MSC_VER        
        OutputDebugString(globalPipe);
        if(m_wtt && m_logger && (globalPipe[0] != PAL_T('\0')))
        {            
            m_logger->TraceMsg(globalPipe, m_loggerHandle);
        }
#endif
    } 

#ifdef _MSC_VER
    else if(m_wtt && m_logger && (globalPipeOutputType == WttLogTestStart))
    {
        m_logger->StartTest(m_testCase, m_loggerHandle);        
    }
    else if(m_wtt && m_logger && (globalPipeOutputType == WttLogTestEnd))
    {        
        m_logger->EndTest(m_testCase, m_loggerTestResult, m_testCaseRepro, m_loggerHandle);
    }
#endif
    globals.EmptyPipe();    
    globals.UnlockPipe();
    globals.AttachDebugger();
}


NITS_EXTERN_C PAL_Uint32 THREAD_API _PipeThread(void* param)
{
    Run &run = Run::GetInstance();
    run.PipeThread();
    return 0;
}

void Run::PipeThread()
{
    System &system = System::GetInstance();
    NamedSem *event = system.GetPipeEvent();

    do {
        DumpPipe();
        NamedSem_TimedWait(event, 200);
    } while (!m_finished);

    DumpPipe();
}

static void TranslateStringList(
    _In_opt_z_ const PAL_Char *source,
    _Out_writes_(bytes) PAL_Char *dest,
    unsigned bytes)
{
    unsigned length;
    unsigned i;

    if (source == NULL)
    {
        dest[0] = PAL_T('\0');
        return;
    }

    length = (unsigned)Tcslen(source);
    size_t toUse = min(length + 1, bytes);
    
    Tcslcpy(dest, source, toUse);

    for (i = 0; i < length && i < bytes; i++)
    {
        if ((dest[i] == PAL_T(';')) ||
            (dest[i] == PAL_T(',')))
        {
            dest[i] = PAL_T('\0');
        }
    }
    dest[bytes - 1] = PAL_T('\0');
}

static Globals g_tempGlobals;

int Run::Execute()
{
    System &system = System::GetInstance();
    Globals &globals = system.GetGlobals();
    m_statistics = globals.GetStatistics();
    Thread pipeThread;
    PAL_Uint32 threadJoinResult = 0;
    memset(&pipeThread, 0, sizeof(Thread));
    
    if (m_config.traces == 0)
    {
        m_config.traces = NitsTraceAllTests;
    }

    if (m_config.mode == 0)
    {
        m_config.mode = NitsTestCompoundFault;
    }

    ptrdiff_t startTicks = CPU_GetTimeStamp();

    // translate ; or , separated list of Filtered switches into \0 separated one
    TranslateStringList(m_testFilter, m_translatedTestFilter, sizeof(m_translatedTestFilter)/sizeof(PAL_Char));

    if (IsParent())
    {        
        TranslateStringList(m_binaryFilter, globals.m_binaryFilter, (sizeof(globals.m_binaryFilter))/sizeof(PAL_Char));
        TranslateStringList(m_binaryTarget, globals.m_binaryTarget, (sizeof(globals.m_binaryTarget))/sizeof(PAL_Char));

        if (!globals.StartRun(m_reset))
        {
            Tprintf(PAL_T("ERROR: Another test run is in progress! Please run \"nits -reset\" to terminate the other run.\n"));
            return 1;
        }

#if _MSC_VER
        if (m_wtt)
        {
            wostringstream buf;
            buf << L"$LogFile:file=\"" << m_wtt << L"\",writemode=overwrite";
            m_logger = new CWTTLogger();
            m_logger->CreateLogDevice((LPWSTR)buf.str().c_str(), &m_loggerHandle);                        
        }
#endif

        if (m_reset)
        {
            //Reset globals.
            TranslateStringList(m_binaryFilter, g_tempGlobals.m_binaryFilter, (sizeof(g_tempGlobals.m_binaryFilter))/sizeof(PAL_Char));
            TranslateStringList(m_binaryTarget, g_tempGlobals.m_binaryTarget, (sizeof(g_tempGlobals.m_binaryTarget))/sizeof(PAL_Char));
            g_tempGlobals.StartRun();
            globals = g_tempGlobals;
        }

        if(Thread_CreateJoinable_Injected(&pipeThread, _PipeThread, NULL, NULL, NitsReservedCallSite()) != NO_ERROR)
        {
            globals.StopRun();
            Tprintf(PAL_T("ERROR: Failed to create pipe thread!\n"));
            return 1;
        }
    }
    
    unsigned returnCode = 0;

    //TODO: Reset XML output file here.
    //After this, everybody opens for append.

    for (DWORD i = 0; i < Modules().size(); i++)
    {
        Modules()[i]->Execute();
        Modules()[i]->Unload();
    }

    //TODO: Finish and close XML output file here.    

    if (IsParent())
    {
        if (Modules().size() > 0)
            returnCode = ReportStatistics();

        if (m_pause)
        {
            SetDefaultOptions(); //Mainly used for debugger setting.
            globals.PostPipe(PAL_T("PAUSED: Press ENTER to continue..."));
            fgetc(stdin);
        }

        if (Modules().size() > 0)
        {
            Statistics const &stats = globals.GetStats();
            ptrdiff_t stopTicks = CPU_GetTimeStamp();
            double seconds = float(stopTicks - startTicks) / 1000000.0;
            // double stackTicks = (double)stats.stackTicks / ((double)stats.stackLookups+0.00001);
            wostringstream buf;
            buf << L"TOTAL RUNNING TIME: " << seconds << L" seconds" << endl
                << L"TOTAL FAULT ITERATIONS: " << stats.faultIterations << endl
                << L"TOTAL SHOULDFAULT CALLS: " << stats.shouldFaults << endl;
    
            //double percentage = 100.0 * (double)stats.shouldFaultIPCs / ((double)stats.shouldFaults+0.00001);
            //buf << L"TOTAL CROSS-PROCESS SHOULDFAULT CALLS: " << stats.shouldFaultIPCs << L" (" << percentage << L"%)\n"
            buf << L"TOTAL STACK PROBES: " << stats.stackLookups << endl
            //    << L"AVERAGE TICKS/STACK PROBE: " << stackTicks << endl
                << L"TOTAL FRAME PROBES: " << stats.frameLookups << endl;
    
            /*unsigned remaining = stats.frameLookups;
            unsigned target = stats.frameInsertions;
            double percentage = 100.0 * (double)target / remaining;
            buf << L"TOTAL FRAME MISSES: " << target << L"/" << remaining << L" (" << percentage << L"%)\n";
    
            remaining -= target;
            target = stats.frameHashHits;
            percentage = 100.0 * (double)target / remaining;
            buf << L"REMAINDER, HASH HITS: " << target << L"/" << remaining << L" (" << percentage << L"%)\n";
    
            for (int i = 0; i < 16; i++)
            {
                if (stats.frameHits[i] == 0)
                {
                    continue;
                }
    
                remaining -= target;
                target = stats.frameHits[i];
                percentage = 100.0 * (double)target / remaining;
                buf << L"REMAINDER, LEVEL " << i+1 << L" CACHE HITS: " << target
                    << L"/" << remaining << L" (" << percentage << L"%)\n";
            }*/
            PAL_Char *postPipeStr = ConvertStringWToPalChar(buf.str().c_str());
            globals.PostPipe(postPipeStr);
            delete [] postPipeStr;
        }

        m_finished = true;

        NamedSem_Post(system.GetPipeEvent(), 1);

        Thread_Join(&pipeThread, &threadJoinResult);

        globals.StopRun();

#ifdef _MSC_VER
        if (m_logger)
        {
            m_logger->CloseLogDevice(NULL, m_loggerHandle);
            delete m_logger;
        }
#endif
    }

    for (DWORD i = 0; i < Modules().size(); i++)
        delete Modules()[i];
    Modules().clear();

    return returnCode;
}

#ifdef _MSC_VER
static void WaitTillPipeEmpty()
{
    int count = 0; 
    do
    {
        Sleep_Milliseconds(20);
        count++;
    }
    while(!GetGlobals().IsPipeEmpty() && (count < 50));
}
#endif

void Run::ExecuteDeferredCleanup(_In_ Test *test)
{
    m_currentTest = test; 
    m_currentTest->RunDeferredCleanup();
}

void Run::ExecuteTest(_In_ Test *test, _In_opt_ const PAL_Char *choices,
                      TestFixtureType executeIfTypeIs /* = BodyFixture */)
{    
    if(test->IsNewInterfaceTest())
    {         
        struct RegistrationInfo *r = (struct RegistrationInfo *) (test->m_registration);
        if(executeIfTypeIs != r->fixtureType)
            return;
    }

    m_currentTest = test;    

    //Spawn a child process to run the test for us.
    //Construct the same argument list, but for this particular test.
    wostringstream buf, repro;
    if (m_debugger)
    {
        buf << m_debugger << L" ";
        repro << m_debugger << L" ";
    }

    buf << L"nits.exe -child " << test->GetModule() << L":" << test->GetName();
    repro << L"nits.exe " << test->GetModule() << L":" << test->GetName();
    if (choices)
    {
        buf << L"/" << choices;
        repro << L"/" << choices;
    }

    if (m_config.traces == NitsTraceFailedTests)
    {
        buf << L" -trace:FailedTests";
    }
    else if (m_config.traces == NitsTraceWarnedTests)
    {
        buf << L" -trace:WarnedTests";
    }
    else if (m_config.traces == NitsTraceAllTests)
    {
        buf << L" -trace:AllTests";
    }
    else if (m_config.traces == NitsTraceAsserts)
    {
        buf << L" -trace:Asserts";
    }
    else if (m_config.traces == NitsTraceWarnings)
    {
        buf << L" -trace:Warnings";
    }
    else if (m_config.traces == NitsTraceIterations)
    {
        buf << L" -trace:Iterations";
    }
    else //NitsTraceVerbose.
    {
        buf << L" -trace:Verbose";
    }

    if (m_config.mode == NitsTestSkip)
    {
        buf << L" -mode:Skip";
    }
    else if (m_config.mode == NitsTestEnable)
    {
        buf << L" -mode:Enable";
    }
    else if (m_config.mode == NitsTestIterativeFault)
    {
        buf << L" -mode:IterativeFault";
    }
    else if (m_config.mode == NitsTestStackFault)
    {
        buf << L" -mode:StackFault";
    }
    else //NitsTestCompoundFault.
    {
        buf << L" -mode:CompoundFault";
    }

    if (m_config.breakAssert)
    {
        buf << L" -bpassert";
    }

    if (m_config.skipFlakyTests)
    {
        buf << L" -skipflaky";
    }

    if (m_fault)
    {
        buf << L" -fault";
        repro << L" -fault";
    }
    if (m_debugger)
    {
        buf << L" -debug:\"" << m_debugger << L"\"";
        repro << L" -debug:\"" << m_debugger << L"\"";
    }

    if(m_testFilter)
    {
        buf << L" -match:" << m_testFilter;
    }

    vector<Param *> &params = Params();
    for (size_t i = 0; i < params.size(); i++)
    {
        buf << L" +" << params[i]->m_name << L":" << params[i]->m_value;
        repro << L" +" << params[i]->m_name << L":" << params[i]->m_value;
    }

#ifdef _MSC_VER
    int countFailed = m_statistics[Failed] + m_statistics[Error] + m_statistics[Killed];
    int countPassed = m_statistics[Passed] + m_statistics[Faulted];
    int countSkipped = m_statistics[Skipped] + m_statistics[Omitted];

    wostringstream nameBuf;
    nameBuf << test->GetModule() << L":" << test->GetName();
    BSTRBuffer testCase(nameBuf.str().c_str());
    BSTRBuffer testCaseRepro(repro.str().c_str());
    if (m_logger)
    {
        m_testCase = testCase.GetBSTR();
        GetGlobals().PostPipe(PAL_T("WttStartTest"), WttLogTestStart);
        WaitTillPipeEmpty();
    }
#endif
#ifdef _MSC_VER
    if (m_child || !m_isolation && !test->GetIsolation())
#endif
    {
        test->Execute(choices);
    }
#ifdef _MSC_VER
    else
    {
    	// isolation not implemented currently in unix
        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        ZeroMemory(&pi, sizeof(pi));

        si.dwFlags |= STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_SHOWMINNOACTIVE;


        PAL_Char *temp = Copy(buf.str().c_str());
        if (!CreateProcess(NULL, temp, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
        {
            Tprintf(PAL_T("%T%d%T"), PAL_T("ERROR: Failed to create child process! Error="), GetLastError(), PAL_T("\n"));
            GetGlobals().SetResult(Error);
            ReportResult();
            delete [] temp;
            if (m_logger)
            {                
                // make sure all output is flushed before we record end test
                WaitTillPipeEmpty();
                m_testCase = testCase.GetBSTR();
                m_testCaseRepro = testCaseRepro.GetBSTR();
                m_loggerTestResult = WTTLOG_TESTCASE_RESULT_FAIL;
                GetGlobals().PostPipe(PAL_T("WttTestEnd"), WttLogTestEnd);                
                WaitTillPipeEmpty();
            }
            goto EndExecute;
        }

        delete [] temp;
        int timeout = test->GetTimeout() * 1000;
        if (m_debugger)
        {
            timeout = INFINITE;
        }
        if (WaitForSingleObject(pi.hProcess, timeout) == WAIT_TIMEOUT)
        {
            TerminateProcess(pi.hProcess, 1);
            GetGlobals().SetResult(Killed);
            ReportResult();
        }
        else
        {
            DWORD exitCode;
            GetExitCodeProcess(pi.hProcess, &exitCode);

            if (exitCode != 0)
            {
                GetGlobals().SetResult(Error);
                ReportResult();
            }
        }

        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }
#endif

#ifdef _MSC_VER
    long wttResult = WTTLOG_TESTCASE_RESULT_FAIL;
    if (countFailed == m_statistics[Failed] + m_statistics[Error] + m_statistics[Killed])
    {
        if (countPassed != m_statistics[Passed] + m_statistics[Faulted])
        {
            wttResult = WTTLOG_TESTCASE_RESULT_PASS;
        }
        else if (countSkipped != (m_statistics[Skipped] + m_statistics[Omitted]))
        {
            wttResult = WTTLOG_TESTCASE_RESULT_SKIPPED;
        }
    }

    if (m_logger)
    {        
        // make sure all output is flushed before we record end test
        WaitTillPipeEmpty();
        m_testCase = testCase.GetBSTR();
        m_testCaseRepro = testCaseRepro.GetBSTR();
        m_loggerTestResult = wttResult;
        GetGlobals().PostPipe(PAL_T("WttTestEnd"), WttLogTestEnd);
        WaitTillPipeEmpty();
    }
#endif

#ifdef _MSC_VER
EndExecute:
#endif
    if(test->m_deleteMeAfterRun)
    {
        delete test;
    }
}

void Run::SetDefaultOptions()
{
    Globals &globals = GetGlobals();

    m_exclusions = NULL;
    m_exclusionsCount = 0;
    m_enabled = m_config.mode >= NitsTestEnable;
    m_simMode = m_fault ? Automatic : Manual;

    globals.Reset();
    globals.SetConfiguration(m_config);
    if (m_debugger != NULL)
    {
        globals.SetDebugger(m_debugger);
    }
}

void Run::ReportResult()
{
    Test &test = GetCurrentTest();

    const wchar_t *results[] = {L"[Faulted]",
                        L"[Passed] ",
                        L"[Skipped]",
                        L"[Omitted]",                        
                        L"[Killed] ",
                        L"[Failed] ",
                        L"[Error]  "};

    Globals &globals = GetGlobals();
    Result result = globals.GetResult();
    double seconds = globals.GetRunTime();

    if (result < Faulted || result > Error)
    {
        FatalError();
        return;
    }

    m_statistics[result]++;

    if(test.m_filteredOutByUser)
        return;

    if (result < Killed && globals.GetConfiguration().traces == NitsTraceFailedTests)
    {
        return;
    }
    
    wostringstream buf;
    buf << L"\t" << results[result] << L" ";
    Buffer wrappedBuf(buf);
    test.PrintName(wrappedBuf, !m_isolation);
    buf << L"\t" << seconds;
    buf << L"\n";

    //TODO: Dump XML output in addition to stderr.
    PAL_Char *postPipeStr = ConvertStringWToPalChar(buf.str().c_str());
    globals.PostPipe(postPipeStr);
    delete [] postPipeStr;
}

unsigned Run::ReportStatistics()
{
    int passed = m_statistics[Faulted] +
                 m_statistics[Passed];
    int failed = m_statistics[Killed] +
                 m_statistics[Failed] +
                 m_statistics[Error];
    int total = passed + failed;

    const wchar_t *results[] = {L"Faulted: ",
                                L"Passed:  ",
                                L"Skipped: ",
                                L"Omitted: ",                                                        
                                L"Killed:  ",
                                L"Failed:  ",
                                L"Error:   "};


    //TODO: Include relevant summary information in XML output.
    wostringstream buf;
    buf << L"\n\nSummary:\n";
    for (int i = 0; i < ResultCount; i++)
    {
        buf << L"\t" << results[i] << L"\t" << m_statistics[i] << L"\n";
    }

    buf << L"\n"
           L"\tSuccesses:\t" << passed << L"\n"
           L"\tFailures:\t" << failed << L"\n"
           L"\tTotal:\t\t" << total << L"\n";
    
    Globals &globals = GetGlobals();
    PAL_Char *postPipeStr = ConvertStringWToPalChar(buf.str().c_str());
    globals.PostPipe(postPipeStr);
    delete [] postPipeStr;
    return failed ? 1 : 0;
}

bool Run::IsSiteExcluded(int site) const
{
    for (int i = 0; i < m_exclusionsCount; i++)
    {
        //ignore everything if exclusions is zero
        if (m_exclusions[i] == 0)
        {
            return true;
        }

        //check for specific exclusion
        if (m_exclusions[i] == site)
        {
            return true;
        }
    }
    
    return false;
}

void Run::SetExclusions(_In_reads_opt_(count) int sites[], int count)
{
    m_exclusions = sites;
    m_exclusionsCount = count;
}

bool Run::ParseArgument(_In_ wchar_t *arg)
{
	wchar_t *temp = wcschr(arg, L':');
	wchar_t *second = NULL;

    if (arg[0] != L'-' && arg[0] != L'+')
    {
        /* This is not an option. Must be a test path. */

        /* Ignore the colon in drive-letter paths. */
        if (temp && (temp[1] == L'\\' || temp[1] == L'/'))
            temp = wcschr(temp + 1, L':');

        /* New test path format matches WinDBG symbol. */
        if (temp == NULL)
            temp = wcschr(arg, L'!');
    }

    if (temp)
    {
        *temp = L'\0';
        second = temp + 1;
    }

    if (!Wcscasecmp(arg, L"-localinjection"))
    {
        if (!ConfigureLocalInjection())
            return false;
    }
    else if (!Wcscasecmp(arg, L"-bpassert"))
    {
        m_config.breakAssert = true;
    }
    else if (!Wcscasecmp(arg, L"-skipflaky"))
    {
        m_config.skipFlakyTests = true;
    }
    else if (!Wcscasecmp(arg, L"-bpfault"))
    {
        m_config.breakFault = true;
        if (second == NULL)
        {
            Tprintf(PAL_T("ERROR: 'bpfault' option requires an iteration number!\n"));
            return false;
        }

        m_bpFault->push_back(Wcstoul(second, 0, 10));
    }
#ifdef _MSC_VER
    else if (!Wcscasecmp(arg, L"-debug"))
    {
        if (m_debugger)
            delete [] m_debugger;

        m_debugger = ConvertStringWToPalChar(second);
    }
#endif
    else if (!Wcscasecmp(arg, L"-file"))
    {
        //Open file, tokenize, and parse contents.
        if (second == NULL)
        {
            Tprintf(PAL_T("ERROR: 'file' option requires a filename!\n"));
            return false;
        }

        char buf[MAX_PATH];
#ifdef _MSC_VER
        WideCharToMultiByte(CP_THREAD_ACP, 0, 
                second, (int)wcslen(second) + 1, buf, sizeof(buf), NULL, NULL);
#else
        wcstombs(buf, second, MAX_PATH);
#endif

        ifstream file(buf);
        if (!file.good())
        {
            Tprintf(PAL_T("%T%T%T"), PAL_T("ERROR: file '"), second, PAL_T("' could not be loaded!\n"));
            return false;
        }
        while (file.good())
        {
            wchar_t widebuf[MAX_PATH];

            memset(buf, 0, sizeof(buf));

            file >> buf;

            // allow empty lines or commented lines with #
            if (buf[0] == '\0' || buf[0] == '#')
                continue;
            
            int size = WcsStrlcpy(widebuf, buf, MAX_PATH);
            if (size == 0)
            {
                throw Exception();
            }

            if (!ParseArgument(widebuf))
                return false;
        }
    }
    else if (!Wcscasecmp(arg, L"-filter"))
    {
        if (m_binaryFilter)
            delete [] m_binaryFilter;

        m_binaryFilter = ConvertStringWToPalChar(second);
    }
    else if (!Wcscasecmp(arg, L"-match"))
    {
        if (m_testFilter)
            delete [] m_testFilter;

        if (!second || (second[0] == L'\0'))
        {
            Tprintf(PAL_T("ERROR: '-match' option requires at least one string to match with!\n"));
            return false;
        }

        m_testFilter = ConvertStringWToPalChar(second);
    }
    else if (!Wcscasecmp(arg, L"-install"))
    {
        if (!InstallInjector())
            return false;
    }
    else if (!Wcscasecmp(arg, L"-mode"))
    {
        if (!second)
        {
            Tprintf(PAL_T("ERROR: 'mode' option requires a mode to be selected!\n"));
            return false;
        }
        else if (!Wcscasecmp(second, L"Skip"))
        {
            m_config.mode = NitsTestSkip;
        }
        else if (!Wcscasecmp(second, L"Enable"))
        {
            m_config.mode = NitsTestEnable;
        }
        else if (!Wcscasecmp(second, L"IterativeFault"))
        {
            m_config.mode = NitsTestIterativeFault;
        }
        else if (!Wcscasecmp(second, L"StackFault"))
        {
            m_config.mode = NitsTestStackFault;
        }
        else if (!Wcscasecmp(second, L"CompoundFault"))
        {
            m_config.mode = NitsTestCompoundFault;
        }
        else
        {
            Tprintf(PAL_T("ERROR: Invalid value for 'mode' option!\n"));
            return false;
        }
    }
    else if (!Wcscasecmp(arg, L"-pause"))
    {
        m_pause = true;
    }
    else if (!Wcscasecmp(arg, L"-reset"))
    {
        m_reset = true;
    }
    else if (!Wcscasecmp(arg, L"-target"))
    {
        if (m_binaryTarget)
            delete [] m_binaryTarget;

        m_binaryTarget = ConvertStringWToPalChar(second);
    }
    else if (!Wcscasecmp(arg, L"-trace"))
    {
        if (!second || !Wcscasecmp(second, L"Verbose"))
        {
            m_config.traces = NitsTraceVerbose;
        }
        else if (!Wcscasecmp(second, L"FailedTests"))
        {
            m_config.traces = NitsTraceFailedTests;
        }
        else if (!Wcscasecmp(second, L"WarnedTests"))
        {
            m_config.traces = NitsTraceWarnedTests;
        }
        else if (!Wcscasecmp(second, L"AllTests"))
        {
            m_config.traces = NitsTraceAllTests;
        }
        else if (!Wcscasecmp(second, L"Asserts"))
        {
            m_config.traces = NitsTraceAsserts;
        }
        else if (!Wcscasecmp(second, L"Warnings"))
        {
            m_config.traces = NitsTraceWarnings;
        }
        else if (!Wcscasecmp(second, L"Iterations"))
        {
            m_config.traces = NitsTraceIterations;
        }
        else
        {
            Tprintf(PAL_T("ERROR: Invalid value for 'trace' option!\n"));
            return false;
        }
    }
    else if (!Wcscasecmp(arg, L"-uninstall"))
    {
        RemoveInjector();
    }
    //V1 compatibility options.
    else if (!Wcscasecmp(arg, L"-assert"))
    {
    }
    else if (!Wcscasecmp(arg, L"-noassert"))
    {
        m_config.traces = NitsTraceAllTests;
    }
    else if (!Wcscasecmp(arg, L"-child"))
    {
        m_child = true;
    }
    else if (!Wcscasecmp(arg, L"-fault"))
    {
        m_fault = true;

        if (second)
        {
            m_config.breakFault = true;
            m_bpFault->push_back(Wcstoul(second, 0, 10));
        }
    }
#ifdef _MSC_VER
    else if (!Wcscasecmp(arg, L"-isolate"))
    {
        m_isolation = true;
    }
#endif
    else if (!Wcscasecmp(arg, L"-output"))
    {
        Tprintf(PAL_T("ERROR: 'output' option is not implemented!\n"));
        return false;
    }
    else if (!Wcscasecmp(arg, L"-skip"))
    {
        m_config.mode = NitsTestSkip;
    }
    else if (!Wcscasecmp(arg, L"-verbose"))
    {
        m_config.traces = NitsTraceVerbose;
    }
    else if (!Wcscasecmp(arg, L"-noverbose"))
    {
    }
#ifdef _MSC_VER
    else if (!Wcscasecmp(arg, L"-wtt"))
    {
        if (second == NULL)
        {
            Tprintf(PAL_T("ERROR: 'wtt' option requires a filename!\n"));
            return false;
        }

        if (m_wtt)
            delete [] m_wtt;

        m_wtt = ConvertStringWToPalChar(second);
    }
#endif
    else if (!Wcsncmp(arg, L"+", Wcslen(L"+")))
    {
        //Spurious prefast warning.
        if (wcslen(arg) < 1)
            throw Exception();

        if ((second == NULL) || (second[0] == L'\0'))
        {
            Tprintf(PAL_T("%T%T%T"), PAL_T("ERROR: Custom argument "), arg, PAL_T(" should be of the form +name:value!\n"));
            return false;
        }

        AddParam(ConvertStringWToPalChar(arg+1), ConvertStringWToPalChar(second));
    }
    else
    {
        PAL_Char *palCharArg = ConvertStringWToPalChar(arg);
        PAL_Char *palCharSecond = ConvertStringWToPalChar(second);
        bool addRunSuccess = AddRun(palCharArg, palCharSecond);
        delete [] palCharArg;
        delete [] palCharSecond;
        //Error message already printed.
        if(!addRunSuccess)
            return false;
    }

    return true;
}

PAL_Char *Run::GetTestVariationNodeNames()
{
    wostringstream buf;
    buf << L"\t          ";        

    for(size_t i = 0; i < m_currentTestVariationNodes.size(); i++)
    {
        buf << m_currentTestVariationNodes[i]->GetName() << L",";
    }

    buf << L"...\n";

    return ConvertStringWToPalChar(buf.str().c_str());
}

// TODO: Move this into PAL
static PAL_Boolean DoesTestMatch(
    _In_ PAL_Char *list,
    _In_z_ const PAL_Char *name)
{
    if (*list == '\0')
        return PAL_TRUE;

    for (;;)
    {
#ifdef _MSC_VER
#pragma prefast(push)
#pragma prefast (disable: 26006)
#endif
        if (*list == '\0')
            return PAL_FALSE;
        
        if (Tcsstr(name, list))
            return PAL_TRUE;

#ifdef _MSC_VER
#pragma prefast(pop)
#endif
        list += Tcslen(list) + 1;
    }
}

void Run::AddFixtureInvocation(_In_opt_ BodyProc parentFixtureFuncParam, _In_ BodyProc funcParam, _In_ void *selfContextParam, TestFixtureType fixtureTypeParam)
{    
    FixtureInvocation *parentFixtureInvocation = NULL;
    FixtureInvocation *fixtureInvocation = NULL;

    if(parentFixtureFuncParam != NULL)
    {
        size_t count = m_currentTestFixtureInvocations.size();

        for(size_t i = 0; i < count; i++)
        {
            fixtureInvocation = m_currentTestFixtureInvocations[i];
            if(fixtureInvocation->func == parentFixtureFuncParam)
            {
                parentFixtureInvocation = fixtureInvocation;
                break;
            }
        }

        if(parentFixtureInvocation == NULL)
        {
            Tprintf(PAL_T("ERROR: Internal logic error, cleanup is being run on a switch for which we skipped running setup; Quitting.\n"));
            throw Exception();
        }
    }

    fixtureInvocation = new FixtureInvocation(parentFixtureInvocation, funcParam, selfContextParam, fixtureTypeParam);
    if(fixtureInvocation == NULL)
    {
        Tprintf(PAL_T("ERROR: Out of memory error when adding fixture invocation; Quitting.\n"));
        throw Exception();
    }

    m_currentTestFixtureInvocations.push_back(fixtureInvocation);
}

void Run::ResetFixtureInvocationStateForRerun()
{
    size_t count = m_currentTestFixtureInvocations.size();

    for(size_t i = 0; i < count; i++)
    {
        m_currentTestFixtureInvocations[i]->executedInCurrentIteration = false;
    }

    m_nextFixtureInvocationToExecute = 0;
}

bool Run::DoneExecutingAllFixtureInvocations()
{    
    if(m_nextFixtureInvocationToExecute >= m_currentTestFixtureInvocations.size())
        return true;
    else
        return false;
}

void Run::ExecuteNextFixtureInvocationDuringFaultSim()
{    
    size_t count = m_currentTestFixtureInvocations.size();
    if(m_nextFixtureInvocationToExecute >= count)
        return;

    Globals &globals = GetGlobals();
    Fault &fault = globals.GetAutoFault();    
    FixtureInvocation *fixtureInvocation = NULL;
    fault.Toggle(false);

    if(globals.GetResult() == Passed)
    {
        m_nextFixtureInvocationToExecute++;        
        fixtureInvocation = m_currentTestFixtureInvocations[m_nextFixtureInvocationToExecute - 1];
        if(fixtureInvocation->fixtureType == BodyFixture)
        {
            fault.Toggle(true); // turn on fault injection only for body fixtures
        }

        fixtureInvocation->Execute();

        fault.Toggle(false); 
    }
    else
    {
        for(size_t i = m_nextFixtureInvocationToExecute; i < count; i++)
        {
            fixtureInvocation = m_currentTestFixtureInvocations[i];
            if((fixtureInvocation->parentFixtureInvocation != NULL) && (fixtureInvocation->parentFixtureInvocation->executedInCurrentIteration))
            {
                m_nextFixtureInvocationToExecute = i + 1;
                fixtureInvocation->Execute(); // this can not be a body fixture; it has to be a cleanup
                                              // this will in turn call NitsRunContinuation and from there it will come back to here again
                return;
            }
        }
        m_nextFixtureInvocationToExecute = count;
    }
}

void Run::ClearFixtureInvocations()
{
    size_t count = m_currentTestFixtureInvocations.size();

    for(size_t i = 0; i < count; i++)
    {
        FixtureInvocation *fixtureInvocation = m_currentTestFixtureInvocations[i];
        delete fixtureInvocation;
    }

    m_currentTestFixtureInvocations.clear();    
}

bool Run::CurrentTestMatchesTestFilter()
{    
    size_t count = m_currentTestVariationNodes.size();
    if(count == 0)
        return true;

    if(m_currentTest->IsNewInterfaceTest())
    {
        struct RegistrationInfo *r = (struct RegistrationInfo *) (m_currentTest->m_registration);
        if((r->fixtureType == ModuleSetupFixture))
            return true;
    }

    for(size_t i = 0; i < count; i++)
    {
        if(DoesTestMatch(m_translatedTestFilter, m_currentTestVariationNodes[i]->GetName()))
            return true;
    }
    return false;
}


ViewCVS 0.9.2