/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <gtest/gtest.h>

#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#include "pmemalloc.h"

class DepsStub : public PmemUserspaceAllocator::Deps, public PmemKernelAllocator::Deps {

 public:

    virtual size_t getPmemTotalSize(int fd, size_t* size) {
        return 0;
    }

    virtual int connectPmem(int fd, int master_fd) {
        return 0;
    }

    virtual int mapPmem(int fd, int offset, size_t size) {
        return 0;
    }

    virtual int unmapPmem(int fd, int offset, size_t size) {
        return 0;
    }

    virtual int getErrno() {
        return 0;
    }

    virtual void* mmap(void* start, size_t length, int prot, int flags, int fd,
            off_t offset) {
        return 0;
    }

    virtual int munmap(void* start, size_t length) {
        return 0;
    }

    virtual int open(const char* pathname, int flags, int mode) {
        return 0;
    }

    virtual int close(int fd) {
        return 0;
    }
};

/******************************************************************************/

class AllocatorStub : public PmemUserspaceAllocator::Deps::Allocator {
    virtual ssize_t setSize(size_t size) {
        return 0;
    }

    virtual size_t  size() const {
        return 0;
    }

    virtual ssize_t allocate(size_t size, uint32_t flags = 0) {
        return 0;
    }

    virtual ssize_t deallocate(size_t offset) {
        return 0;
    }
};

/******************************************************************************/

static const char* fakePmemDev = "/foo/bar";

/******************************************************************************/

struct Deps_InitPmemAreaLockedWithSuccessfulCompletion : public DepsStub {

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags);
        EXPECT_EQ(0, mode);
        return 1234;
    }

    virtual size_t getPmemTotalSize(int fd, size_t* size) {
        EXPECT_EQ(1234, fd);
        *size = 16 << 20;
        return 0;
    }

    virtual void* mmap(void* start, size_t length, int prot, int flags, int fd,
            off_t offset) {
        EXPECT_EQ(1234, fd);
        return (void*)0x87654321;
    }

};

struct Allocator_InitPmemAreaLockedWithSuccessfulCompletion : public AllocatorStub {
    
    virtual ssize_t setSize(size_t size) {
        EXPECT_EQ(size_t(16 << 20), size);
        return 0;
    }
};

TEST(test_pmem_userspace_allocator, testInitPmemAreaLockedWithSuccessfulCompletion) {
    Deps_InitPmemAreaLockedWithSuccessfulCompletion depsMock;
    Allocator_InitPmemAreaLockedWithSuccessfulCompletion allocMock;
    PmemUserspaceAllocator pma(depsMock, allocMock, fakePmemDev);

    int result = pma.init_pmem_area_locked();
    ASSERT_EQ(0, result);
}

/******************************************************************************/

struct Deps_InitPmemAreaLockedWithEnomemOnMmap : public DepsStub {

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags);
        EXPECT_EQ(0, mode);
        return 1234;
    }

    virtual size_t getPmemTotalSize(int fd, size_t* size) {
        EXPECT_EQ(1234, fd);
        *size = 16 << 20;
        return 0;
    }

    virtual int getErrno() {
        return ENOMEM;
    }

    virtual void* mmap(void* start, size_t length, int prot, int flags, int fd,
            off_t offset) {
        return (void*)MAP_FAILED;
    }

};

struct Allocator_InitPmemAreaLockedWithEnomemOnMmap : public AllocatorStub {
    
    virtual ssize_t setSize(size_t size) {
        EXPECT_EQ(size_t(16 << 20), size);
        return 0;
    }
};

TEST(test_pmem_userspace_allocator, testInitPmemAreaLockedWthEnomemOnMmap) {
    Deps_InitPmemAreaLockedWithEnomemOnMmap depsMock;
    Allocator_InitPmemAreaLockedWithEnomemOnMmap allocMock;
    PmemUserspaceAllocator pma(depsMock, allocMock, fakePmemDev);

    int result = pma.init_pmem_area_locked();
    ASSERT_EQ(-ENOMEM, result);
}

/******************************************************************************/

struct Deps_InitPmemAreaLockedWithEaccesOnGetPmemTotalSize : public DepsStub {

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags);
        EXPECT_EQ(0, mode);
        return 1234;
    }

    virtual size_t getPmemTotalSize(int fd, size_t* size) {
        EXPECT_EQ(1234, fd);
        return -EACCES;
    }
};

TEST(test_pmem_userspace_allocator, testInitPmemAreaLockedWthEaccesOnGetPmemTotalSize) {
    Deps_InitPmemAreaLockedWithEaccesOnGetPmemTotalSize depsMock;
    AllocatorStub allocStub;
    PmemUserspaceAllocator pma(depsMock, allocStub, fakePmemDev);

    int result = pma.init_pmem_area_locked();
    ASSERT_EQ(-EACCES, result);
}

