/*
 * Copyright (C) 2017 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 <cstdint>

#include <gtest/gtest.h>

#include "netdutils/MockSyscalls.h"
#include "netdutils/Status.h"
#include "netdutils/Syscalls.h"

using testing::Mock;
using testing::Return;
using testing::StrictMock;

namespace android {
namespace netdutils {
namespace {

// Force implicit conversion from UniqueFd -> Fd
inline Fd toFd(const UniqueFd& fd) {
    return fd;
}

}  // namespace

TEST(Fd, smoke) {
    // Expect the following lines to compile
    Fd fd1(1);
    Fd fd2(fd1);
    Fd fd3 = fd1;
    const Fd fd4(8);
    const Fd fd5(fd4);
    const Fd fd6 = fd4;
    EXPECT_TRUE(isWellFormed(fd3));
    EXPECT_TRUE(isWellFormed(fd6));

    // Corner case
    Fd zero(0);
    EXPECT_TRUE(isWellFormed(zero));

    // Invalid file descriptors
    Fd bad(-1);
    Fd weird(-9);
    EXPECT_FALSE(isWellFormed(bad));
    EXPECT_FALSE(isWellFormed(weird));

    // Default constructor
    EXPECT_EQ(Fd(-1), Fd());
    std::stringstream ss;
    ss << fd3 << " " << fd6 << " " << bad << " " << weird;
    EXPECT_EQ("Fd[1] Fd[8] Fd[-1] Fd[-9]", ss.str());
}

class UniqueFdTest : public testing::Test {
  protected:
    StrictMock<ScopedMockSyscalls> mSyscalls;
};

TEST_F(UniqueFdTest, operatorOstream) {
    UniqueFd u(97);
    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
    std::stringstream ss;
    ss << u;
    EXPECT_EQ("UniqueFd[Fd[97]]", ss.str());
    u.reset();
}

TEST_F(UniqueFdTest, destructor) {
    {
        UniqueFd u(98);
        EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
    }
    // Expectation above should be upon leaving nested scope
    Mock::VerifyAndClearExpectations(&mSyscalls);
}

TEST_F(UniqueFdTest, reset) {
    UniqueFd u(99);
    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
    u.reset();

    // Expectation above should be upon reset
    Mock::VerifyAndClearExpectations(&mSyscalls);
}

TEST_F(UniqueFdTest, moveConstructor) {
    constexpr Fd kFd(101);
    UniqueFd u1(kFd);
    {
        UniqueFd u2(std::move(u1));
        EXPECT_FALSE(isWellFormed(u1));
        EXPECT_TRUE(isWellFormed(u2));
        EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
    }
    // Expectation above should be upon leaving nested scope
    Mock::VerifyAndClearExpectations(&mSyscalls);
}

TEST_F(UniqueFdTest, moveAssignment) {
    constexpr Fd kFd(102);
    UniqueFd u1(kFd);
    {
        UniqueFd u2 = std::move(u1);
        EXPECT_FALSE(isWellFormed(u1));
        EXPECT_TRUE(isWellFormed(u2));
        UniqueFd u3;
        u3 = std::move(u2);
        EXPECT_FALSE(isWellFormed(u2));
        EXPECT_TRUE(isWellFormed(u3));
        EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
    }
    // Expectation above should be upon leaving nested scope
    Mock::VerifyAndClearExpectations(&mSyscalls);
}

TEST_F(UniqueFdTest, constConstructor) {
    constexpr Fd kFd(103);
    const UniqueFd u(kFd);
    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
}

TEST_F(UniqueFdTest, closeFailure) {
    constexpr Fd kFd(103);
    UniqueFd u(kFd);
    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(statusFromErrno(EINTR, "test")));
    EXPECT_DEBUG_DEATH(u.reset(), "");
}

}  // namespace netdutils
}  // namespace android