
/*
 * sophia database
 * sphia.org
 *
 * Copyright (c) Dmitry Simonenko
 * BSD License
*/

#include <unistd.h>
#include <fcntl.h>
#include <sophia.h>
#include <sp.h>
#include "test.h"

static char *dbrep = "./rep";
static spa a;

static inline int
cmp(char *a, size_t asz, char *b, size_t bsz, void *arg) {
	register uint32_t av = *(uint32_t*)a;
	register uint32_t bv = *(uint32_t*)b;
	if (av == bv)
		return 0;
	return (av > bv) ? 1 : -1;
}

static void
log_empty(void) {
	spfile f;
	sp_fileinit(&f, &a);
	t( mkdir(dbrep, 0755) == 0 );
	t( sp_lognew(&f, dbrep, 1) == 0 );
	t( sp_logcomplete(&f)  == 0 );
	t( sp_logclose(&f)  == 0 );
	t( exists(dbrep, 1, "log") == 1 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_error(env) != NULL );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
log_empty_incomplete(void) {
	spfile f;
	sp_fileinit(&f, &a);
	t( mkdir(dbrep, 0755) == 0 );
	t( sp_lognew(&f, dbrep, 1) == 0 );
	t( sp_logclose(&f)  == 0 );
	t( exists(dbrep, 1, "log.incomplete") == 1 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_error(env) != NULL );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
log_badrecord(void) {
	uint32_t k = 123;
	splogh h = {
		.magic = SPMAGIC,
		.version[0] = SP_VERSION_MAJOR,
		.version[1] = SP_VERSION_MINOR
	};
	spvh vh = {
		.crc = 0,
		.size = sizeof(uint32_t),
		.voffset = 0,
		.vsize = sizeof(uint32_t),
		.flags = SPSET
	};
	speofh eof = { SPEOF };
	spfile f;
	spbatch b;
	sp_fileinit(&f, &a);
	sp_batchinit(&b);
	t( mkdir(dbrep, 0755) == 0 );
	t( sp_lognew(&f, dbrep, 1) == 0 );
	sp_batchadd(&b, &h, sizeof(h));
	sp_batchadd(&b, &vh, sizeof(vh));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &eof, sizeof(eof));
	t( sp_logput(&f, &b) == 0 );
	t( sp_logcomplete(&f)  == 0 );
	t( sp_logclose(&f)  == 0 );
	t( exists(dbrep, 1, "log") == 1 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_error(env) != NULL );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
log_badrecord_incomplete(void) {
	uint32_t k = 123;
	splogh h = {
		.magic = SPMAGIC,
		.version[0] = SP_VERSION_MAJOR,
		.version[1] = SP_VERSION_MINOR
	};
	spvh vh = {
		.crc = 0,
		.size = sizeof(uint32_t),
		.voffset = 0,
		.vsize = sizeof(uint32_t),
		.flags = SPSET
	};
	speofh eof = { SPEOF };
	spfile f;
	spbatch b;
	sp_fileinit(&f, &a);
	sp_batchinit(&b);
	t( mkdir(dbrep, 0755) == 0 );
	t( sp_lognew(&f, dbrep, 1) == 0 );
	sp_batchadd(&b, &h, sizeof(h));
	sp_batchadd(&b, &vh, sizeof(vh));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &eof, sizeof(eof));
	t( sp_logput(&f, &b) == 0 );
	t( sp_logclose(&f)  == 0 );
	t( exists(dbrep, 1, "log.incomplete") == 1 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_error(env) != NULL );
	t( sp_destroy(env) == 0 );
	t( exists(dbrep, 1, "log.incomplete") == 1 );
	t( rmrf(dbrep) == 0 );
}

static void
log_1_badrecord_2_goodrecord(void) {
	uint32_t k = 123;
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db != NULL );
	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );
	t( sp_destroy(db) == 0 );
	t( exists(dbrep, 1, "log") == 1 );
	splogh h = {
		.magic = SPMAGIC,
		.version[0] = SP_VERSION_MAJOR,
		.version[1] = SP_VERSION_MINOR
	};
	spvh vh = {
		.crc = 0,
		.size = sizeof(uint32_t),
		.voffset = 0,
		.vsize = sizeof(uint32_t),
		.flags = SPSET
	};
	speofh eof = { SPEOF };
	spfile f;
	spbatch b;
	sp_batchinit(&b);
	sp_fileinit(&f, &a);
	t( sp_lognew(&f, dbrep, 2) == 0 );
	sp_batchadd(&b, &h, sizeof(h));
	sp_batchadd(&b, &vh, sizeof(vh));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &eof, sizeof(eof));
	t( sp_logput(&f, &b) == 0 );
	t( sp_logcomplete(&f)  == 0 );
	t( sp_logclose(&f)  == 0 );
	t( exists(dbrep, 2, "log") == 1 );
	db = sp_open(env);
	t( db == NULL );
	t( sp_destroy(env) == 0 );
	t( exists(dbrep, 1, "log") == 1 );
	t( exists(dbrep, 2, "log") == 1 );
	t( rmrf(dbrep) == 0 );
}

