The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define C_KINO_RAMFOLDER
#define C_KINO_CHARBUF
#include "KinoSearch/Util/ToolSet.h"

#include "KinoSearch/Test.h"
#include "KinoSearch/Test/Store/TestFolder.h"
#include "KinoSearch/Store/DirHandle.h"
#include "KinoSearch/Store/FileHandle.h"
#include "KinoSearch/Store/InStream.h"
#include "KinoSearch/Store/OutStream.h"
#include "KinoSearch/Store/RAMFolder.h"

static CharBuf foo               = ZCB_LITERAL("foo");
static CharBuf bar               = ZCB_LITERAL("bar");
static CharBuf baz               = ZCB_LITERAL("baz");
static CharBuf boffo             = ZCB_LITERAL("boffo");
static CharBuf banana            = ZCB_LITERAL("banana");
static CharBuf foo_bar           = ZCB_LITERAL("foo/bar");
static CharBuf foo_boffo         = ZCB_LITERAL("foo/boffo");
static CharBuf foo_foo           = ZCB_LITERAL("foo/foo");
static CharBuf foo_bar_baz       = ZCB_LITERAL("foo/bar/baz");
static CharBuf foo_bar_baz_boffo = ZCB_LITERAL("foo/bar/baz/boffo");
static CharBuf nope              = ZCB_LITERAL("nope");

static void
test_Exists(TestBatch *batch)
{
    Folder *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh;

    Folder_MkDir(folder, &foo);
    Folder_MkDir(folder, &foo_bar);
    fh = Folder_Open_FileHandle(folder, &boffo, FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);
    fh = Folder_Open_FileHandle(folder, &foo_boffo, 
        FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);

    TEST_TRUE(batch, Folder_Exists(folder, &foo), "Dir exists");
    TEST_TRUE(batch, Folder_Exists(folder, &boffo), "File exists");
    TEST_TRUE(batch, Folder_Exists(folder, &foo_bar), 
        "Nested dir exists");
    TEST_TRUE(batch, Folder_Exists(folder, &foo_boffo), 
        "Nested file exists");

    TEST_FALSE(batch, Folder_Exists(folder, &banana), 
        "Non-existent entry");
    TEST_FALSE(batch, Folder_Exists(folder, &foo_foo), 
        "Non-existent nested entry");

    DECREF(folder);
}

static void
test_Set_Path_and_Get_Path(TestBatch *batch)
{
    Folder *folder = (Folder*)RAMFolder_new(&foo);
    TEST_TRUE(batch, CB_Equals(Folder_Get_Path(folder), (Obj*)&foo),
        "Get_Path");
    Folder_Set_Path(folder, &bar);
    TEST_TRUE(batch, CB_Equals(Folder_Get_Path(folder), (Obj*)&bar),
        "Set_Path");
    DECREF(folder);
}

