/* vim: set ts=8 sts=4 sw=4 tw=80 noet: */
/*======================================================================
Copyright (C) 2004,2005,2009 Walter Doekes <walter+tthsum@wjd.nu>
This file is part of tthsum.

tthsum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

tthsum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with tthsum.  If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
#include "read.h"

#include "test.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef _WIN32
#   include <unistd.h>
#   define O_BINARY 0
#endif /* !_WIN32 */


static const char test_data_src[] = 
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus in erat. "
"Vivamus lorem felis, tempor a, aliquet a, pretium id, lectus. Maecenas semper"
" blandit sem. Aenean faucibus interdum metus. Curabitur quis nulla. Curabitur"
" risus massa, commodo sit amet, adipiscing quis, aliquam eu, dolor. Donec nec"
" libero. Cras ipsum ante, ultricies in, euismod quis, lobortis ut, turpis. "
"Aliquam purus quam, hendrerit et, luctus et, lobortis porttitor, ipsum. "
"Mauris ut urna sit amet tellus pretium ullamcorper. Praesent non ante. "
"\xff\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\xff\xfe\xfd\xfc\xfb\x84\x83\x82\x81\x80\x7f\x7e\x7d\x7c\x7b\x7a"
"123\r\n456\r789\n101";
static char test_data[2048] = {0};
static unsigned test_data_len = 0;
static char test_filename[512] = {0};

static int is_multiple_of_two(unsigned len) {
    unsigned previous = len << 1;
    while (previous == len << 1) {
	previous = len;
	len >>= 1;
    }
    return len == 0;
}

static void init_test_data(const char* src, unsigned length) {
    test_data[0] = test_data[sizeof(test_data)-1] = '\0';
    test_data_len = length;
    if (test_data_len >= sizeof(test_data)) {
	fprintf(stderr, "read_test: please increase test_data size\n");
	return;
    }
    memcpy(test_data, test_data_src, test_data_len);
}

static void init_test_filename() {
#if defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || _XOPEN_SOURCE >= 500
    int fd;
    strcpy(test_filename, "test_read.XXXXXX");
    if ((fd = mkstemp(test_filename)) < 0) {
	perror("read_test: error running mkstemp");
	test_filename[0] = '\0';
	return;
    }
    close(fd);
#else
    strcpy(test_filename, "tempfile.bin"); /* XXX not a proper name, no? */
#endif
} 

static void init_test_file(const char* data, unsigned data_len) {
    FILE* fp;
    if ((fp = fopen(test_filename, "wb")) == NULL) {
	perror("read_test: error fopen'ing test_filename for writing");
	return;
    }
    if (fwrite(data, sizeof(char), data_len, fp) != data_len) {
	perror("read_test: error fwrite'ing to test_filename");
	fclose(fp);
	return;
    }
    if (fflush(fp) != 0) {
	perror("read_test: error fflush'ing test_filename");
	fclose(fp);
	return;
    }
    if (fclose(fp) != 0)
	perror("read_test: error fclose'ing test_filename"); 
}

static void set_up_file(const char* data, unsigned length) {
    init_test_filename();
    init_test_file(data, length);
}

static void set_up_file_lorem() {
    init_test_data(test_data_src, strlen(test_data_src));
    /* Cheat a bit: we replace the first \xff in test_data with \x00 */
    {
	char* p = strchr(test_data, '\xff');
	if (p == NULL) {
	    fprintf(stderr, "read_test: expected to find \\xff to replace\n");
	    return;
	}
	*p = '\0';
    }
    init_test_filename();
    init_test_file(test_data, test_data_len);
}

static void tear_down_file() {
    if (test_filename[0] != '\0') {
	unlink(test_filename);
	test_filename[0] = '\0';
    }
}

static int test_read(const char* type, struct rofile* rf,
	const char* compare, unsigned compare_len) {
    int ret, last_read;
    unsigned blocksize, getsize;
    uint64_t filesize;
    const char *data_p;
    if (rf == NULL)
	FAIL1("%s open failed!", type);
    rofinfo(&blocksize, &filesize, rf);
    TEST_PASS2(filesize == (uint64_t)compare_len, "File size mismatch: "
	    "expected %u, got %" PRIu64 "\n", compare_len, filesize);
    TEST_PASS1(is_multiple_of_two(blocksize), "Expected multiple of two for "
	    "block size, got %u", blocksize);

    last_read = 0;
    do {
	if ((ret = rofread(&data_p, &getsize, rf)) < 0)
	    FAIL1("%s's read returned -1", type);
	if (ret == 0)
	    break;
	/* We test that all reads are exactly blocksize except the last read. */
	if (getsize < blocksize) {
	    TEST_PASS(!last_read,
		    "Previous block returned was too small while not at EOF");
	    last_read = 1;
	}
	TEST_PASS(memcmp(compare, data_p, getsize) == 0, "Data mismatch!");
	compare += getsize;
    } while (ret);

    return 0;
}

static int test_readall(const char* type, struct rofile* rf,
	const char* compare, unsigned compare_len) {
    unsigned length;
    char* all = rof_readall(rf, &length);
    TEST_PASS2(all != NULL, "%s readall returned NULL, rf is %p", type,
	    (const void*)rf);
    if (rf == NULL) {
	free(all);
	FAIL1("%s open returned NULL", type);
    }
    if (length != compare_len) {
	free(all);
	FAIL3("%s file length %u != %u", type, length, compare_len);
    }
    if (memcmp(all, compare, length) != 0) {
	free(all);
	FAIL1("%s data mismatch", type);
    }
    free(all);
    return 0;
}

static int test_read_file(const char* filename, const char* data,
	unsigned length) {
    struct rofile* rf = NULL;
    int i, ret = 0;
    for (i = 0; i < 6; ++i) {
	switch (i) {
	case 0: ret += test_read("rofopen_mem",
		(rf = rofopen_mem(data, length)), data, length); break;
	case 1: ret += test_readall("rofopen_mem",
		(rf = rofopen_mem(data, length)), data, length); break;
	case 2: ret += test_read("rofopen_mmap",
		(rf = rofopen_mmap(filename)), data, length); break;
	case 3: ret += test_readall("rofopen_mmap",
		(rf = rofopen_mmap(filename)), data, length); break;
	case 4: ret += test_read("rofopen_sysfile",
		(rf = rofopen_sysfile(filename)), data, length); break;
	case 5: ret += test_readall("rofopen_sysfile",
		(rf = rofopen_sysfile(filename)), data, length); break;
	}
	if (rf != NULL)
	    rofclose(rf);
    }
    return ret;
}

static int test_read_file_lorem() {
    int ret;
    set_up_file_lorem();
    ret = test_read_file(test_filename, test_data, test_data_len);
    tear_down_file();
    return ret;
}

static int test_read_file_85s() {
    unsigned len = 3 * 16 * 1024 * 1024 - 123;
    char* data;
    int ret;

    if ((data = (char*)malloc(len)) == NULL)
	FAIL("not enough memory");
    memset(data, 85, len);
    data[0] = '\0';
    data[len - 1] = '\xff';

    set_up_file(data, len);
    ret = test_read_file(test_filename, data, len);
    tear_down_file();

    free(data);
    return ret;
}

static int test_readall_properties() {
    unsigned unused;
    TEST_PASS(rof_readall(NULL, &unused) == NULL,
	    "rof_readall should return NULL when rf is NULL");
    return 0;
}


TESTS(read_test)
    TEST(test_read_file_lorem);
    TEST(test_read_file_85s);
    TEST(test_readall_properties);
ENDTESTS
