/* $Id: tstRTR0Common.h $ */
/** @file
 * IPRT R0 Testcase - Common header.
 */

/*
 * Copyright (C) 2010-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */

#ifndef IPRT_INCLUDED_SRC_testcase_tstRTR0Common_h
#define IPRT_INCLUDED_SRC_testcase_tstRTR0Common_h
#ifndef RT_WITHOUT_PRAGMA_ONCE
# pragma once
#endif

#include <iprt/stdarg.h>
#include <iprt/string.h>
#include "tstRTR0CommonReq.h"


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** Global error buffer used by macros and inline functions in this file. */
static char g_szErr[2048];
/** The number of errors reported in this g_szErr. */
static uint32_t volatile g_cErrors;


/**
 * Service request handler prolog.
 *
 * Returns if the input is invalid.  Initializes the return packet as well as
 * the globals (g_szErr, g_cErrors).
 *
 * @param   pReqHdr         The request packet header.
 */
#define RTR0TESTR0_SRV_REQ_PROLOG_RET(pReqHdr) \
    do \
    { \
        if (!VALID_PTR(pReqHdr)) \
            return VERR_INVALID_PARAMETER; \
        \
        PRTTSTR0REQ pReq    = (PRTTSTR0REQ)(pReqHdr); \
        size_t      cchErr  = pReqHdr->cbReq - sizeof(pReq->Hdr); \
        if (cchErr < 32 || cchErr >= 0x10000) \
            return VERR_INVALID_PARAMETER; \
        pReq->szMsg[0] = '\0'; \
        \
        /* Initialize the global buffer. */ \
        memset(&g_szErr[0], 0, sizeof(g_szErr)); \
        ASMAtomicWriteU32(&g_cErrors, 0); \
    } while (0)


/**
 * Service request handler epilog.
 *
 * Copies any errors or messages into the request packet.
 *
 * @param   pReqHdr         The request packet header.
 */
#define RTR0TESTR0_SRV_REQ_EPILOG(pReqHdr) \
    do \
    { \
        PRTTSTR0REQ pReq    = (PRTTSTR0REQ)(pReqHdr); \
        size_t      cbErr   = pReqHdr->cbReq - sizeof(pReq->Hdr); \
        if (g_szErr[0] && pReq->szMsg[0] != '!') \
            RTStrCopyEx(pReq->szMsg, (cbErr), g_szErr, sizeof(g_szErr) - 1); \
    } while (0)


/**
 * Implement the sanity check switch-cases of a service request handler.
 */
#define RTR0TESTR0_IMPLEMENT_SANITY_CASES() \
    case RTTSTR0REQ_SANITY_OK: \
        break; \
    case RTTSTR0REQ_SANITY_FAILURE: \
        RTR0TestR0Error("42failure42%4096s", ""); \
        break

/**
 * Implements the default switch-case of a service request handler.
 * @param   uOperation          The operation.
 */
#define RTR0TESTR0_IMPLEMENT_DEFAULT_CASE(uOperation) \
    default: \
        RTR0TestR0Error("Unknown test #%d", (uOperation)); \
        break


/**
 * Macro for checking the return code of an API in the ring-0 testcase.
 *
 * Similar to RTTESTI_CHECK_RC.
 *
 * @param   rcExpr      The expression producing the return code.  Only
 *                      evaluated once.
 * @param   rcExpect    The expected result.  Evaluated multiple times.
 */
