/*
 * Copyright 2015-2017, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *
 *     * Neither the name of the copyright holder nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * obj_tx_strdup.c -- unit test for pmemobj_tx_strdup
 */
#include <sys/param.h>
#include <string.h>
#include <wchar.h>

#include "unittest.h"

#define LAYOUT_NAME "tx_strdup"

TOID_DECLARE(char, 0);
TOID_DECLARE(wchar_t, 1);

enum type_number {
	TYPE_NO_TX,
	TYPE_WCS_NO_TX,
	TYPE_COMMIT,
	TYPE_WCS_COMMIT,
	TYPE_ABORT,
	TYPE_WCS_ABORT,
	TYPE_FREE_COMMIT,
	TYPE_WCS_FREE_COMMIT,
	TYPE_FREE_ABORT,
	TYPE_WCS_FREE_ABORT,
	TYPE_COMMIT_NESTED1,
	TYPE_WCS_COMMIT_NESTED1,
	TYPE_COMMIT_NESTED2,
	TYPE_WCS_COMMIT_NESTED2,
	TYPE_ABORT_NESTED1,
	TYPE_WCS_ABORT_NESTED1,
	TYPE_ABORT_NESTED2,
	TYPE_WCS_ABORT_NESTED2,
	TYPE_ABORT_AFTER_NESTED1,
	TYPE_WCS_ABORT_AFTER_NESTED1,
	TYPE_ABORT_AFTER_NESTED2,
	TYPE_WCS_ABORT_AFTER_NESTED2,
};

#define TEST_STR_1	"Test string 1"
#define TEST_STR_2	"Test string 2"
#define TEST_WCS_1	L"Test string 3"
#define TEST_WCS_2	L"Test string 4"
#define MAX_FUNC	2

typedef void (*fn_tx_strdup)(TOID(char) *str, const char *s,
						unsigned type_num);
typedef void (*fn_tx_wcsdup)(TOID(wchar_t) *wcs, const wchar_t *s,
						unsigned type_num);

static unsigned counter;

/*
 * tx_strdup -- duplicate a string using pmemobj_tx_strdup
 */
static void
tx_strdup(TOID(char) *str, const char *s, unsigned type_num)
{
	TOID_ASSIGN(*str, pmemobj_tx_strdup(s, type_num));
}

/*
 * tx_wcsdup -- duplicate a string using pmemobj_tx_wcsdup
 */
static void
tx_wcsdup(TOID(wchar_t) *wcs, const wchar_t *s, unsigned type_num)
{
	TOID_ASSIGN(*wcs, pmemobj_tx_wcsdup(s, type_num));
}

/*
 * tx_strdup_macro -- duplicate a string using macro
 */
static void
tx_strdup_macro(TOID(char) *str, const char *s, unsigned type_num)
{
	TOID_ASSIGN(*str, TX_STRDUP(s, type_num));
}

/*
 * tx_wcsdup_macro -- duplicate a wide character string using macro
 */
static void
tx_wcsdup_macro(TOID(wchar_t) *wcs, const wchar_t *s, unsigned type_num)
{
	TOID_ASSIGN(*wcs, TX_WCSDUP(s, type_num));
}

static fn_tx_strdup do_tx_strdup[MAX_FUNC] = {tx_strdup, tx_strdup_macro};
static fn_tx_wcsdup do_tx_wcsdup[MAX_FUNC] = {tx_wcsdup, tx_wcsdup_macro};

/*
 * do_tx_strdup_commit -- duplicate a string and commit the transaction
 */
