/*
 * Copyright 2015-2018, 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_realloc.c -- unit test for pmemobj_tx_realloc and pmemobj_tx_zrealloc
 */
#include <sys/param.h>
#include <string.h>

#include "unittest.h"
#include "util.h"

#define LAYOUT_NAME "tx_realloc"

#define TEST_VALUE_1	1
#define OBJ_SIZE	1024

enum type_number {
	TYPE_NO_TX,
	TYPE_COMMIT,
	TYPE_ABORT,
	TYPE_TYPE,
	TYPE_COMMIT_ZERO,
	TYPE_COMMIT_ZERO_MACRO,
	TYPE_ABORT_ZERO,
	TYPE_ABORT_ZERO_MACRO,
	TYPE_COMMIT_ALLOC,
	TYPE_ABORT_ALLOC,
	TYPE_ABORT_HUGE,
	TYPE_ABORT_ZERO_HUGE,
	TYPE_ABORT_ZERO_HUGE_MACRO,
	TYPE_FREE,
};

struct object {
	size_t value;
	char data[OBJ_SIZE - sizeof(size_t)];
};

TOID_DECLARE(struct object, 0);

struct object_macro {
	size_t value;
	char data[OBJ_SIZE - sizeof(size_t)];
};

TOID_DECLARE(struct object_macro, TYPE_COMMIT_ZERO_MACRO);

/*
 * do_tx_alloc -- do tx allocation with specified type number
 */
static PMEMoid
do_tx_alloc(PMEMobjpool *pop, unsigned type_num, size_t value)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, OID_NULL);

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, pmemobj_tx_alloc(
				sizeof(struct object), type_num));
		if (!TOID_IS_NULL(obj)) {
			D_RW(obj)->value = value;
		}
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	return obj.oid;
}

/*
 * do_tx_realloc_commit -- reallocate an object and commit the transaction
 */
static void
do_tx_realloc_commit(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_COMMIT, TEST_VALUE_1));
	size_t new_size = 2 * pmemobj_alloc_usable_size(obj.oid);

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, pmemobj_tx_realloc(obj.oid,
			new_size, TYPE_COMMIT));
		UT_ASSERT(!TOID_IS_NULL(obj));
		UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_COMMIT));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);

	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_realloc_abort -- reallocate an object and commit the transaction
 */
static void
do_tx_realloc_abort(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_ABORT, TEST_VALUE_1));
	size_t new_size = 2 * pmemobj_alloc_usable_size(obj.oid);

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, pmemobj_tx_realloc(obj.oid,
			new_size, TYPE_ABORT));
		UT_ASSERT(!TOID_IS_NULL(obj));
		UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);

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

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) < new_size);

	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_realloc_huge -- reallocate an object to a huge size to trigger tx abort
 */
static void
do_tx_realloc_huge(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_ABORT_HUGE, TEST_VALUE_1));
	size_t new_size = PMEMOBJ_MAX_ALLOC_SIZE + 1;

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, pmemobj_tx_realloc(obj.oid,
			new_size, TYPE_ABORT_HUGE));
		UT_ASSERT(0); /* should not get to this point */
	} TX_ONCOMMIT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_HUGE));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) < new_size);

	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_zrealloc_commit_macro -- reallocate an object, zero it and commit
 * the transaction using macro
 */
static void
do_tx_zrealloc_commit_macro(PMEMobjpool *pop)
{
	TOID(struct object_macro) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_COMMIT_ZERO_MACRO,
								TEST_VALUE_1));
	size_t old_size = pmemobj_alloc_usable_size(obj.oid);
	size_t new_size = 2 * old_size;

	TX_BEGIN(pop) {
		obj = TX_ZREALLOC(obj, new_size);
		UT_ASSERT(!TOID_IS_NULL(obj));
		UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);
		void *new_ptr = (void *)((uintptr_t)D_RW(obj) + old_size);
		UT_ASSERT(util_is_zeroed(new_ptr, new_size - old_size));
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_COMMIT_ZERO_MACRO));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);
	void *new_ptr = (void *)((uintptr_t)D_RW(obj) + old_size);
	UT_ASSERT(util_is_zeroed(new_ptr, new_size - old_size));

	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_zrealloc_commit -- reallocate an object, zero it and commit
 * the transaction
 */