/******************************************************************************/

struct Deps_InitPmemAreaLockedWithEaccesOnOpen : public DepsStub {

    virtual int getErrno() {
        return EACCES;
    }

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags);
        EXPECT_EQ(0, mode);
        return -1;
    }
};

TEST(test_pmem_userspace_allocator, testInitPmemAreaLockedWithEaccesOnOpenMaster) {
    Deps_InitPmemAreaLockedWithEaccesOnOpen depsMock;
    AllocatorStub allocStub;
    PmemUserspaceAllocator pma(depsMock, allocStub, fakePmemDev);

    int result = pma.init_pmem_area_locked();
    ASSERT_EQ(-EACCES, result);
}

/******************************************************************************/

typedef Deps_InitPmemAreaLockedWithSuccessfulCompletion Deps_InitPmemAreaWithSuccessfulInitialCompletion;

TEST(test_pmem_userspace_allocator, testInitPmemAreaWithSuccessfulInitialCompletion) {
    Deps_InitPmemAreaWithSuccessfulInitialCompletion depsMock;
    AllocatorStub allocStub;
    PmemUserspaceAllocator pma(depsMock, allocStub, fakePmemDev);

    int result = pma.init_pmem_area();
    ASSERT_EQ(0, result);
}

/******************************************************************************/

typedef Deps_InitPmemAreaLockedWithEaccesOnOpen Deps_InitPmemAreaWithEaccesOnInitLocked;

TEST(test_pmem_userspace_allocator, testInitPmemAreaWithEaccesOnInitLocked) {
    Deps_InitPmemAreaWithEaccesOnInitLocked depsMock;
    AllocatorStub allocStub;
    PmemUserspaceAllocator pma(depsMock, allocStub, fakePmemDev);

    int result = pma.init_pmem_area();
    ASSERT_EQ(-EACCES, result);
}

/******************************************************************************/

TEST(test_pmem_userspace_allocator, testInitPmemAreaAfterSuccessfulInitialCompletion) {
    DepsStub depsStub;
    AllocatorStub allocStub;
    PmemUserspaceAllocator pma(depsStub, allocStub, fakePmemDev);

    pma.set_master_values(1234, 0); // Indicate that the pma has been successfully init'd

    int result = pma.init_pmem_area();
    ASSERT_EQ(0, result);
    //XXX JMG: Add this back in maybe? ASSERT_EQ(1234, pmi.master); // Make sure the master fd wasn't changed
}

/******************************************************************************/

TEST(test_pmem_userspace_allocator, testInitPmemAreaAfterFailedInit) {
    DepsStub depsStub;
    AllocatorStub allocStub;
    PmemUserspaceAllocator pma(depsStub, allocStub, fakePmemDev);

    pma.set_master_values(-EACCES, 0); // Indicate that the pma has failed init

    int result = pma.init_pmem_area();
    ASSERT_EQ(-EACCES, result);
}

/******************************************************************************/

struct Deps_InitPmemAreaLockedWithSuccessfulCompletionWithNoFlags : public DepsStub {

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags & O_RDWR);
        EXPECT_EQ(0, mode);
        return 5678;
    }

    virtual int connectPmem(int fd, int master_fd) {
        EXPECT_EQ(5678, fd);
        EXPECT_EQ(1234, master_fd);
        return 0;
    }

    virtual int mapPmem(int fd, int offset, size_t size) {
        EXPECT_EQ(5678, fd);
        EXPECT_EQ(0x300, offset);
        EXPECT_EQ(size_t(0x100), size);
        return 0;
    }
};


struct Allocator_AllocPmemBufferWithSuccessfulCompletionWithNoFlags : public AllocatorStub {

    virtual ssize_t allocate(size_t size, uint32_t flags = 0) {
        EXPECT_EQ(size_t(0x100), size);
        EXPECT_EQ(uint32_t(0x0), flags);
        return 0x300;
    }
};

TEST(test_pmem_userspace_allocator, testAllocPmemBufferWithSuccessfulCompletionWithNoFlags) {
    Deps_InitPmemAreaLockedWithSuccessfulCompletionWithNoFlags depsMock;
    Allocator_AllocPmemBufferWithSuccessfulCompletionWithNoFlags allocMock;
    PmemUserspaceAllocator pma(depsMock, allocMock, fakePmemDev);

    uint8_t buf[0x300 + 0x100]; // Create a buffer to get memzero'd
    pma.set_master_values(1234, buf); // Indicate that the pma has been successfully init'd

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = 0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(0, result);
    ASSERT_EQ(0x300, offset);
    ASSERT_EQ(5678, fd);
    for (int i = 0x300; i < 0x400; ++i) {
        ASSERT_EQ(uint8_t(0), buf[i]);
    }
}