#define RTR0TESTR0_CHECK_RC(rcExpr, rcExpect) \
    do { \
        int rcCheck = (rcExpr); \
        if (rcCheck != (rcExpect)) \
            RTR0TestR0Error("line %u: %s: expected %Rrc, got %Rrc", __LINE__, #rcExpr, (rcExpect), rcCheck); \
    } while (0)

/**
 * Same as RTR0TESTR0_CHECK_RC + break.
 */
#define RTR0TESTR0_CHECK_RC_BREAK(rcExpr, rcExpect) \
    if (1) \
    { \
        int rcCheck = (rcExpr); \
        if (rcCheck != (rcExpect)) \
        { \
            RTR0TestR0Error("line %u: %s: expected %Rrc, got %Rrc", __LINE__, #rcExpr, (rcExpect), rcCheck); \
            break; \
        } \
    } else do { } while (0)

/**
 * Macro for checking the return code of an API in the ring-0 testcase.
 *
 * Similar to RTTESTI_CHECK_MSG
 *
 * @param   expr            The expression to evaluate.
 * @param   DetailsArgs     Format string + arguments - in parenthesis.
 */
#define RTR0TESTR0_CHECK_MSG(expr, DetailsArgs) \
    do { \
        if (!(expr)) \
        { \
            RTR0TestR0Error("line %u: expression failed: %s - ", __LINE__, #expr); \
            RTR0TestR0AppendDetails DetailsArgs; \
        } \
    } while (0)

/**
 * Same as RTR0TESTR0_CHECK_MSG + break.
 */
#define RTR0TESTR0_CHECK_MSG_BREAK(expr, DetailsArgs) \
    if (!(expr)) \
    { \
        RTR0TestR0Error("line %u: expression failed: %s - ", __LINE__, #expr); \
        RTR0TestR0AppendDetails DetailsArgs; \
        break; \
    } else do { } while (0)

/**
 * Same as RTR0TESTR0_CHECK_MSG + return @a rcRete.
 */
#define RTR0TESTR0_CHECK_MSG_RET(expr, DetailsArgs, rcRet) \
    do { \
        if (!(expr)) \
        { \
            RTR0TestR0Error("line %u: expression failed: %s - ", __LINE__, #expr); \
            RTR0TestR0AppendDetails DetailsArgs; \
            return (rcRet); \
        } \
    } while (0)

/**
 * Macro for skipping a test in the ring-0 testcase.
 */
#define RTR0TESTR0_SKIP() \
    do { \
        RTR0TestR0Skip("line %u: SKIPPED", __LINE__); \
    } while (0)

/**
 * Same as RTR0TESTR0_SKIP + break.
 */
#define RTR0TESTR0_SKIP_BREAK() \
    if (1) \
    { \
        RTR0TestR0Skip("line %u: SKIPPED", __LINE__); \
        break; \
    } else do { } while (0)


/**
 * Report an error.
 */
void RTR0TestR0Error(const char *pszFormat, ...)
{
    size_t off = RTStrNLen(g_szErr, sizeof(g_szErr) - 1);
    size_t cbLeft = sizeof(g_szErr) - off;
    if (cbLeft > 10)
    {
        char *psz = &g_szErr[off];
        if (off)
        {
            *psz++  = '\n';
            *psz++  = '\n';
            cbLeft -= 2;
        }
        *psz++ = '!';
        cbLeft--;

        va_list va;
        va_start(va, pszFormat);
        RTStrPrintfV(psz, cbLeft, pszFormat, va);
        va_end(va);
    }
    ASMAtomicIncU32(&g_cErrors);
}


/**
 * Append error details.
 */
void RTR0TestR0AppendDetails(const char *pszFormat, ...)
{
    size_t      off = RTStrNLen(g_szErr, sizeof(g_szErr) - 1);
    va_list     va;
    va_start(va, pszFormat);
    RTStrPrintfV(&g_szErr[off], sizeof(g_szErr) - off, pszFormat, va);
    va_end(va);
}


/**
 * Informational message.
 */
void RTR0TestR0Info(const char *pszFormat, ...)
{
    size_t off = RTStrNLen(g_szErr, sizeof(g_szErr) - 1);
    size_t cbLeft = sizeof(g_szErr) - off;
    if (cbLeft > 10)
    {
        char *psz = &g_szErr[off];
        if (off)
        {
            *psz++  = '\n';
            *psz++  = '\n';
            cbLeft -= 2;
        }
        *psz++ = '?';
        cbLeft--;

        va_list va;
        va_start(va, pszFormat);
        RTStrPrintfV(psz, cbLeft, pszFormat, va);
        va_end(va);
    }
}


/**
 * Report an error.
 */
void RTR0TestR0Skip(const char *pszFormat, ...)
{
    size_t off = RTStrNLen(g_szErr, sizeof(g_szErr) - 1);
    size_t cbLeft = sizeof(g_szErr) - off;
    if (cbLeft > 10)
    {
        char *psz = &g_szErr[off];
        if (off)
        {
            *psz++  = '\n';
            *psz++  = '\n';
            cbLeft -= 2;
        }
        *psz++ = '$';
        cbLeft--;

        va_list va;
        va_start(va, pszFormat);
        RTStrPrintfV(psz, cbLeft, pszFormat, va);
        va_end(va);
    }
    ASMAtomicIncU32(&g_cErrors);
}


/**
 * Checks if we have any error reports.
 *
 * @returns true if there are errors, false if none.
 */
bool RTR0TestR0HaveErrors(void)
{
    return ASMAtomicUoReadU32(&g_cErrors) > 0;
}

#endif /* !IPRT_INCLUDED_SRC_testcase_tstRTR0Common_h */