static void
do_tx_strdup_commit(PMEMobjpool *pop)
{
	TOID(char) str;
	TOID(wchar_t) wcs;
	TX_BEGIN(pop) {
		do_tx_strdup[counter](&str, TEST_STR_1, TYPE_COMMIT);
		do_tx_wcsdup[counter](&wcs, TEST_WCS_1, TYPE_WCS_COMMIT);
		UT_ASSERT(!TOID_IS_NULL(str));
		UT_ASSERT(!TOID_IS_NULL(wcs));
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(str, POBJ_FIRST_TYPE_NUM(pop, TYPE_COMMIT));
	TOID_ASSIGN(wcs, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_COMMIT));
	UT_ASSERT(!TOID_IS_NULL(str));
	UT_ASSERTeq(strcmp(TEST_STR_1, D_RO(str)), 0);
	UT_ASSERTeq(wcscmp(TEST_WCS_1, D_RO(wcs)), 0);
}

/*
 * do_tx_strdup_abort -- duplicate a string and abort the transaction
 */
static void
do_tx_strdup_abort(PMEMobjpool *pop)
{
	TOID(char) str;
	TOID(wchar_t) wcs;
	TX_BEGIN(pop) {
		do_tx_strdup[counter](&str, TEST_STR_1, TYPE_ABORT);
		do_tx_wcsdup[counter](&wcs, TEST_WCS_1, TYPE_WCS_ABORT);
		UT_ASSERT(!TOID_IS_NULL(str));
		UT_ASSERT(!TOID_IS_NULL(wcs));
		pmemobj_tx_abort(-1);
	} TX_ONCOMMIT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(str, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT));
	TOID_ASSIGN(wcs, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_ABORT));
	UT_ASSERT(TOID_IS_NULL(str));
	UT_ASSERT(TOID_IS_NULL(wcs));
}

/*
 * do_tx_strdup_null -- duplicate a NULL string to trigger tx abort
 */
static void
do_tx_strdup_null(PMEMobjpool *pop)
{
	TOID(char) str;
	TOID(wchar_t) wcs;
	TX_BEGIN(pop) {
		do_tx_strdup[counter](&str, NULL, TYPE_ABORT);
		do_tx_wcsdup[counter](&wcs, NULL, TYPE_WCS_ABORT);
		UT_ASSERT(0); /* should not get to this point */
	} TX_ONCOMMIT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(str, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT));
	TOID_ASSIGN(wcs, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_ABORT));
	UT_ASSERT(TOID_IS_NULL(str));
	UT_ASSERT(TOID_IS_NULL(wcs));
}

/*
 * do_tx_strdup_free_commit -- duplicate a string, free and commit the
 * transaction
 */
static void
do_tx_strdup_free_commit(PMEMobjpool *pop)
{
	TOID(char) str;
	TOID(wchar_t) wcs;
	TX_BEGIN(pop) {
		do_tx_strdup[counter](&str, TEST_STR_1, TYPE_FREE_COMMIT);
		do_tx_wcsdup[counter](&wcs, TEST_WCS_1, TYPE_WCS_FREE_COMMIT);
		UT_ASSERT(!TOID_IS_NULL(str));
		UT_ASSERT(!TOID_IS_NULL(wcs));
		int ret = pmemobj_tx_free(str.oid);
		UT_ASSERTeq(ret, 0);
		ret = pmemobj_tx_free(wcs.oid);
		UT_ASSERTeq(ret, 0);
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(str, POBJ_FIRST_TYPE_NUM(pop, TYPE_FREE_COMMIT));
	TOID_ASSIGN(wcs, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_FREE_COMMIT));
	UT_ASSERT(TOID_IS_NULL(str));
	UT_ASSERT(TOID_IS_NULL(wcs));
}

/*
 * do_tx_strdup_free_abort -- duplicate a string, free and abort the
 * transaction
 */
static void
do_tx_strdup_free_abort(PMEMobjpool *pop)
{
	TOID(char) str;
	TOID(wchar_t) wcs;
	TX_BEGIN(pop) {
		do_tx_strdup[counter](&str, TEST_STR_1, TYPE_FREE_ABORT);
		do_tx_wcsdup[counter](&wcs, TEST_WCS_1, TYPE_WCS_FREE_ABORT);
		UT_ASSERT(!TOID_IS_NULL(str));
		UT_ASSERT(!TOID_IS_NULL(wcs));
		int ret = pmemobj_tx_free(str.oid);
		UT_ASSERTeq(ret, 0);
		ret = pmemobj_tx_free(wcs.oid);
		UT_ASSERTeq(ret, 0);
		pmemobj_tx_abort(-1);
	} TX_ONCOMMIT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(str, POBJ_FIRST_TYPE_NUM(pop, TYPE_FREE_ABORT));
	TOID_ASSIGN(wcs, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_FREE_ABORT));
	UT_ASSERT(TOID_IS_NULL(str));
	UT_ASSERT(TOID_IS_NULL(wcs));
}