/******************************************************************************/

typedef Deps_InitPmemAreaLockedWithSuccessfulCompletionWithNoFlags Deps_InitPmemAreaLockedWithSuccessfulCompletionWithAllFlags;

typedef Allocator_AllocPmemBufferWithSuccessfulCompletionWithNoFlags Allocator_AllocPmemBufferWithSuccessfulCompletionWithAllFlags;

TEST(test_pmem_userspace_allocator, testAllocPmemBufferWithSuccessfulCompletionWithAllFlags) {
    Deps_InitPmemAreaLockedWithSuccessfulCompletionWithAllFlags depsMock;
    Allocator_AllocPmemBufferWithSuccessfulCompletionWithAllFlags allocMock;
    PmemUserspaceAllocator pma(depsMock, allocMock, fakePmemDev);

    uint8_t buf[0x300 + 0x100]; // Create a buffer to get memzero'd
    pma.set_master_values(1234, buf); // Indicate that the pma has been successfully init'd

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = ~0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(0, result);
    ASSERT_EQ(0x300, offset);
    ASSERT_EQ(5678, fd);
    for (int i = 0x300; i < 0x400; ++i) {
        ASSERT_EQ(0, buf[i]);
    }
}

/******************************************************************************/

struct Deps_InitPmemAreaLockedWithEnodevOnOpen : public Deps_InitPmemAreaLockedWithSuccessfulCompletionWithNoFlags {

    virtual int getErrno() {
        return ENODEV;
    }

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags & O_RDWR);
        EXPECT_EQ(0, mode);
        return -1;
    }
};

typedef Allocator_AllocPmemBufferWithSuccessfulCompletionWithNoFlags Allocator_AllocPmemBufferWithEnodevOnOpen;

TEST(test_pmem_userspace_allocator, testAllocPmemBufferWithSuccessfulCompletionWithEnodevOnOpen) {
    Deps_InitPmemAreaLockedWithEnodevOnOpen depsMock;
    Allocator_AllocPmemBufferWithEnodevOnOpen allocMock;
    PmemUserspaceAllocator pma(depsMock, allocMock, fakePmemDev);

    uint8_t buf[0x300 + 0x100]; // Create a buffer to get memzero'd
    pma.set_master_values(1234, buf); // Indicate that the pma has been successfully init'd

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = ~0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(-ENODEV, result);
}

/******************************************************************************/

struct Deps_InitPmemAreaLockedWithEnomemOnConnectPmem : public Deps_InitPmemAreaLockedWithSuccessfulCompletionWithNoFlags {

    virtual int getErrno() {
        return ENOMEM;
    }

    virtual int connectPmem(int fd, int master_fd) {
        EXPECT_EQ(5678, fd);
        EXPECT_EQ(1234, master_fd);
        return -1;
    }
};

typedef Allocator_AllocPmemBufferWithSuccessfulCompletionWithNoFlags Allocator_AllocPmemBufferWithEnomemOnConnectPmem;

TEST(test_pmem_userspace_allocator, testAllocPmemBufferWithSuccessfulCompletionWithEnomemOnConnectPmem) {
    Deps_InitPmemAreaLockedWithEnomemOnConnectPmem depsMock;
    Allocator_AllocPmemBufferWithEnomemOnConnectPmem allocMock;
    PmemUserspaceAllocator pma(depsMock, allocMock, fakePmemDev);

    uint8_t buf[0x300 + 0x100]; // Create a buffer to get memzero'd
    pma.set_master_values(1234, buf); // Indicate that the pma has been successfully init'd

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = ~0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(-ENOMEM, result);
}

/******************************************************************************/

struct Deps_InitPmemAreaLockedWithEnomemOnMapPmem : public Deps_InitPmemAreaLockedWithSuccessfulCompletionWithNoFlags {

    virtual int getErrno() {
        return ENOMEM;
    }

    virtual int mapPmem(int fd, int offset, size_t size) {
        EXPECT_EQ(5678, fd);
        EXPECT_EQ(0x300, offset);
        EXPECT_EQ(size_t(0x100), size);
        return -1;
    }
};

typedef Allocator_AllocPmemBufferWithSuccessfulCompletionWithNoFlags Allocator_AllocPmemBufferWithEnomemOnMapPmem;

