// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <gtest/gtest.h>

#include <util/unittests/resource.h>
#include <util/unittests/check_valgrind.h>

#include <log/logger.h>
#include <log/logger_manager.h>
#include <log/logger_name.h>
#include <log/logger_support.h>
#include <log/log_messages.h>
#include <log/interprocess/interprocess_sync_file.h>
#include <log/output_option.h>
#include <log/tests/log_test_messages.h>

#include <cstdlib>
#include <iostream>
#include <string>

using namespace isc;
using namespace isc::log;
using namespace std;

/// \brief Logger Test
///
/// As the logger is only a shell around the implementation, this tests also
/// checks the logger implementation class as well.

class LoggerTest : public ::testing::Test {
public:
    LoggerTest() : kld_value_(0) {
        // HasAppender fails when KEA_LOGGER_DESTINATION is set so remove it.
        kld_value_ = getenv("KEA_LOGGER_DESTINATION");
        if (kld_value_ != 0) {
            static_cast<void>(unsetenv("KEA_LOGGER_DESTINATION"));
        }

        isc::log::initLogger();
    }
    ~LoggerTest() {
        LoggerManager::reset();

        // Restore KEA_LOGGER_DESTINATION value.
        if (kld_value_ != 0) {
            static_cast<void>(setenv("KEA_LOGGER_DESTINATION", kld_value_, 1));
        }
    }

    // The KEA_LOGGER_DESTINATION environment variable value.
    char* kld_value_;
};

// Check version

TEST_F(LoggerTest, Version) {
    EXPECT_NO_THROW(Logger::getVersion());
}

// Checks that the logger is named correctly.

TEST_F(LoggerTest, Name) {

    // Create a logger
    Logger logger("alpha");

    // ... and check the name
    EXPECT_EQ(getRootLoggerName() + string(".alpha"), logger.getName());
}

// This test attempts to get two instances of a logger with the same name
// and checks that they are in fact the same logger.

TEST_F(LoggerTest, GetLogger) {

    const char* name1 = "alpha";
    const char* name2 = "beta";

    // Instantiate two loggers that should be the same
    Logger logger1(name1);
    Logger logger2(name1);
    // And check they equal
    EXPECT_TRUE(logger1 == logger2);

    // Instantiate another logger with another name and check that it
    // is different to the previously instantiated ones.
    Logger logger3(name2);
    EXPECT_FALSE(logger1 == logger3);
}

// Check that the logger levels are get set properly.

TEST_F(LoggerTest, Severity) {

    // Create a logger
    Logger logger("alpha");

    // Now check the levels
    logger.setSeverity(isc::log::NONE);
    EXPECT_EQ(isc::log::NONE, logger.getSeverity());

    logger.setSeverity(isc::log::FATAL);
    EXPECT_EQ(isc::log::FATAL, logger.getSeverity());

    logger.setSeverity(isc::log::ERROR);
    EXPECT_EQ(isc::log::ERROR, logger.getSeverity());

    logger.setSeverity(isc::log::WARN);
    EXPECT_EQ(isc::log::WARN, logger.getSeverity());

    logger.setSeverity(isc::log::INFO);
    EXPECT_EQ(isc::log::INFO, logger.getSeverity());

    logger.setSeverity(isc::log::DEBUG);
    EXPECT_EQ(isc::log::DEBUG, logger.getSeverity());

    logger.setSeverity(isc::log::DEFAULT);
    EXPECT_EQ(isc::log::DEFAULT, logger.getSeverity());
}

// Check that the debug level is set correctly.

TEST_F(LoggerTest, DebugLevels) {

    // Create a logger
    Logger logger("alpha");

    // Debug level should be 0 if not at debug severity
    logger.setSeverity(isc::log::NONE, 20);
    EXPECT_EQ(0, logger.getDebugLevel());

    logger.setSeverity(isc::log::INFO, 42);
    EXPECT_EQ(0, logger.getDebugLevel());

    // Should be the value set if the severity is set to DEBUG though.
    logger.setSeverity(isc::log::DEBUG, 32);
    EXPECT_EQ(32, logger.getDebugLevel());

    logger.setSeverity(isc::log::DEBUG, 97);
    EXPECT_EQ(97, logger.getDebugLevel());

    // Try the limits
    logger.setSeverity(isc::log::DEBUG, -1);
    EXPECT_EQ(0, logger.getDebugLevel());

    logger.setSeverity(isc::log::DEBUG, 0);
    EXPECT_EQ(0, logger.getDebugLevel());

    logger.setSeverity(isc::log::DEBUG, 1);
    EXPECT_EQ(1, logger.getDebugLevel());

    logger.setSeverity(isc::log::DEBUG, 98);
    EXPECT_EQ(98, logger.getDebugLevel());

    logger.setSeverity(isc::log::DEBUG, 99);
    EXPECT_EQ(99, logger.getDebugLevel());

    logger.setSeverity(isc::log::DEBUG, 100);
    EXPECT_EQ(99, logger.getDebugLevel());
}

