/*-
 *   BSD LICENSE
 *
 *   Copyright (c) Intel Corporation.
 *   All rights reserved.
 *
 *   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 Intel Corporation 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.
 */

#include "spdk/stdinc.h"

#include "CUnit/Basic.h"

#include "common/lib/ut_multithread.c"

#include "spdk_cunit.h"
#include "blobfs/blobfs.c"
#include "blobfs/tree.c"
#include "blob/blobstore.h"

#include "spdk_internal/thread.h"

#include "unit/lib/blob/bs_dev_common.c"

struct spdk_filesystem *g_fs;
struct spdk_file *g_file;
int g_fserrno;
struct spdk_trace_histories *g_trace_histories;
DEFINE_STUB_V(spdk_trace_add_register_fn, (struct spdk_trace_register_fn *reg_fn));
DEFINE_STUB_V(spdk_trace_register_description, (const char *name,
		uint16_t tpoint_id, uint8_t owner_type,
		uint8_t object_type, uint8_t new_object,
		uint8_t arg1_is_ptr, const char *arg1_name));
DEFINE_STUB_V(_spdk_trace_record, (uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id,
				   uint32_t size, uint64_t object_id, uint64_t arg1));

/* Return NULL to test hardcoded defaults. */
struct spdk_conf_section *
spdk_conf_find_section(struct spdk_conf *cp, const char *name)
{
	return NULL;
}

/* Return -1 to test hardcoded defaults. */
int
spdk_conf_section_get_intval(struct spdk_conf_section *sp, const char *key)
{
	return -1;
}

static void
fs_op_complete(void *ctx, int fserrno)
{
	g_fserrno = fserrno;
}

static void
fs_op_with_handle_complete(void *ctx, struct spdk_filesystem *fs, int fserrno)
{
	g_fs = fs;
	g_fserrno = fserrno;
}

static void
fs_init(void)
{
	struct spdk_filesystem *fs;
	struct spdk_bs_dev *dev;

	dev = init_dev();

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
}

static void
create_cb(void *ctx, int fserrno)
{
	g_fserrno = fserrno;
}

static void
open_cb(void *ctx, struct spdk_file *f, int fserrno)
{
	g_fserrno = fserrno;
	g_file = f;
}

static void
delete_cb(void *ctx, int fserrno)
{
	g_fserrno = fserrno;
}

