/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
 * testutil.h
 *
 * Utility functions for handling test errors
 *
 */

#ifndef _TESTUTIL_H
#define _TESTUTIL_H

#include "pkix.h"
#include "plstr.h"
#include "prprf.h"
#include "prlong.h"
#include "pkix_pl_common.h"
#include "secutil.h"
#include <stdio.h>
#include <ctype.h>

#ifdef __cplusplus
extern "C" {
#endif

/*
 * In order to have a consistent format for displaying test information,
 * all tests are REQUIRED to use the functions provided by this library
 * (libtestutil.a) for displaying their information.
 *
 * A test using this library begins with a call to startTests with the test
 * name as the arg (which is used only for formatting). Before the first
 * subtest, a call to subTest should be made with the subtest name as the arg
 * (again, for formatting). If the subTest is successful, then no action
 * is needed. However, if the subTest is not successful, then a call
 * to testError should be made with a descriptive error message as the arg.
 * Note that a subTest MUST NOT call testError more than once.
 * Finally, a call to endTests is made with the test name as the arg (for
 * formatting). Note that most of these macros assume that a variable named
 * "plContext" of type (void *) has been defined by the test. As such, it
 * is essential that the test satisfy this condition.
 */

/*
 * PKIX_TEST_STD_VARS should be called at the beginning of every function
 * that uses PKIX_TEST_RETURN (e.g. subTests), but it should be called only
 * AFTER declaring local variables (so we don't get compiler warnings about
 * declarations after statements). PKIX_TEST_STD_VARS declares and initializes
 * several variables needed by the other test macros.
 */
#define PKIX_TEST_STD_VARS()                \
    PKIX_Error *pkixTestErrorResult = NULL; \
    char *pkixTestErrorMsg = NULL;

/*
 * PKIX_TEST_EXPECT_NO_ERROR should be used to wrap a standard PKIX function
 * call (one which returns a pointer to PKIX_Error) that is expected to return
 * NULL (i.e. to succeed). If "pkixTestErrorResult" is not NULL,
 * "goto cleanup" is executed, where a testError call is made if there were
 * unexpected results. This macro MUST NOT be called after the "cleanup" label.
 *
 * Example Usage: PKIX_TEST_EXPECT_NO_ERROR(pkixFunc_expected_to_succeed(...));
 */

#define PKIX_TEST_EXPECT_NO_ERROR(func) \
    do {                                \
        pkixTestErrorResult = (func);   \
        if (pkixTestErrorResult) {      \
            goto cleanup;               \
        }                               \
    } while (0)

/*
 * PKIX_TEST_EXPECT_ERROR should be used to wrap a standard PKIX function call
 * (one which returns a pointer to PKIX_Error) that is expected to return
 * a non-NULL value (i.e. to fail). If "pkixTestErrorResult" is NULL,
 * "pkixTestErrorMsg" is set to a standard string and "goto cleanup"
 * is executed, where a testError call is made if there were unexpected
 * results. This macro MUST NOT be called after the "cleanup" label.
 *
 * Example Usage:  PKIX_TEST_EXPECT_ERROR(pkixFunc_expected_to_fail(...));
 */

#define PKIX_TEST_EXPECT_ERROR(func)                 \
    do {                                             \
        pkixTestErrorResult = (func);                \
        if (!pkixTestErrorResult) {                  \
            pkixTestErrorMsg =                       \
                "Should have thrown an error here."; \
            goto cleanup;                            \
        }                                            \
        PKIX_TEST_DECREF_BC(pkixTestErrorResult);    \
    } while (0)

/*
 * PKIX_TEST_DECREF_BC is a convenience macro which should only be called
 * BEFORE the "cleanup" label ("BC"). If the input parameter is non-NULL, it
 * DecRefs the input parameter and wraps the function with
 * PKIX_TEST_EXPECT_NO_ERROR, which executes "goto cleanup" upon error.
 * This macro MUST NOT be called after the "cleanup" label.
 */

#define PKIX_TEST_DECREF_BC(obj)                                                                  \
    do {                                                                                          \
        if (obj) {                                                                                \
            PKIX_TEST_EXPECT_NO_ERROR(PKIX_PL_Object_DecRef((PKIX_PL_Object *)(obj), plContext)); \
            obj = NULL;                                                                           \
        }                                                                                         \
    } while (0)

/*
 * PKIX_TEST_DECREF_AC is a convenience macro which should only be called
 * AFTER the "cleanup" label ("AC"). If the input parameter is non-NULL, it
 * DecRefs the input parameter. A pkixTestTempResult variable is used to prevent
 * incorrectly overwriting pkixTestErrorResult with NULL.
 * In the case DecRef succeeds, pkixTestTempResult will be NULL, and we won't
 * overwrite a previously set pkixTestErrorResult (if any). If DecRef fails,
 * then we do want to overwrite a previously set pkixTestErrorResult since a
 * DecRef failure is fatal and may be indicative of memory corruption.
 */

#define PKIX_TEST_DECREF_AC(obj)                                           \
    do {                                                                   \
        if (obj) {                                                         \
            PKIX_Error *pkixTestTempResult = NULL;                         \
            pkixTestTempResult =                                           \
                PKIX_PL_Object_DecRef((PKIX_PL_Object *)(obj), plContext); \
            if (pkixTestTempResult)                                        \
                pkixTestErrorResult = pkixTestTempResult;                  \
            obj = NULL;                                                    \
        }                                                                  \
    } while (0)

/*
 * PKIX_TEST_RETURN must always be AFTER the "cleanup" label. It does nothing
 * if everything went as expected. However, if there were unexpected results,
 * PKIX_TEST_RETURN calls testError, which displays a standard failure message
 * and increments the number of subtests that have failed. In the case
 * of an unexpected error, testError is called using the error's description
 * as an input and the error is DecRef'd. In the case of unexpected success
 * testError is called with a standard string.
 */
#define PKIX_TEST_RETURN()                                                   \
    {                                                                        \
        if (pkixTestErrorMsg) {                                              \
            testError(pkixTestErrorMsg);                                     \
        } else if (pkixTestErrorResult) {                                    \
            pkixTestErrorMsg =                                               \
                PKIX_Error2ASCII(pkixTestErrorResult, plContext);            \
            if (pkixTestErrorMsg) {                                          \
                testError(pkixTestErrorMsg);                                 \
                PKIX_PL_Free((PKIX_PL_Object *)pkixTestErrorMsg,             \
                             plContext);                                     \
            } else {                                                         \
                testError("PKIX_Error2ASCII Failed");                        \
            }                                                                \
            if (pkixTestErrorResult != PKIX_ALLOC_ERROR()) {                 \
                PKIX_PL_Object_DecRef((PKIX_PL_Object *)pkixTestErrorResult, \
                                      plContext);                            \
                pkixTestErrorResult = NULL;                                  \
            }                                                                \
        }                                                                    \
    }

/*
 * PKIX_TEST_EQ_HASH_TOSTR_DUP is a convenience macro which executes the
 * standard set of operations that test the Equals, Hashcode, ToString, and
 * Duplicate functions of an object type. The goodObj, equalObj, and diffObj
 * are as the names suggest. The expAscii parameter is the expected result of
 * calling ToString on the goodObj. If expAscii is NULL, then ToString will
 * not be called on the goodObj. The checkDuplicate parameter is treated as
 * a Boolean to indicate whether the Duplicate function should be tested. If
 * checkDuplicate is NULL, then Duplicate will not be called on the goodObj.
 * The type is the name of the function's family. For example, if the type is
 * Cert, this macro will call PKIX_PL_Cert_Equals, PKIX_PL_Cert_Hashcode, and
 * PKIX_PL_Cert_ToString.
 *
 * Note: If goodObj uses the default Equals and Hashcode functions, then
 * for goodObj and equalObj to be equal, they must have the same pointer value.
 */
#define PKIX_TEST_EQ_HASH_TOSTR_DUP(goodObj, equalObj, diffObj,        \
                                    expAscii, type, checkDuplicate)    \
    do {                                                               \
        subTest("PKIX_PL_" #type "_Equals   <match>");                 \
        testEqualsHelper((PKIX_PL_Object *)(goodObj),                  \
                         (PKIX_PL_Object *)(equalObj),                 \
                         PKIX_TRUE,                                    \
                         plContext);                                   \
        subTest("PKIX_PL_" #type "_Hashcode <match>");                 \
        testHashcodeHelper((PKIX_PL_Object *)(goodObj),                \
                           (PKIX_PL_Object *)(equalObj),               \
                           PKIX_TRUE,                                  \
                           plContext);                                 \
        subTest("PKIX_PL_" #type "_Equals   <non-match>");             \
        testEqualsHelper((PKIX_PL_Object *)(goodObj),                  \
                         (PKIX_PL_Object *)(diffObj),                  \
                         PKIX_FALSE,                                   \
                         plContext);                                   \
        subTest("PKIX_PL_" #type "_Hashcode <non-match>");             \
        testHashcodeHelper((PKIX_PL_Object *)(goodObj),                \
                           (PKIX_PL_Object *)(diffObj),                \
                           PKIX_FALSE,                                 \
                           plContext);                                 \
        if (expAscii) {                                                \
            subTest("PKIX_PL_" #type "_ToString");                     \
            testToStringHelper((PKIX_PL_Object *)(goodObj),            \
                               (expAscii),                             \
                               plContext);                             \
        }                                                              \
        if (checkDuplicate) {                                          \
            subTest("PKIX_PL_" #type "_Duplicate");                    \
            testDuplicateHelper((PKIX_PL_Object *)goodObj, plContext); \
        }                                                              \
    } while (0)

/*
 * PKIX_TEST_DECREF_BC is a convenience macro which should only be called
 * BEFORE the "cleanup" label ("BC"). If the input parameter is non-NULL, it
 * DecRefs the input parameter and wraps the function with
 * PKIX_TEST_EXPECT_NO_ERROR, which executes "goto cleanup" upon error.
 * This macro MUST NOT be called after the "cleanup" label.
 */

#define PKIX_TEST_ABORT_ON_NULL(obj) \
    do {                             \
        if (!obj) {                  \
            goto cleanup;            \
        }                            \
    } while (0)

#define PKIX_TEST_ARENAS_ARG(arena) \
    (arena ? (PORT_Strcmp(arena, "arenas") ? PKIX_FALSE : (j++, PKIX_TRUE)) : PKIX_FALSE)

#define PKIX_TEST_ERROR_RECEIVED (pkixTestErrorMsg || pkixTestErrorResult)

/* see source file for function documentation */

void startTests(char *testName);

void endTests(char *testName);

void subTest(char *subTestName);

void testError(char *msg);

extern PKIX_Error *
_ErrorCheck(PKIX_Error *errorResult);

extern PKIX_Error *
_OutputError(PKIX_Error *errorResult);

char *PKIX_String2ASCII(PKIX_PL_String *string, void *plContext);

char *PKIX_Error2ASCII(PKIX_Error *error, void *plContext);

char *PKIX_Object2ASCII(PKIX_PL_Object *object);

char *PKIX_Cert2ASCII(PKIX_PL_Cert *cert);

void
testHashcodeHelper(
    PKIX_PL_Object *goodObject,
    PKIX_PL_Object *otherObject,
    PKIX_Boolean match,
    void *plContext);

void
testToStringHelper(
    PKIX_PL_Object *goodObject,
    char *expected,
    void *plContext);

void
testEqualsHelper(
    PKIX_PL_Object *goodObject,
    PKIX_PL_Object *otherObject,
    PKIX_Boolean match,
    void *plContext);

void
testDuplicateHelper(
    PKIX_PL_Object *object,
    void *plContext);
void
testErrorUndo(char *msg);

#ifdef __cplusplus
}
#endif

#endif /* TESTUTIL_H */