static void
log_noeof(void) {
	uint32_t k = 123;
	splogh h = {
		.magic = SPMAGIC,
		.version[0] = SP_VERSION_MAJOR,
		.version[1] = SP_VERSION_MINOR
	};
	spvh vh = {
		.crc = 0,
		.size = sizeof(uint32_t),
		.voffset = 0,
		.vsize = sizeof(uint32_t),
		.flags = SPSET
	};
	uint32_t crc;
 	crc    = sp_crc32c(0, &k, sizeof(k));
	crc    = sp_crc32c(crc, &k, sizeof(k));
	vh.crc = sp_crc32c(crc, &vh.size, sizeof(spvh) - sizeof(uint32_t));
	spfile f;
	spbatch b;
	sp_batchinit(&b);
	sp_fileinit(&f, &a);
	t( mkdir(dbrep, 0755) == 0 );
	t( sp_lognew(&f, dbrep, 1) == 0 );
	sp_batchadd(&b, &h, sizeof(h));
	sp_batchadd(&b, &vh, sizeof(vh));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &k, sizeof(k));
	t( sp_logput(&f, &b) == 0 );
	t( sp_logclose(&f)  == 0 );
	t( exists(dbrep, 1, "log.incomplete") == 1 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db != NULL );
	size_t vsize = 0;
	void *vp = NULL;
	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );
	t( vsize == sizeof(k) );
	t( *(uint32_t*)vp == k );
	free(vp);
	t( sp_destroy(db) == 0 );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
log_noeof_complete(void) {
	uint32_t k = 123;
	splogh h = {
		.magic = SPMAGIC,
		.version[0] = SP_VERSION_MAJOR,
		.version[1] = SP_VERSION_MINOR
	};
	spvh vh = {
		.crc = 0,
		.size = sizeof(uint32_t),
		.voffset = 0,
		.vsize = sizeof(uint32_t),
		.flags = SPSET
	};
	uint32_t crc;
 	crc    = sp_crc32c(0, &k, sizeof(k));
	crc    = sp_crc32c(crc, &k, sizeof(k));
	vh.crc = sp_crc32c(crc, &vh.size, sizeof(spvh) - sizeof(uint32_t));
	spfile f;
	spbatch b;
	sp_batchinit(&b);
	sp_fileinit(&f, &a);
	sp_batchinit(&b);
	t( mkdir(dbrep, 0755) == 0 );
	t( sp_lognew(&f, dbrep, 1) == 0 );
	sp_batchadd(&b, &h, sizeof(h));
	sp_batchadd(&b, &vh, sizeof(vh));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &k, sizeof(k));
	t( sp_logput(&f, &b) == 0 );
	t( sp_logcomplete(&f)  == 0 );
	t( sp_logclose(&f)  == 0 );
	t( exists(dbrep, 1, "log") == 1 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_error(env) != NULL );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