static void
do_tx_zrealloc_commit(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_COMMIT_ZERO, TEST_VALUE_1));
	size_t old_size = pmemobj_alloc_usable_size(obj.oid);
	size_t new_size = 2 * old_size;

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, pmemobj_tx_zrealloc(obj.oid,
			new_size, TYPE_COMMIT_ZERO));
		UT_ASSERT(!TOID_IS_NULL(obj));
		UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);
		void *new_ptr = (void *)((uintptr_t)D_RW(obj) + old_size);
		UT_ASSERT(util_is_zeroed(new_ptr, new_size - old_size));
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_COMMIT_ZERO));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);
	void *new_ptr = (void *)((uintptr_t)D_RW(obj) + old_size);
	UT_ASSERT(util_is_zeroed(new_ptr, new_size - old_size));


	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_realloc_abort_macro -- reallocate an object, zero it and commit the
 * transaction using macro
 */
static void
do_tx_zrealloc_abort_macro(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_ABORT_ZERO_MACRO, TEST_VALUE_1));
	size_t old_size = pmemobj_alloc_usable_size(obj.oid);
	size_t new_size = 2 * old_size;

	TX_BEGIN(pop) {
		obj = TX_ZREALLOC(obj, new_size);
		UT_ASSERT(!TOID_IS_NULL(obj));
		UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);
		void *new_ptr = (void *)((uintptr_t)D_RW(obj) + old_size);
		UT_ASSERT(util_is_zeroed(new_ptr, new_size - old_size));

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

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_ZERO_MACRO));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) < new_size);
	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_realloc_abort -- reallocate an object and commit the transaction
 */
static void
do_tx_zrealloc_abort(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_ABORT_ZERO, TEST_VALUE_1));
	size_t old_size = pmemobj_alloc_usable_size(obj.oid);
	size_t new_size = 2 * old_size;

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, pmemobj_tx_zrealloc(obj.oid,
			new_size, TYPE_ABORT_ZERO));
		UT_ASSERT(!TOID_IS_NULL(obj));
		UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);
		void *new_ptr = (void *)((uintptr_t)D_RW(obj) + old_size);
		UT_ASSERT(util_is_zeroed(new_ptr, new_size - old_size));

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

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_ZERO));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) < new_size);
	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_realloc_huge_macro -- reallocate an object to a huge size to trigger
 * tx abort and zero it using macro
 */
static void
do_tx_zrealloc_huge_macro(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_ABORT_ZERO_HUGE_MACRO,
								TEST_VALUE_1));
	size_t old_size = pmemobj_alloc_usable_size(obj.oid);
	size_t new_size = 2 * old_size;

	TX_BEGIN(pop) {
		obj = TX_ZREALLOC(obj, PMEMOBJ_MAX_ALLOC_SIZE + 1);
		UT_ASSERT(0); /* should not get to this point */
	} TX_ONCOMMIT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_ZERO_HUGE_MACRO));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) < new_size);

	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_realloc_huge -- reallocate an object to a huge size to trigger tx abort
 */
static void
do_tx_zrealloc_huge(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_ABORT_ZERO_HUGE, TEST_VALUE_1));
	size_t old_size = pmemobj_alloc_usable_size(obj.oid);
	size_t new_size = 2 * old_size;

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, pmemobj_tx_zrealloc(obj.oid,
			PMEMOBJ_MAX_ALLOC_SIZE + 1, TYPE_ABORT_ZERO_HUGE));
		UT_ASSERT(0); /* should not get to this point */
	} TX_ONCOMMIT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_ZERO_HUGE));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) < new_size);

	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_realloc_alloc_commit -- reallocate an allocated object
 * and commit the transaction
 */