// Check that changing the parent and child severity does not affect the
// other.

TEST_F(LoggerTest, SeverityInheritance) {

    // Create two loggers.  We cheat here as we know that the underlying
    // implementation will set a parent-child relationship if the loggers
    // are named <parent> and <parent>.<child>.
    Logger parent("alpha");
    Logger child("alpha.beta");

    // By default, newly created loggers should have a level of DEFAULT
    // (i.e. default to parent)
    EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
    EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());

    // Set the severity of the parent to debug and check what is
    // reported by the child.
    parent.setSeverity(isc::log::DEBUG, 42);
    EXPECT_EQ(42, parent.getDebugLevel());
    EXPECT_EQ(0,  child.getDebugLevel());
    EXPECT_EQ(42, child.getEffectiveDebugLevel());

    // Setting the child to DEBUG severity should set its own
    // debug level.
    child.setSeverity(isc::log::DEBUG, 53);
    EXPECT_EQ(53,  child.getDebugLevel());
    EXPECT_EQ(53, child.getEffectiveDebugLevel());

    // If the child severity is set to something other than DEBUG,
    // the debug level should be reported as 0.
    child.setSeverity(isc::log::ERROR);
    EXPECT_EQ(0,  child.getDebugLevel());
    EXPECT_EQ(0, child.getEffectiveDebugLevel());
}

// Check that changing the parent and child debug level does not affect
// the other.

TEST_F(LoggerTest, DebugLevelInheritance) {

    // Create two loggers.  We cheat here as we know that the underlying
    // implementation will set a parent-child relationship if the loggers
    // are named <parent> and <parent>.<child>.
    Logger parent("alpha");
    Logger child("alpha.beta");

    // By default, newly created loggers should have a level of DEFAULT
    // (i.e. default to parent)
    EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
    EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());

    // Set the severity of the child to something other than the default -
    // check it changes and that of the parent does not.
    child.setSeverity(isc::log::INFO);
    EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
    EXPECT_EQ(isc::log::INFO, child.getSeverity());

    // Reset the child severity and set that of the parent
    child.setSeverity(isc::log::DEFAULT);
    EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
    EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
    parent.setSeverity(isc::log::WARN);
    EXPECT_EQ(isc::log::WARN, parent.getSeverity());
    EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
}

// Check that severity is inherited.

TEST_F(LoggerTest, EffectiveSeverityInheritance) {

    // Create two loggers.  We cheat here as we know that the underlying
    // implementation will set a parent-child relationship if the loggers
    // are named <parent> and <parent>.<child>.
    Logger parent("test6");
    Logger child("test6.beta");

    // By default, newly created loggers should have a level of DEFAULT
    // (i.e. default to parent) and the root should have a default severity
    // of INFO.  However, the latter is only enforced when created by the
    // RootLogger class, so explicitly set it for the parent for now.
    parent.setSeverity(isc::log::INFO);
    EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());

    EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
    EXPECT_EQ(isc::log::INFO, child.getEffectiveSeverity());

    // Set the severity of the child to something other than the default -
    // check it changes and that of the parent does not.
    child.setSeverity(isc::log::FATAL);
    EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
    EXPECT_EQ(isc::log::FATAL, child.getEffectiveSeverity());

    // Reset the child severity and check again.
    child.setSeverity(isc::log::DEFAULT);
    EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
    EXPECT_EQ(isc::log::INFO, child.getEffectiveSeverity());

    // Change the parent's severity and check it is reflects in the child.
    parent.setSeverity(isc::log::WARN);
    EXPECT_EQ(isc::log::WARN, parent.getEffectiveSeverity());
    EXPECT_EQ(isc::log::WARN, child.getEffectiveSeverity());
}