/*
 * do_tx_strdup_commit_nested -- duplicate two string  suing nested
 * transaction and commit the transaction
 */
static void
do_tx_strdup_commit_nested(PMEMobjpool *pop)
{
	TOID(char) str1;
	TOID(char) str2;
	TOID(wchar_t) wcs1;
	TOID(wchar_t) wcs2;

	TX_BEGIN(pop) {
		do_tx_strdup[counter](&str1, TEST_STR_1, TYPE_COMMIT_NESTED1);
		do_tx_wcsdup[counter](&wcs1, TEST_WCS_1,
				TYPE_WCS_COMMIT_NESTED1);
		UT_ASSERT(!TOID_IS_NULL(str1));
		UT_ASSERT(!TOID_IS_NULL(wcs1));
		TX_BEGIN(pop) {
			do_tx_strdup[counter](&str2, TEST_STR_2,
						TYPE_COMMIT_NESTED2);
			do_tx_wcsdup[counter](&wcs2, TEST_WCS_2,
						TYPE_WCS_COMMIT_NESTED2);
			UT_ASSERT(!TOID_IS_NULL(str2));
			UT_ASSERT(!TOID_IS_NULL(wcs2));
		} TX_ONABORT {
			UT_ASSERT(0);
		} TX_END
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(str1, POBJ_FIRST_TYPE_NUM(pop, TYPE_COMMIT_NESTED1));
	TOID_ASSIGN(wcs1, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_COMMIT_NESTED1));
	UT_ASSERT(!TOID_IS_NULL(str1));
	UT_ASSERT(!TOID_IS_NULL(wcs1));
	UT_ASSERTeq(strcmp(TEST_STR_1, D_RO(str1)), 0);
	UT_ASSERTeq(wcscmp(TEST_WCS_1, D_RO(wcs1)), 0);

	TOID_ASSIGN(str2, POBJ_FIRST_TYPE_NUM(pop, TYPE_COMMIT_NESTED2));
	TOID_ASSIGN(wcs2, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_COMMIT_NESTED2));
	UT_ASSERT(!TOID_IS_NULL(str2));
	UT_ASSERT(!TOID_IS_NULL(wcs2));
	UT_ASSERTeq(strcmp(TEST_STR_2, D_RO(str2)), 0);
	UT_ASSERTeq(wcscmp(TEST_WCS_2, D_RO(wcs2)), 0);
}

/*
 * do_tx_strdup_commit_abort -- duplicate two string  suing nested
 * transaction and abort the transaction
 */
static void
do_tx_strdup_abort_nested(PMEMobjpool *pop)
{
	TOID(char) str1;
	TOID(char) str2;
	TOID(wchar_t) wcs1;
	TOID(wchar_t) wcs2;

	TX_BEGIN(pop) {
		do_tx_strdup[counter](&str1, TEST_STR_1, TYPE_ABORT_NESTED1);
		do_tx_wcsdup[counter](&wcs1, TEST_WCS_1,
				TYPE_WCS_ABORT_NESTED1);
		UT_ASSERT(!TOID_IS_NULL(str1));
		UT_ASSERT(!TOID_IS_NULL(wcs1));
		TX_BEGIN(pop) {
			do_tx_strdup[counter](&str2, TEST_STR_2,
						TYPE_ABORT_NESTED2);
			do_tx_wcsdup[counter](&wcs2, TEST_WCS_2,
						TYPE_WCS_ABORT_NESTED2);
			UT_ASSERT(!TOID_IS_NULL(str2));
			UT_ASSERT(!TOID_IS_NULL(wcs2));
			pmemobj_tx_abort(-1);
		} TX_ONCOMMIT {
			UT_ASSERT(0);
		} TX_END
	} TX_ONCOMMIT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(str1, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_NESTED1));
	TOID_ASSIGN(wcs1, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_ABORT_NESTED1));
	UT_ASSERT(TOID_IS_NULL(str1));
	UT_ASSERT(TOID_IS_NULL(wcs1));

	TOID_ASSIGN(str2, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_NESTED2));
	TOID_ASSIGN(wcs2, POBJ_FIRST_TYPE_NUM(pop, TYPE_WCS_ABORT_NESTED2));
	UT_ASSERT(TOID_IS_NULL(str2));
	UT_ASSERT(TOID_IS_NULL(wcs2));
}