TEST(test_pmem_userspace_allocator, testAllocPmemBufferWithEnomemOnMapPmem) {
    Deps_InitPmemAreaLockedWithEnomemOnMapPmem depsMock;
    Allocator_AllocPmemBufferWithEnomemOnMapPmem allocMock;
    PmemUserspaceAllocator pma(depsMock, allocMock, fakePmemDev);

    uint8_t buf[0x300 + 0x100]; // Create a buffer to get memzero'd
    pma.set_master_values(1234, buf); // Indicate that the pma has been successfully init'd

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = ~0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(-ENOMEM, result);
}

/******************************************************************************/

struct Deps_KernelAllocPmemBufferWithSuccessfulCompletionWithNoFlags : public DepsStub {

    void* mmapResult;

    Deps_KernelAllocPmemBufferWithSuccessfulCompletionWithNoFlags(void* mmapResult) :
            mmapResult(mmapResult) {}

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags & O_RDWR);
        EXPECT_EQ(0, mode);
        return 5678;
    }

    virtual void* mmap(void* start, size_t length, int prot, int flags, int fd,
            off_t offset) {
        EXPECT_EQ(5678, fd);
        return mmapResult;
    }
};

TEST(test_pmem_kernel_allocator, testAllocPmemBufferWithSuccessfulCompletionWithNoFlags) {
    uint8_t buf[0x100]; // Create a buffer to get memzero'd
    Deps_KernelAllocPmemBufferWithSuccessfulCompletionWithNoFlags depsMock(buf);
    PmemKernelAllocator pma(depsMock, fakePmemDev);

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = 0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(0, result);
    ASSERT_EQ(buf, base);
    ASSERT_EQ(0, offset);
    ASSERT_EQ(5678, fd);
    for (int i = 0; i < 0x100; ++i) {
        ASSERT_EQ(0, buf[i]);
    }
}

/******************************************************************************/

typedef Deps_KernelAllocPmemBufferWithSuccessfulCompletionWithNoFlags Deps_KernelAllocPmemBufferWithSuccessfulCompletionWithAllFlags;

TEST(test_pmem_kernel_allocator, testAllocPmemBufferWithSuccessfulCompletionWithAllFlags) {
    uint8_t buf[0x100]; // Create a buffer to get memzero'd
    Deps_KernelAllocPmemBufferWithSuccessfulCompletionWithAllFlags depsMock(buf);
    PmemKernelAllocator pma(depsMock, fakePmemDev);

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = ~0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(0, result);
    ASSERT_EQ(buf, base);
    ASSERT_EQ(0, offset);
    ASSERT_EQ(5678, fd);
    for (int i = 0; i < 0x100; ++i) {
        ASSERT_EQ(0, buf[i]);
    }
}

/******************************************************************************/

struct Deps_KernelAllocPmemBufferWithEpermOnOpen : public DepsStub {

    virtual int getErrno() {
        return EPERM;
    }

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags & O_RDWR);
        EXPECT_EQ(0, mode);
        return -1;
    }
};


TEST(test_pmem_kernel_allocator, testAllocPmemBufferWithEpermOnOpen) {
    Deps_KernelAllocPmemBufferWithEpermOnOpen depsMock;
    PmemKernelAllocator pma(depsMock, fakePmemDev);

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = ~0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(-EPERM, result);
    ASSERT_EQ(0, base);
    ASSERT_EQ(0, offset);
    ASSERT_EQ(-1, fd);
}

/******************************************************************************/

struct Deps_KernelAllocPmemBufferWithEnomemOnMmap : DepsStub {

    virtual int open(const char* pathname, int flags, int mode) {
        EXPECT_EQ(fakePmemDev, pathname);
        EXPECT_EQ(O_RDWR, flags & O_RDWR);
        EXPECT_EQ(0, mode);
        return 5678;
    }

    virtual void* mmap(void* start, size_t length, int prot, int flags, int fd,
            off_t offset) {
        return (void*)MAP_FAILED;
    }

    virtual int getErrno() {
        return ENOMEM;
    }
};


TEST(test_pmem_kernel_allocator, testAllocPmemBufferWithEnomemOnMmap) {
    Deps_KernelAllocPmemBufferWithEnomemOnMmap depsMock;
    PmemKernelAllocator pma(depsMock, fakePmemDev);

    void* base = 0;
    int offset = -9182, fd = -9182;
    int size = 0x100;
    int flags = ~0;
    int result = pma.alloc_pmem_buffer(size, flags, &base, &offset, &fd);
    ASSERT_EQ(-ENOMEM, result);
    ASSERT_EQ(0, base);
    ASSERT_EQ(0, offset);
    ASSERT_EQ(-1, fd);
}

/******************************************************************************/