// Test the isXxxxEnabled methods.

TEST_F(LoggerTest, IsXxxEnabled) {

    Logger logger("test7");

    logger.setSeverity(isc::log::INFO);
    EXPECT_FALSE(logger.isDebugEnabled());
    EXPECT_TRUE(logger.isInfoEnabled());
    EXPECT_TRUE(logger.isWarnEnabled());
    EXPECT_TRUE(logger.isErrorEnabled());
    EXPECT_TRUE(logger.isFatalEnabled());

    logger.setSeverity(isc::log::WARN);
    EXPECT_FALSE(logger.isDebugEnabled());
    EXPECT_FALSE(logger.isInfoEnabled());
    EXPECT_TRUE(logger.isWarnEnabled());
    EXPECT_TRUE(logger.isErrorEnabled());
    EXPECT_TRUE(logger.isFatalEnabled());

    logger.setSeverity(isc::log::ERROR);
    EXPECT_FALSE(logger.isDebugEnabled());
    EXPECT_FALSE(logger.isInfoEnabled());
    EXPECT_FALSE(logger.isWarnEnabled());
    EXPECT_TRUE(logger.isErrorEnabled());
    EXPECT_TRUE(logger.isFatalEnabled());

    logger.setSeverity(isc::log::FATAL);
    EXPECT_FALSE(logger.isDebugEnabled());
    EXPECT_FALSE(logger.isInfoEnabled());
    EXPECT_FALSE(logger.isWarnEnabled());
    EXPECT_FALSE(logger.isErrorEnabled());
    EXPECT_TRUE(logger.isFatalEnabled());

    // Check various debug levels

    logger.setSeverity(isc::log::DEBUG);
    EXPECT_TRUE(logger.isDebugEnabled());
    EXPECT_TRUE(logger.isInfoEnabled());
    EXPECT_TRUE(logger.isWarnEnabled());
    EXPECT_TRUE(logger.isErrorEnabled());
    EXPECT_TRUE(logger.isFatalEnabled());

    logger.setSeverity(isc::log::DEBUG, 45);
    EXPECT_TRUE(logger.isDebugEnabled());
    EXPECT_TRUE(logger.isInfoEnabled());
    EXPECT_TRUE(logger.isWarnEnabled());
    EXPECT_TRUE(logger.isErrorEnabled());
    EXPECT_TRUE(logger.isFatalEnabled());

    // Create a child logger with no severity set, and check that it reflects
    // the severity of the parent logger.

    Logger child("test7.child");
    logger.setSeverity(isc::log::FATAL);
    EXPECT_FALSE(child.isDebugEnabled());
    EXPECT_FALSE(child.isInfoEnabled());
    EXPECT_FALSE(child.isWarnEnabled());
    EXPECT_FALSE(child.isErrorEnabled());
    EXPECT_TRUE(child.isFatalEnabled());

    logger.setSeverity(isc::log::INFO);
    EXPECT_FALSE(child.isDebugEnabled());
    EXPECT_TRUE(child.isInfoEnabled());
    EXPECT_TRUE(child.isWarnEnabled());
    EXPECT_TRUE(child.isErrorEnabled());
    EXPECT_TRUE(child.isFatalEnabled());
}

// Within the Debug level there are 100 debug levels.  Test that we know
// when to issue a debug message.

TEST_F(LoggerTest, IsDebugEnabledLevel) {

    Logger logger("test8");

    int MID_LEVEL = (MIN_DEBUG_LEVEL + MAX_DEBUG_LEVEL) / 2;

    logger.setSeverity(isc::log::DEBUG);
    EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
    EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
    EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));

    logger.setSeverity(isc::log::DEBUG, MIN_DEBUG_LEVEL);
    EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
    EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
    EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));

    logger.setSeverity(isc::log::DEBUG, MID_LEVEL);
    EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
    EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL - 1));
    EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
    EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL + 1));
    EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));

    logger.setSeverity(isc::log::DEBUG, MAX_DEBUG_LEVEL);
    EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
    EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
    EXPECT_TRUE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
}

// Check that loggers with invalid names give an error.