static void
do_tx_realloc_alloc_commit(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	size_t new_size = 0;

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_COMMIT_ALLOC,
					TEST_VALUE_1));
		UT_ASSERT(!TOID_IS_NULL(obj));
		new_size = 2 * pmemobj_alloc_usable_size(obj.oid);
		TOID_ASSIGN(obj, pmemobj_tx_realloc(obj.oid,
			new_size, TYPE_COMMIT_ALLOC));
		UT_ASSERT(!TOID_IS_NULL(obj));
		UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_COMMIT_ALLOC));
	UT_ASSERT(!TOID_IS_NULL(obj));
	UT_ASSERTeq(D_RO(obj)->value, TEST_VALUE_1);
	UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);

	TOID_ASSIGN(obj, POBJ_NEXT_TYPE_NUM(obj.oid));
	UT_ASSERT(TOID_IS_NULL(obj));
}

/*
 * do_tx_realloc_alloc_abort -- reallocate an allocated object
 * and commit the transaction
 */
static void
do_tx_realloc_alloc_abort(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	size_t new_size = 0;

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_ABORT_ALLOC,
					TEST_VALUE_1));
		UT_ASSERT(!TOID_IS_NULL(obj));
		new_size = 2 * pmemobj_alloc_usable_size(obj.oid);
		TOID_ASSIGN(obj, pmemobj_tx_realloc(obj.oid,
			new_size, TYPE_ABORT_ALLOC));
		UT_ASSERT(!TOID_IS_NULL(obj));
		UT_ASSERT(pmemobj_alloc_usable_size(obj.oid) >= new_size);

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

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_ABORT_ALLOC));
	UT_ASSERT(TOID_IS_NULL(obj));
}


/*
 * do_tx_root_realloc -- retrieve root inside of transaction
 */
static void
do_tx_root_realloc(PMEMobjpool *pop)
{
	TX_BEGIN(pop) {
		PMEMoid root = pmemobj_root(pop, sizeof(struct object));
		UT_ASSERT(!OID_IS_NULL(root));
		UT_ASSERT(util_is_zeroed(pmemobj_direct(root),
				sizeof(struct object)));
		UT_ASSERTeq(sizeof(struct object), pmemobj_root_size(pop));

		root = pmemobj_root(pop, 2 * sizeof(struct object));
		UT_ASSERT(!OID_IS_NULL(root));
		UT_ASSERT(util_is_zeroed(pmemobj_direct(root),
				2 * sizeof(struct object)));
		UT_ASSERTeq(2 * sizeof(struct object), pmemobj_root_size(pop));
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END
}

/*
 * do_tx_realloc_free -- reallocate an allocated object
 * and commit the transaction
 */
static void
do_tx_realloc_free(PMEMobjpool *pop)
{
	TOID(struct object) obj;
	TOID_ASSIGN(obj, do_tx_alloc(pop, TYPE_FREE, TEST_VALUE_1));

	TX_BEGIN(pop) {
		TOID_ASSIGN(obj, pmemobj_tx_realloc(obj.oid,
			0, TYPE_COMMIT));
	} TX_ONABORT {
		UT_ASSERT(0);
	} TX_END

	TOID_ASSIGN(obj, POBJ_FIRST_TYPE_NUM(pop, TYPE_FREE));
	UT_ASSERT(TOID_IS_NULL(obj));
}

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

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

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

	do_tx_root_realloc(pop);
	do_tx_realloc_commit(pop);
	do_tx_realloc_abort(pop);
	do_tx_realloc_huge(pop);
	do_tx_zrealloc_commit(pop);
	do_tx_zrealloc_commit_macro(pop);
	do_tx_zrealloc_abort(pop);
	do_tx_zrealloc_abort_macro(pop);
	do_tx_zrealloc_huge(pop);
	do_tx_zrealloc_huge_macro(pop);
	do_tx_realloc_alloc_commit(pop);
	do_tx_realloc_alloc_abort(pop);
	do_tx_realloc_free(pop);

	pmemobj_close(pop);

	DONE(NULL);
}