static void
test_MkDir_and_Is_Directory(TestBatch *batch)
{
    Folder *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh;

    TEST_FALSE(batch, Folder_Is_Directory(folder, &foo), 
        "Is_Directory() false for non-existent entry");

    TEST_TRUE(batch, Folder_MkDir(folder, &foo), 
        "MkDir returns true on success");
    TEST_TRUE(batch, Folder_Is_Directory(folder, &foo), 
        "Is_Directory() true for local folder");

    TEST_FALSE(batch, Folder_Is_Directory(folder, &foo_bar_baz), 
        "Is_Directory() false for non-existent deeply nested dir");
    Err_set_error(NULL);
    TEST_FALSE(batch, Folder_MkDir(folder, &foo_bar_baz), 
        "MkDir for deeply nested dir fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "MkDir for deeply nested dir sets Err_error");

    TEST_TRUE(batch, Folder_MkDir(folder, &foo_bar), 
        "MkDir for nested dir");
    TEST_TRUE(batch, Folder_Is_Directory(folder, &foo_bar), 
        "Is_Directory() true for nested dir");

    Err_set_error(NULL);
    TEST_FALSE(batch, Folder_MkDir(folder, &foo_bar), 
        "Overwrite dir with MkDir fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Overwrite dir with MkDir sets Err_error");

    fh = Folder_Open_FileHandle(folder, &foo_boffo, 
        FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);
    Err_set_error(NULL);
    TEST_FALSE(batch, Folder_MkDir(folder, &foo_boffo), 
        "Overwrite file with MkDir fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Overwrite file with MkDir sets Err_error");
    TEST_FALSE(batch, Folder_Is_Directory(folder, &foo_boffo), 
        "Is_Directory() false for nested file");

    DECREF(folder);
}

static void
test_Enclosing_Folder_and_Find_Folder(TestBatch *batch)
{
    Folder *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh;

    Folder_MkDir(folder, &foo);
    Folder_MkDir(folder, &foo_bar);
    Folder_MkDir(folder, &foo_bar_baz);
    fh = Folder_Open_FileHandle(folder, &foo_bar_baz_boffo, 
        FH_CREATE | FH_WRITE_ONLY);

    {
        Folder *encloser = Folder_Enclosing_Folder(folder, (CharBuf*)&nope);
        Folder *found = Folder_Find_Folder(folder, (CharBuf*)&nope);
        TEST_TRUE(batch, encloser == folder, 
            "Enclosing_Folder() - non-existent entry yields parent");
        TEST_TRUE(batch, found == NULL,
            "Find_Folder() - non-existent entry yields NULL");
    }

    {
        Folder *encloser = Folder_Enclosing_Folder(folder, &foo_bar);
        Folder *found = Folder_Find_Folder(folder, &foo_bar);
        TEST_TRUE(batch, 
            encloser 
            && Folder_Is_A(encloser, FOLDER)
            && CB_Ends_With(Folder_Get_Path(encloser), &foo),
            "Enclosing_Folder() - find one directory down");
        TEST_TRUE(batch, 
            found 
            && Folder_Is_A(found, FOLDER)
            && CB_Ends_With(Folder_Get_Path(found), &bar),
            "Find_Folder() - 'foo/bar'");
    }

    {
        Folder *encloser = Folder_Enclosing_Folder(folder, &foo_bar_baz);
        Folder *found = Folder_Find_Folder(folder, &foo_bar_baz);
        TEST_TRUE(batch, 
            encloser 
            && Folder_Is_A(encloser, FOLDER)
            && CB_Ends_With(Folder_Get_Path(encloser), &bar), 
            "Find two directories down");
        TEST_TRUE(batch, 
            found 
            && Folder_Is_A(found, FOLDER)
            && CB_Ends_With(Folder_Get_Path(found), &baz),
            "Find_Folder() - 'foo/bar/baz'");
    }

    {
        Folder *encloser 
            = Folder_Enclosing_Folder(folder, &foo_bar_baz_boffo);
        Folder *found = Folder_Find_Folder(folder, &foo_bar_baz_boffo);
        TEST_TRUE(batch, 
            encloser 
            && Folder_Is_A(encloser, FOLDER)
            && CB_Ends_With(Folder_Get_Path(encloser), &baz), 
            "Recurse to find a directory containing a real file");
        TEST_TRUE(batch, found == NULL,
            "Find_Folder() - file instead of folder yields NULL");
    }

    DECREF(fh);
    DECREF(folder);
}

static void
test_List(TestBatch *batch)
{
    Folder     *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh;
    VArray     *list;
    CharBuf    *elem;

    Folder_MkDir(folder, &foo);
    Folder_MkDir(folder, &foo_bar);
    Folder_MkDir(folder, &foo_bar_baz);
    fh = Folder_Open_FileHandle(folder, &boffo, FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);
    fh = Folder_Open_FileHandle(folder, &banana, FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);

    list = Folder_List(folder, NULL);
    VA_Sort(list, NULL, NULL);
    TEST_INT_EQ(batch, VA_Get_Size(list), 3, "List");
    elem = (CharBuf*)DOWNCAST(VA_Fetch(list, 0), CHARBUF);
    TEST_TRUE(batch, elem && CB_Equals(elem, (Obj*)&banana), 
        "List first file");
    elem = (CharBuf*)DOWNCAST(VA_Fetch(list, 1), CHARBUF);
    TEST_TRUE(batch, elem && CB_Equals(elem, (Obj*)&boffo), 
        "List second file");
    elem = (CharBuf*)DOWNCAST(VA_Fetch(list, 2), CHARBUF);
    TEST_TRUE(batch, elem && CB_Equals(elem, (Obj*)&foo), "List dir");
    DECREF(list);

    list = Folder_List(folder, &foo_bar);
    TEST_INT_EQ(batch, VA_Get_Size(list), 1, "List subdirectory contents");
    elem = (CharBuf*)DOWNCAST(VA_Fetch(list, 0), CHARBUF);
    TEST_TRUE(batch, elem && CB_Equals(elem, (Obj*)&baz), 
        "Just the filename");
    DECREF(list);

    DECREF(folder);
}

static void
test_Open_Dir(TestBatch *batch)
{
    Folder     *folder = (Folder*)RAMFolder_new(NULL);
    DirHandle  *dh;

    Folder_MkDir(folder, &foo);
    Folder_MkDir(folder, &foo_bar);

    dh = Folder_Open_Dir(folder, &foo);
    TEST_TRUE(batch, dh && DH_Is_A(dh, DIRHANDLE), "Open_Dir");
    DECREF(dh);
    dh = Folder_Open_Dir(folder, &foo_bar);
    TEST_TRUE(batch, dh && DH_Is_A(dh, DIRHANDLE), "Open_Dir nested dir");
    DECREF(dh);

    Err_set_error(NULL);
    dh = Folder_Open_Dir(folder, &bar);
    TEST_TRUE(batch, dh == NULL,
        "Open_Dir on non-existent entry fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_Dir on non-existent entry sets Err_error");

    Err_set_error(NULL);
    dh = Folder_Open_Dir(folder, &foo_foo);
    TEST_TRUE(batch, dh == NULL,
        "Open_Dir on non-existent nested entry fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_Dir on non-existent nested entry sets Err_error");

    DECREF(folder);
}

static void
test_Open_FileHandle(TestBatch *batch)
{
    Folder     *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh;

    Folder_MkDir(folder, &foo);

    fh = Folder_Open_FileHandle(folder, &boffo, FH_CREATE | FH_WRITE_ONLY);
    TEST_TRUE(batch, fh && FH_Is_A(fh, FILEHANDLE), "Open_FileHandle");
    DECREF(fh);

    fh = Folder_Open_FileHandle(folder, &foo_boffo, 
        FH_CREATE | FH_WRITE_ONLY);
    TEST_TRUE(batch, fh && FH_Is_A(fh, FILEHANDLE), 
        "Open_FileHandle for nested file");
    DECREF(fh);

    Err_set_error(NULL);
    fh = Folder_Open_FileHandle(folder, &foo, FH_CREATE | FH_WRITE_ONLY);
    TEST_TRUE(batch, fh == NULL,
        "Open_FileHandle on existing dir path fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_FileHandle on existing dir name sets Err_error");

    Err_set_error(NULL);
    fh = Folder_Open_FileHandle(folder, &foo_bar_baz_boffo, 
        FH_CREATE | FH_WRITE_ONLY);
    TEST_TRUE(batch, fh == NULL,
        "Open_FileHandle for entry within non-existent dir fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_FileHandle for entry within non-existent dir sets Err_error");

    DECREF(folder);
}

static void
test_Open_Out(TestBatch *batch)
{
    Folder    *folder = (Folder*)RAMFolder_new(NULL);
    OutStream *outstream;

    Folder_MkDir(folder, &foo);

    outstream = Folder_Open_Out(folder, &boffo);
    TEST_TRUE(batch, outstream && OutStream_Is_A(outstream, OUTSTREAM), 
        "Open_Out");
    DECREF(outstream);

    outstream = Folder_Open_Out(folder, &foo_boffo);
    TEST_TRUE(batch, outstream && OutStream_Is_A(outstream, OUTSTREAM), 
        "Open_Out for nested file");
    DECREF(outstream);

    Err_set_error(NULL);
    outstream = Folder_Open_Out(folder, &boffo);
    TEST_TRUE(batch, outstream == NULL,
        "Open_OutStream on existing file fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_Out on existing file sets Err_error");

    Err_set_error(NULL);
    outstream = Folder_Open_Out(folder, &foo);
    TEST_TRUE(batch, outstream == NULL,
        "Open_OutStream on existing dir path fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_Out on existing dir name sets Err_error");

    Err_set_error(NULL);
    outstream = Folder_Open_Out(folder, &foo_bar_baz_boffo);
    TEST_TRUE(batch, outstream == NULL,
        "Open_Out for entry within non-existent dir fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_Out for entry within non-existent dir sets Err_error");

    DECREF(folder);
}

static void
test_Open_In(TestBatch *batch)
{
    Folder     *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh;
    InStream   *instream;

    Folder_MkDir(folder, &foo);
    Folder_MkDir(folder, &foo_bar);
    fh = Folder_Open_FileHandle(folder, &boffo, FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);
    fh = Folder_Open_FileHandle(folder, &foo_boffo, FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);

    instream = Folder_Open_In(folder, &boffo);
    TEST_TRUE(batch, instream && InStream_Is_A(instream, INSTREAM), 
        "Open_In");
    DECREF(instream);

    instream = Folder_Open_In(folder, &foo_boffo);
    TEST_TRUE(batch, instream && InStream_Is_A(instream, INSTREAM), 
        "Open_In for nested file");
    DECREF(instream);

    Err_set_error(NULL);
    instream = Folder_Open_In(folder, &foo);
    TEST_TRUE(batch, instream == NULL,
        "Open_InStream on existing dir path fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_In on existing dir name sets Err_error");

    Err_set_error(NULL);
    instream = Folder_Open_In(folder, &foo_bar_baz_boffo);
    TEST_TRUE(batch, instream == NULL,
        "Open_In for entry within non-existent dir fails");
    TEST_TRUE(batch, Err_get_error() != NULL,
        "Open_In for entry within non-existent dir sets Err_error");

    DECREF(folder);
}

static void
test_Delete(TestBatch *batch)
{
    Folder *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh;
    bool_t result;

    Folder_MkDir(folder, &foo);
    Folder_MkDir(folder, &foo_bar);
    fh = Folder_Open_FileHandle(folder, &boffo, FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);
    fh = Folder_Open_FileHandle(folder, &foo_boffo, 
        FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);

    Err_set_error(NULL);
    result = Folder_Delete(folder, &banana);
    TEST_FALSE(batch, result, "Delete on non-existent entry returns false");

    Err_set_error(NULL);
    result = Folder_Delete(folder, &foo);
    TEST_FALSE(batch, result, "Delete on non-empty dir returns false");

    TEST_TRUE(batch, Folder_Delete(folder, &foo_boffo), 
        "Delete nested file");
    TEST_FALSE(batch, Folder_Exists(folder, &foo_boffo), 
        "File is really gone");
    TEST_TRUE(batch, Folder_Delete(folder, &foo_bar), 
        "Delete nested dir");
    TEST_FALSE(batch, Folder_Exists(folder, &foo_bar), 
        "Dir is really gone");
    TEST_TRUE(batch, Folder_Delete(folder, &foo), "Delete empty dir");
    TEST_FALSE(batch, Folder_Exists(folder, &foo), "Dir is really gone");

    DECREF(folder);
}

static void
test_Delete_Tree(TestBatch *batch)
{
    Folder *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh;
    bool_t result;

    // Create tree to be deleted. 
    Folder_MkDir(folder, &foo);
    Folder_MkDir(folder, &foo_bar);
    Folder_MkDir(folder, &foo_bar_baz);
    fh = Folder_Open_FileHandle(folder, &foo_bar_baz_boffo, 
        FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);

    // Create bystanders. 
    Folder_MkDir(folder, &bar);
    fh = Folder_Open_FileHandle(folder, &baz, FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);

    result = Folder_Delete_Tree(folder, &foo);
    TEST_TRUE(batch, result, "Delete_Tree() succeeded");
    TEST_FALSE(batch, Folder_Exists(folder, &foo), "Tree really gone");

    TEST_TRUE(batch, Folder_Exists(folder, &bar), 
        "local dir with same name as nested dir left intact");
    TEST_TRUE(batch, Folder_Exists(folder, &baz), 
        "local file with same name as nested dir left intact");

    // Kill off the bystanders. 
    result = Folder_Delete_Tree(folder, &bar);
    TEST_TRUE(batch, result, "Delete_Tree() on empty dir");
    result = Folder_Delete_Tree(folder, &baz);
    TEST_TRUE(batch, result, "Delete_Tree() on file");

    // Create new tree to be deleted. 
    Folder_MkDir(folder, &foo);
    Folder_MkDir(folder, &foo_bar);
    Folder_MkDir(folder, &foo_bar_baz);
    fh = Folder_Open_FileHandle(folder, &foo_bar_baz_boffo, 
        FH_CREATE | FH_WRITE_ONLY);
    DECREF(fh);

    // Remove tree in subdir. 
    result = Folder_Delete_Tree(folder, &foo_bar);
    TEST_TRUE(batch, result, "Delete_Tree() of subdir succeeded");
    TEST_FALSE(batch, Folder_Exists(folder, &foo_bar), 
        "subdir really gone");
    TEST_TRUE(batch, Folder_Exists(folder, &foo), 
        "enclosing dir left intact");

    DECREF(folder);
}

static void
test_Slurp_File(TestBatch *batch)
{
    Folder *folder = (Folder*)RAMFolder_new(NULL);
    FileHandle *fh = Folder_Open_FileHandle(folder, &foo, 
        FH_CREATE | FH_WRITE_ONLY);
    ByteBuf *contents;

    FH_Write(fh, "stuff", 5);
    FH_Close(fh);
    DECREF(fh);
    contents = Folder_Slurp_File(folder, &foo);
    TEST_TRUE(batch, BB_Equals_Bytes(contents, "stuff", 5), "Slurp_File");

    DECREF(contents);
    DECREF(folder);
}

void
TestFolder_run_tests()
{
    TestBatch *batch = TestBatch_new(79);

    TestBatch_Plan(batch);
    test_Exists(batch);
    test_Set_Path_and_Get_Path(batch);
    test_MkDir_and_Is_Directory(batch);
    test_Enclosing_Folder_and_Find_Folder(batch);
    test_List(batch);
    test_Open_Dir(batch);
    test_Open_FileHandle(batch);
    test_Open_Out(batch);
    test_Open_In(batch);
    test_Delete(batch);
    test_Delete_Tree(batch);
    test_Slurp_File(batch);

    DECREF(batch);
}

/* Copyright 2005-2011 Marvin Humphrey
 *
 * This program is free software; you can redistribute it and/or modify
 * under the same terms as Perl itself.
 */