TEST_F(LoggerTest, LoggerNameLength) {
    // Null name
    EXPECT_THROW(Logger(NULL), LoggerNameNull);

    // Declare space for the logger name.  The length of names checked
    // will range from 0 through MAX_LOGGER_NAME_SIZE + 1: to allow for
    // the trailing null, at least one more byte than the longest name size
    // must be reserved.
    char name[Logger::MAX_LOGGER_NAME_SIZE + 2];

    // Zero-length name should throw an exception
    name[0] = '\0';
    EXPECT_THROW({
            Logger dummy(name);
            }, LoggerNameError);

    // Work through all valid names.
    for (size_t i = 0; i < Logger::MAX_LOGGER_NAME_SIZE; ++i) {

        // Append a character to the name and check that a logger with that
        // name can be created without throwing an exception.
        name[i] = 'X';
        name[i + 1] = '\0';
        EXPECT_NO_THROW({
                Logger dummy(name);
                }) << "Size of logger name is " << (i + 1);
    }

    // ... and check that an overly long name throws an exception.
    name[Logger::MAX_LOGGER_NAME_SIZE] = 'X';
    name[Logger::MAX_LOGGER_NAME_SIZE + 1] = '\0';
    EXPECT_THROW({
            Logger dummy(name);
            }, LoggerNameError);

}

TEST_F(LoggerTest, setInterprocessSync) {
    // Create a logger
    Logger logger("alpha");

    EXPECT_THROW(logger.setInterprocessSync(NULL), BadInterprocessSync);
}

class MockSync : public isc::log::interprocess::InterprocessSync {
public:
    /// \brief Constructor
    MockSync(const std::string& component_name) :
        InterprocessSync(component_name), was_locked_(false),
        was_unlocked_(false)
    {}

    bool wasLocked() const {
        return (was_locked_);
    }

    bool wasUnlocked() const {
        return (was_unlocked_);
    }

protected:
    bool lock() {
        was_locked_ = true;
        return (true);
    }

    bool tryLock() {
        return (true);
    }

    bool unlock() {
        was_unlocked_ = true;
        return (true);
    }

private:
    bool was_locked_;
    bool was_unlocked_;
};

// Checks that the logger logs exclusively and other Kea components
// are locked out.

TEST_F(LoggerTest, Lock) {
    // Create a logger
    Logger logger("alpha");

    // Setup our own mock sync object so that we can intercept the lock
    // call and check if a lock has been taken.
    MockSync* sync = new MockSync("logger");
    logger.setInterprocessSync(sync);

    // Log a message and put things into play.
    logger.setSeverity(isc::log::INFO, 100);
    logger.info(LOG_LOCK_TEST_MESSAGE);

    EXPECT_TRUE(sync->wasLocked());
    EXPECT_TRUE(sync->wasUnlocked());
}

// Checks that hasAppender() reports
TEST_F(LoggerTest, HasAppender) {
    // Create a logger.
    Logger logger("logger");

    // By default, loggers have a file appender to /dev/null.
    EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_CONSOLE));
    EXPECT_TRUE(logger.hasAppender(OutputOption::DEST_FILE));
    EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_SYSLOG));

    // -- Create some specifications. --

    OutputOption console;
    console.destination = OutputOption::DEST_CONSOLE;
    LoggerSpecification spec_console("logger");
    spec_console.addOutputOption(console);

    OutputOption file;
    file.destination = OutputOption::DEST_FILE;
    file.filename = "/dev/null";
    LoggerSpecification spec_file("logger");
    spec_file.addOutputOption(file);

    OutputOption syslog;
    syslog.destination = OutputOption::DEST_SYSLOG;
    LoggerSpecification spec_syslog("logger");
    spec_syslog.addOutputOption(syslog);

    LoggerManager manager;

    // Check console.
    manager.process(spec_console);
    EXPECT_TRUE(logger.hasAppender(OutputOption::DEST_CONSOLE));
    EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_FILE));
    EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_SYSLOG));

    // Check file.
    manager.process(spec_file);
    EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_CONSOLE));
    EXPECT_TRUE(logger.hasAppender(OutputOption::DEST_FILE));
    EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_SYSLOG));

    // Check syslog.
    manager.process(spec_syslog);
    EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_CONSOLE));
    EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_FILE));
    EXPECT_TRUE(logger.hasAppender(OutputOption::DEST_SYSLOG));
}