db_empty(void) {
	spfile f;
	sp_fileinit(&f, &a);
	t( mkdir(dbrep, 0755) == 0 );
	char path[1024];
	snprintf(path, sizeof(path), "%s/%"PRIu32".db",
	         dbrep, 1);
	int fd = open(path, O_CREAT|O_RDWR, 0644);
	t( fd != -1 );
	t( close(fd) == 0 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_error(env) != NULL );
	t( exists(dbrep, 1, "db") == 1 );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
db_empty_incomplete(void) {
	spfile f;
	sp_fileinit(&f, &a);
	t( mkdir(dbrep, 0755) == 0 );
	char path[1024];
	snprintf(path, sizeof(path), "%s/%"PRIu32".db.incomplete",
	         dbrep, 1);
	int fd = open(path, O_CREAT|O_RDWR, 0644);
	t( fd != -1 );
	t( close(fd) == 0 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_error(env) != NULL );
	t( exists(dbrep, 1, "db.incomplete") == 1 );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
db_badpage(void) {
	sppageh h = {
		.crc = 0,
		.count = 0,
		.size = 1234,
		.bsize = 1234,
	};
	t( mkdir(dbrep, 0755) == 0 );
	char path[1024];
	snprintf(path, sizeof(path), "%s/%"PRIu32".db",
	         dbrep, 1);
	int fd = open(path, O_CREAT|O_RDWR, 0644);
	t( fd != -1 );
	t( write(fd, &h, sizeof(h)) == sizeof(h) );
	t( close(fd) == 0 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_error(env) != NULL );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
log_incomplete_db_incomplete(void) {
	uint32_t k = 123;
	splogh h = {
		.magic = SPMAGIC,
		.version[0] = SP_VERSION_MAJOR,
		.version[1] = SP_VERSION_MINOR
	};
	spvh vh = {
		.crc = 0,
		.size = sizeof(uint32_t),
		.voffset = 0,
		.vsize = sizeof(uint32_t),
		.flags = SPSET
	};
	speofh eof = { SPEOF };
	uint32_t crc;
 	crc    = sp_crc32c(0, &k, sizeof(k));
	crc    = sp_crc32c(crc, &k, sizeof(k));
	vh.crc = sp_crc32c(crc, &vh.size, sizeof(spvh) - sizeof(uint32_t));
	spfile f;
	spbatch b;
	sp_batchinit(&b);
	sp_fileinit(&f, &a);
	t( mkdir(dbrep, 0755) == 0 );
	t( sp_lognew(&f, dbrep, 1) == 0 );
	sp_batchadd(&b, &h, sizeof(h));
	sp_batchadd(&b, &vh, sizeof(vh));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &eof, sizeof(eof));
	t( sp_logput(&f, &b) == 0 );
	t( sp_logclose(&f)  == 0 );
	char path[1024];
	snprintf(path, sizeof(path), "%s/%"PRIu32".db.incomplete",
	         dbrep, 1);
	int fd = open(path, O_CREAT|O_RDWR, 0644);
	t( fd != -1 );
	t( write(fd, &h, sizeof(h)) == sizeof(h) );
	t( close(fd) == 0 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db == NULL );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

static void
log_db_incomplete(void) {
	uint32_t k = 123;
	splogh h = {
		.magic = SPMAGIC,
		.version[0] = SP_VERSION_MAJOR,
		.version[1] = SP_VERSION_MINOR
	};
	spvh vh = {
		.crc = 0,
		.size = sizeof(uint32_t),
		.voffset = 0,
		.vsize = sizeof(uint32_t),
		.flags = SPSET
	};
	speofh eof = { SPEOF };
	uint32_t crc;
 	crc    = sp_crc32c(0, &k, sizeof(k));
	crc    = sp_crc32c(crc, &k, sizeof(k));
	vh.crc = sp_crc32c(crc, &vh.size, sizeof(spvh) - sizeof(uint32_t));
	spfile f;
	spbatch b;
	sp_batchinit(&b);
	sp_fileinit(&f, &a);
	t( mkdir(dbrep, 0755) == 0 );
	t( sp_lognew(&f, dbrep, 1) == 0 );
	sp_batchadd(&b, &h, sizeof(h));
	sp_batchadd(&b, &vh, sizeof(vh));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &k, sizeof(k));
	sp_batchadd(&b, &eof, sizeof(eof));
	t( sp_logput(&f, &b) == 0 );
	t( sp_logcomplete(&f) == 0 );
	t( sp_logclose(&f)  == 0 );
	t( exists(dbrep, 1, "log") == 1 );
	char path[1024];
	snprintf(path, sizeof(path), "%s/%"PRIu32".db.incomplete",
	         dbrep, 1);
	int fd = open(path, O_CREAT|O_RDWR, 0644);
	t( fd != -1 );
	t( write(fd, &h, sizeof(h)) == sizeof(h) );
	t( close(fd) == 0 );
	void *env = sp_env();
	t( env != NULL );
	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );
	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );
	t( sp_ctl(env, SPMERGE, 0) == 0 );
	void *db = sp_open(env);
	t( db != NULL );
	size_t vsize = 0;
	void *vp = NULL;
	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );
	t( vsize == sizeof(k) );
	t( *(uint32_t*)vp == k );
	free(vp);
	t( sp_destroy(db) == 0 );
	t( sp_destroy(env) == 0 );
	t( rmrf(dbrep) == 0 );
}

int
main(int argc, char *argv[])
{
	sp_allocinit(&a, sp_allocstd, NULL);
	rmrf(dbrep);

	test(log_empty);
	test(log_empty_incomplete);
	test(log_badrecord);
	test(log_badrecord_incomplete);
	test(log_1_badrecord_2_goodrecord);
	test(log_noeof);
	test(log_noeof_complete);

	test(db_empty);
	test(db_empty_incomplete);
	test(db_badpage);

	test(log_db_incomplete);
	test(log_incomplete_db_incomplete);
	return 0;
}