static void
fs_open(void)
{
	struct spdk_filesystem *fs;
	spdk_fs_iter iter;
	struct spdk_bs_dev *dev;
	struct spdk_file *file;
	char name[257] = {'\0'};

	dev = init_dev();
	memset(name, 'a', sizeof(name) - 1);

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	g_fserrno = 0;
	/* Open should fail, because the file name is too long. */
	spdk_fs_open_file_async(fs, name, SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == -ENAMETOOLONG);

	g_fserrno = 0;
	spdk_fs_open_file_async(fs, "file1", 0, open_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == -ENOENT);

	g_file = NULL;
	g_fserrno = 1;
	spdk_fs_open_file_async(fs, "file1", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	SPDK_CU_ASSERT_FATAL(g_file != NULL);
	CU_ASSERT(!strcmp("file1", g_file->name));
	CU_ASSERT(g_file->ref_count == 1);

	iter = spdk_fs_iter_first(fs);
	CU_ASSERT(iter != NULL);
	file = spdk_fs_iter_get_file(iter);
	SPDK_CU_ASSERT_FATAL(file != NULL);
	CU_ASSERT(!strcmp("file1", file->name));
	iter = spdk_fs_iter_next(iter);
	CU_ASSERT(iter == NULL);

	g_fserrno = 0;
	/* Delete should successful, we will mark the file as deleted. */
	spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(!TAILQ_EMPTY(&fs->files));

	g_fserrno = 1;
	spdk_file_close_async(g_file, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(TAILQ_EMPTY(&fs->files));

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
}

static void
fs_create(void)
{
	struct spdk_filesystem *fs;
	struct spdk_bs_dev *dev;
	char name[257] = {'\0'};

	dev = init_dev();
	memset(name, 'a', sizeof(name) - 1);

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	g_fserrno = 0;
	/* Create should fail, because the file name is too long. */
	spdk_fs_create_file_async(fs, name, create_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == -ENAMETOOLONG);

	g_fserrno = 1;
	spdk_fs_create_file_async(fs, "file1", create_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);

	g_fserrno = 1;
	spdk_fs_create_file_async(fs, "file1", create_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == -EEXIST);

	g_fserrno = 1;
	spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(TAILQ_EMPTY(&fs->files));

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
}

static void
fs_truncate(void)
{
	struct spdk_filesystem *fs;
	struct spdk_bs_dev *dev;

	dev = init_dev();

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	g_file = NULL;
	g_fserrno = 1;
	spdk_fs_open_file_async(fs, "file1", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	SPDK_CU_ASSERT_FATAL(g_file != NULL);

	g_fserrno = 1;
	spdk_file_truncate_async(g_file, 18 * 1024 * 1024 + 1, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(g_file->length == 18 * 1024 * 1024 + 1);

	g_fserrno = 1;
	spdk_file_truncate_async(g_file, 1, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(g_file->length == 1);

	g_fserrno = 1;
	spdk_file_truncate_async(g_file, 18 * 1024 * 1024 + 1, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(g_file->length == 18 * 1024 * 1024 + 1);

	g_fserrno = 1;
	spdk_file_close_async(g_file, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(g_file->ref_count == 0);

	g_fserrno = 1;
	spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(TAILQ_EMPTY(&fs->files));

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
}

static void
fs_rename(void)
{
	struct spdk_filesystem *fs;
	struct spdk_file *file, *file2, *file_iter;
	struct spdk_bs_dev *dev;

	dev = init_dev();

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	g_fserrno = 1;
	spdk_fs_create_file_async(fs, "file1", create_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);

	g_file = NULL;
	g_fserrno = 1;
	spdk_fs_open_file_async(fs, "file1", 0, open_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	SPDK_CU_ASSERT_FATAL(g_file != NULL);
	CU_ASSERT(g_file->ref_count == 1);

	file = g_file;
	g_file = NULL;
	g_fserrno = 1;
	spdk_file_close_async(file, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	SPDK_CU_ASSERT_FATAL(file->ref_count == 0);

	g_file = NULL;
	g_fserrno = 1;
	spdk_fs_open_file_async(fs, "file2", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	SPDK_CU_ASSERT_FATAL(g_file != NULL);
	CU_ASSERT(g_file->ref_count == 1);

	file2 = g_file;
	g_file = NULL;
	g_fserrno = 1;
	spdk_file_close_async(file2, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	SPDK_CU_ASSERT_FATAL(file2->ref_count == 0);

	/*
	 * Do a 3-way rename.  This should delete the old "file2", then rename
	 *  "file1" to "file2".
	 */
	g_fserrno = 1;
	spdk_fs_rename_file_async(fs, "file1", "file2", fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(file->ref_count == 0);
	CU_ASSERT(!strcmp(file->name, "file2"));
	CU_ASSERT(TAILQ_FIRST(&fs->files) == file);
	CU_ASSERT(TAILQ_NEXT(file, tailq) == NULL);

	g_fserrno = 0;
	spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == -ENOENT);
	CU_ASSERT(!TAILQ_EMPTY(&fs->files));
	TAILQ_FOREACH(file_iter, &fs->files, tailq) {
		if (file_iter == NULL) {
			SPDK_CU_ASSERT_FATAL(false);
		}
	}

	g_fserrno = 1;
	spdk_fs_delete_file_async(fs, "file2", delete_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(TAILQ_EMPTY(&fs->files));

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
}

static void
fs_rw_async(void)
{
	struct spdk_filesystem *fs;
	struct spdk_bs_dev *dev;
	uint8_t w_buf[4096];
	uint8_t r_buf[4096];

	dev = init_dev();

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	g_file = NULL;
	g_fserrno = 1;
	spdk_fs_open_file_async(fs, "file1", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	SPDK_CU_ASSERT_FATAL(g_file != NULL);

	/* Write file */
	CU_ASSERT(g_file->length == 0);
	g_fserrno = 1;
	memset(w_buf, 0x5a, sizeof(w_buf));
	spdk_file_write_async(g_file, fs->sync_target.sync_io_channel, w_buf, 0, 4096,
			      fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(g_file->length == 4096);

	/* Read file */
	g_fserrno = 1;
	memset(r_buf, 0x0, sizeof(r_buf));
	spdk_file_read_async(g_file, fs->sync_target.sync_io_channel, r_buf, 0, 4096,
			     fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(memcmp(r_buf, w_buf, sizeof(r_buf)) == 0);

	g_fserrno = 1;
	spdk_file_close_async(g_file, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
}

static void
fs_writev_readv_async(void)
{
	struct spdk_filesystem *fs;
	struct spdk_bs_dev *dev;
	struct iovec w_iov[2];
	struct iovec r_iov[2];
	uint8_t w_buf[4096];
	uint8_t r_buf[4096];

	dev = init_dev();

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	g_file = NULL;
	g_fserrno = 1;
	spdk_fs_open_file_async(fs, "file1", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	SPDK_CU_ASSERT_FATAL(g_file != NULL);

	/* Write file */
	CU_ASSERT(g_file->length == 0);
	g_fserrno = 1;
	memset(w_buf, 0x5a, sizeof(w_buf));
	w_iov[0].iov_base = w_buf;
	w_iov[0].iov_len = 2048;
	w_iov[1].iov_base = w_buf + 2048;
	w_iov[1].iov_len = 2048;
	spdk_file_writev_async(g_file, fs->sync_target.sync_io_channel,
			       w_iov, 2, 0, 4096, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(g_file->length == 4096);

	/* Read file */
	g_fserrno = 1;
	memset(r_buf, 0x0, sizeof(r_buf));
	r_iov[0].iov_base = r_buf;
	r_iov[0].iov_len = 2048;
	r_iov[1].iov_base = r_buf + 2048;
	r_iov[1].iov_len = 2048;
	spdk_file_readv_async(g_file, fs->sync_target.sync_io_channel,
			      r_iov, 2, 0, 4096, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(memcmp(r_buf, w_buf, sizeof(r_buf)) == 0);

	/* Overwrite file with block aligned */
	g_fserrno = 1;
	memset(w_buf, 0x6a, sizeof(w_buf));
	w_iov[0].iov_base = w_buf;
	w_iov[0].iov_len = 2048;
	w_iov[1].iov_base = w_buf + 2048;
	w_iov[1].iov_len = 2048;
	spdk_file_writev_async(g_file, fs->sync_target.sync_io_channel,
			       w_iov, 2, 0, 4096, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(g_file->length == 4096);

	/* Read file to verify the overwritten data */
	g_fserrno = 1;
	memset(r_buf, 0x0, sizeof(r_buf));
	r_iov[0].iov_base = r_buf;
	r_iov[0].iov_len = 2048;
	r_iov[1].iov_base = r_buf + 2048;
	r_iov[1].iov_len = 2048;
	spdk_file_readv_async(g_file, fs->sync_target.sync_io_channel,
			      r_iov, 2, 0, 4096, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	CU_ASSERT(memcmp(r_buf, w_buf, sizeof(r_buf)) == 0);

	g_fserrno = 1;
	spdk_file_close_async(g_file, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
}

static void
tree_find_buffer_ut(void)
{
	struct cache_tree *root;
	struct cache_tree *level1_0;
	struct cache_tree *level0_0_0;
	struct cache_tree *level0_0_12;
	struct cache_buffer *leaf_0_0_4;
	struct cache_buffer *leaf_0_12_8;
	struct cache_buffer *leaf_9_23_15;
	struct cache_buffer *buffer;

	level1_0 = calloc(1, sizeof(struct cache_tree));
	SPDK_CU_ASSERT_FATAL(level1_0 != NULL);
	level0_0_0 = calloc(1, sizeof(struct cache_tree));
	SPDK_CU_ASSERT_FATAL(level0_0_0 != NULL);
	level0_0_12 = calloc(1, sizeof(struct cache_tree));
	SPDK_CU_ASSERT_FATAL(level0_0_12 != NULL);
	leaf_0_0_4 = calloc(1, sizeof(struct cache_buffer));
	SPDK_CU_ASSERT_FATAL(leaf_0_0_4 != NULL);
	leaf_0_12_8 = calloc(1, sizeof(struct cache_buffer));
	SPDK_CU_ASSERT_FATAL(leaf_0_12_8 != NULL);
	leaf_9_23_15 = calloc(1, sizeof(struct cache_buffer));
	SPDK_CU_ASSERT_FATAL(leaf_9_23_15 != NULL);

	level1_0->level = 1;
	level0_0_0->level = 0;
	level0_0_12->level = 0;

	leaf_0_0_4->offset = CACHE_BUFFER_SIZE * 4;
	level0_0_0->u.buffer[4] = leaf_0_0_4;
	level0_0_0->present_mask |= (1ULL << 4);

	leaf_0_12_8->offset = CACHE_TREE_LEVEL_SIZE(1) * 12 + CACHE_BUFFER_SIZE * 8;
	level0_0_12->u.buffer[8] = leaf_0_12_8;
	level0_0_12->present_mask |= (1ULL << 8);

	level1_0->u.tree[0] = level0_0_0;
	level1_0->present_mask |= (1ULL << 0);
	level1_0->u.tree[12] = level0_0_12;
	level1_0->present_mask |= (1ULL << 12);

	buffer = spdk_tree_find_buffer(NULL, 0);
	CU_ASSERT(buffer == NULL);

	buffer = spdk_tree_find_buffer(level0_0_0, 0);
	CU_ASSERT(buffer == NULL);

	buffer = spdk_tree_find_buffer(level0_0_0, CACHE_TREE_LEVEL_SIZE(0) + 1);
	CU_ASSERT(buffer == NULL);

	buffer = spdk_tree_find_buffer(level0_0_0, leaf_0_0_4->offset);
	CU_ASSERT(buffer == leaf_0_0_4);

	buffer = spdk_tree_find_buffer(level1_0, leaf_0_0_4->offset);
	CU_ASSERT(buffer == leaf_0_0_4);

	buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset);
	CU_ASSERT(buffer == leaf_0_12_8);

	buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset + CACHE_BUFFER_SIZE - 1);
	CU_ASSERT(buffer == leaf_0_12_8);

	buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset - 1);
	CU_ASSERT(buffer == NULL);

	leaf_9_23_15->offset = CACHE_TREE_LEVEL_SIZE(2) * 9 +
			       CACHE_TREE_LEVEL_SIZE(1) * 23 +
			       CACHE_BUFFER_SIZE * 15;
	root = spdk_tree_insert_buffer(level1_0, leaf_9_23_15);
	CU_ASSERT(root != level1_0);
	buffer = spdk_tree_find_buffer(root, leaf_9_23_15->offset);
	CU_ASSERT(buffer == leaf_9_23_15);
	spdk_tree_free_buffers(root);
	free(root);
}

static void
channel_ops(void)
{
	struct spdk_filesystem *fs;
	struct spdk_bs_dev *dev;
	struct spdk_io_channel *channel;

	dev = init_dev();

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	channel =  spdk_fs_alloc_io_channel(fs);
	CU_ASSERT(channel != NULL);

	spdk_fs_free_io_channel(channel);

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	g_fs = NULL;
}

static void
channel_ops_sync(void)
{
	struct spdk_filesystem *fs;
	struct spdk_bs_dev *dev;
	struct spdk_fs_thread_ctx *channel;

	dev = init_dev();

	spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
	poll_threads();
	SPDK_CU_ASSERT_FATAL(g_fs != NULL);
	CU_ASSERT(g_fserrno == 0);
	fs = g_fs;
	SPDK_CU_ASSERT_FATAL(fs->bs->dev == dev);

	channel =  spdk_fs_alloc_thread_ctx(fs);
	CU_ASSERT(channel != NULL);

	spdk_fs_free_thread_ctx(channel);

	g_fserrno = 1;
	spdk_fs_unload(fs, fs_op_complete, NULL);
	poll_threads();
	CU_ASSERT(g_fserrno == 0);
	g_fs = NULL;
}

int main(int argc, char **argv)
{
	CU_pSuite	suite = NULL;
	unsigned int	num_failures;

	if (CU_initialize_registry() != CUE_SUCCESS) {
		return CU_get_error();
	}

	suite = CU_add_suite("blobfs_async_ut", NULL, NULL);
	if (suite == NULL) {
		CU_cleanup_registry();
		return CU_get_error();
	}

	if (
		CU_add_test(suite, "fs_init", fs_init) == NULL ||
		CU_add_test(suite, "fs_open", fs_open) == NULL ||
		CU_add_test(suite, "fs_create", fs_create) == NULL ||
		CU_add_test(suite, "fs_truncate", fs_truncate) == NULL ||
		CU_add_test(suite, "fs_rename", fs_rename) == NULL ||
		CU_add_test(suite, "fs_rw_async", fs_rw_async) == NULL ||
		CU_add_test(suite, "fs_writev_readv_async", fs_writev_readv_async) == NULL ||
		CU_add_test(suite, "tree_find_buffer", tree_find_buffer_ut) == NULL ||
		CU_add_test(suite, "channel_ops", channel_ops) == NULL ||
		CU_add_test(suite, "channel_ops_sync", channel_ops_sync) == NULL
	) {
		CU_cleanup_registry();
		return CU_get_error();
	}

	allocate_threads(1);
	set_thread(0);

	g_dev_buffer = calloc(1, DEV_BUFFER_SIZE);
	CU_basic_set_mode(CU_BRM_VERBOSE);
	CU_basic_run_tests();
	num_failures = CU_get_number_of_failures();
	CU_cleanup_registry();
	free(g_dev_buffer);

	free_threads();

	return num_failures;
}