/* 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 */