/*
 * do_tx_strdup_commit_abort -- duplicate two string  suing nested
 * transaction and abort after the nested transaction
 */
static void
do_tx_strdup_abort_after_nested(PMEMobjpool *pop)
{
	TOID(char) str1;
	TOID(char) str2;
	TOID(wchar_t) wcs1;
	TOID(wchar_t) wcs2;

	TX_BEGIN(pop) {
		do_tx_strdup[counter](&str1, TEST_STR_1,
						TYPE_ABORT_AFTER_NESTED1);
		do_tx_wcsdup[counter](&wcs1, TEST_WCS_1,
						TYPE_WCS_ABORT_AFTER_NESTED1);
		UT_ASSERT(!TOID_IS_NULL(str1));
		UT_ASSERT(!TOID_IS_NULL(wcs1));
		TX_BEGIN(pop) {
			do_tx_strdup[counter](&str2, TEST_STR_2,
						TYPE_ABORT_AFTER_NESTED2);
			do_tx_wcsdup[counter](&wcs2, TEST_WCS_2,
						TYPE_WCS_ABORT_AFTER_NESTED2);
			UT_ASSERT(!TOID_IS_NULL(str2));
			UT_ASSERT(!TOID_IS_NULL(wcs2));
		} TX_ONABORT {
			UT_ASSERT(0);
		} TX_END

		pmemobj_tx_abort(-1);
	} TX_ONCOMMIT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(str1, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_AFTER_NESTED1));
	TOID_ASSIGN(wcs1, POBJ_FIRST_TYPE_NUM(pop,
			TYPE_WCS_ABORT_AFTER_NESTED1));
	UT_ASSERT(TOID_IS_NULL(str1));
	UT_ASSERT(TOID_IS_NULL(wcs1));

	TOID_ASSIGN(str2, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_AFTER_NESTED2));
	TOID_ASSIGN(wcs2, POBJ_FIRST_TYPE_NUM(pop,
			TYPE_WCS_ABORT_AFTER_NESTED2));
	UT_ASSERT(TOID_IS_NULL(str2));
	UT_ASSERT(TOID_IS_NULL(wcs2));
}

int
main(int argc, char *argv[])
{
	START(argc, argv, "obj_tx_strdup");

	if (argc != 2)
		UT_FATAL("usage: %s [file]", argv[0]);

	PMEMobjpool *pop;
	if ((pop = pmemobj_create(argv[1], LAYOUT_NAME, PMEMOBJ_MIN_POOL,
	    S_IWUSR | S_IRUSR)) == NULL)
		UT_FATAL("!pmemobj_create");

	for (counter = 0; counter < MAX_FUNC; counter++) {
		do_tx_strdup_commit(pop);
		do_tx_strdup_abort(pop);
		do_tx_strdup_null(pop);
		do_tx_strdup_free_commit(pop);
		do_tx_strdup_free_abort(pop);
		do_tx_strdup_commit_nested(pop);
		do_tx_strdup_abort_nested(pop);
		do_tx_strdup_abort_after_nested(pop);
	}
	pmemobj_close(pop);

	DONE(NULL);
}
