text: /**
   * (c) 2019 by Mega Limited, Wellsford, New Zealand
   *
   * This file is part of the MEGA SDK - Client Access Engine.
   *
   * Applications using the MEGA API must present a valid application key
   * and comply with the the rules set forth in the Terms of Service.
   *
   * The MEGA SDK 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.
   *
   * @copyright Simplified (2-clause) BSD License.
   *
   * You should have received a copy of the license along with this
   * program.
   */
  #include <gtest/gtest.h>
  
  #include <mega/logging.h>
  
  #ifdef ENABLE_LOG_PERFORMANCE
  namespace {
  
  class MockLogger : public mega::Logger
  {
  public:
  
      MockLogger()
      {
          mega::SimpleLogger::logger = this;
      }
  
      ~MockLogger()
      {
          mega::SimpleLogger::logger = nullptr;
      }
  
      void log(const char *time, int loglevel, const char *source, const char *message) override
      {
          EXPECT_EQ(nullptr, time);
          EXPECT_EQ(nullptr, source);
          EXPECT_NE(nullptr, message);
          mLogLevel.insert(loglevel);
          mMessage.push_back(message);
      }
  
      void checkLogLevel(const int expLogLevel) const
      {
          EXPECT_EQ(1, mLogLevel.size());
          EXPECT_EQ(expLogLevel, *mLogLevel.begin());
      }
  
      std::vector<std::string> mMessage;
  
  private:
      std::set<int> mLogLevel;
  };
  
  std::string expMsg(const std::string& file, const int line, const std::string& message)
  {
      return file + ":" + std::to_string(line) + " " + message;
  }
  
  }
  
  TEST(Logging, performanceMode_forStdString)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          const std::string message = "some message";
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message;
          logger.checkLogLevel(level);
          ASSERT_EQ(1, logger.mMessage.size());
          ASSERT_EQ(expMsg(file, line, message), logger.mMessage[0]);
      }
  }
  
  TEST(Logging, performanceMode_forCString)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          const std::string message = "some message";
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message.c_str();
          logger.checkLogLevel(level);
          ASSERT_EQ(1, logger.mMessage.size());
          ASSERT_EQ(expMsg(file, line, message), logger.mMessage[0]);
      }
  }
  
  TEST(Logging, performanceMode_forEnum)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          const auto obj = mega::LogLevel::logDebug;
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << obj;
          logger.checkLogLevel(level);
          ASSERT_EQ(1, logger.mMessage.size());
          ASSERT_EQ(expMsg(file, line, "4"), logger.mMessage[0]);
      }
  }
  
  TEST(Logging, performanceMode_forPointer)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          const double obj = 42;
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << &obj;
          logger.checkLogLevel(level);
          ASSERT_EQ(1, logger.mMessage.size());
          ASSERT_GE(logger.mMessage[0].size(), file.size() + 5); // 5 = ':13 ' plus null terminator
      }
  }
  
  TEST(Logging, performanceMode_forNullPointer)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          const double* obj = nullptr;
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << obj;
          logger.checkLogLevel(level);
          ASSERT_EQ(1, logger.mMessage.size());
          ASSERT_EQ(expMsg(file, line, "(NULL)"), logger.mMessage[0]);
      }
  }
  
  namespace {
  
  template<typename Type>
  void test_forIntegerNumber(const Type number)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << number;
          logger.checkLogLevel(level);
          EXPECT_EQ(1, logger.mMessage.size());
          std::ostringstream expected;
          expected << number;
          EXPECT_EQ(expMsg(file, line, expected.str()), logger.mMessage[0]);
      }
  }
  
  template<typename Type>
  void test_forFloatingNumber(const Type number)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << number;
          logger.checkLogLevel(level);
          EXPECT_EQ(1, logger.mMessage.size());
          std::ostringstream expected;
          expected << number;
          const auto msg = expMsg(file, line, expected.str());
          EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos);
      }
  }
  
  }
  
  TEST(Logging, performanceMode_forInt)
  {
      test_forIntegerNumber<int>(0);
      test_forIntegerNumber<int>(42);
      test_forIntegerNumber<int>(-42);
      test_forIntegerNumber<int>(std::numeric_limits<int>::lowest());
      test_forIntegerNumber<int>(std::numeric_limits<int>::max());
  }
  
  TEST(Logging, performanceMode_forLong)
  {
      test_forIntegerNumber<long>(0);
      test_forIntegerNumber<long>(42);
      test_forIntegerNumber<long>(-42);
      test_forIntegerNumber<long>(std::numeric_limits<long>::lowest());
      test_forIntegerNumber<long>(std::numeric_limits<long>::max());
  }
  
  TEST(Logging, performanceMode_forLongLong)
  {
      test_forIntegerNumber<long long>(0);
      test_forIntegerNumber<long long>(42);
      test_forIntegerNumber<long long>(-42);
      test_forIntegerNumber<long long>(std::numeric_limits<long long>::lowest());
      test_forIntegerNumber<long long>(std::numeric_limits<long long>::max());
  }
  
  TEST(Logging, performanceMode_forUnsignedInt)
  {
      test_forIntegerNumber<unsigned int>(0);
      test_forIntegerNumber<unsigned int>(42);
      test_forIntegerNumber<unsigned int>(std::numeric_limits<unsigned int>::lowest());
      test_forIntegerNumber<unsigned int>(std::numeric_limits<unsigned int>::max());
  }
  
  TEST(Logging, performanceMode_forUnsignedLong)
  {
      test_forIntegerNumber<unsigned long>(0);
      test_forIntegerNumber<unsigned long>(42);
      test_forIntegerNumber<unsigned long>(std::numeric_limits<unsigned long>::lowest());
      test_forIntegerNumber<unsigned long>(std::numeric_limits<unsigned long>::max());
  }
  
  TEST(Logging, performanceMode_forUnsignedLongLong)
  {
      test_forIntegerNumber<unsigned long long>(0);
      test_forIntegerNumber<unsigned long long>(42);
      test_forIntegerNumber<unsigned long long>(std::numeric_limits<unsigned long long>::lowest());
      test_forIntegerNumber<unsigned long long>(std::numeric_limits<unsigned long long>::max());
  }
  
  TEST(Logging, performanceMode_forFloat)
  {
      test_forFloatingNumber<float>(0.f);
      test_forFloatingNumber<float>(42.123f);
      test_forFloatingNumber<float>(-42.123f);
      test_forFloatingNumber<float>(std::numeric_limits<float>::lowest());
      test_forFloatingNumber<float>(std::numeric_limits<float>::min());
      test_forFloatingNumber<float>(std::numeric_limits<float>::max());
  }
  
  TEST(Logging, performanceMode_forDouble)
  {
      test_forFloatingNumber<double>(0.);
      test_forFloatingNumber<double>(42.123);
      test_forFloatingNumber<double>(-42.123);
      test_forFloatingNumber<double>(std::numeric_limits<double>::lowest());
      test_forFloatingNumber<double>(std::numeric_limits<double>::min());
      test_forFloatingNumber<double>(std::numeric_limits<double>::max());
  }
  
  TEST(Logging, performanceMode_withMessageLargeThanLogBuffer)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          const std::string firstMessage(256 - file.size() - 5, 'X'); // 5 = ':13 ' plus null terminator
          const std::string secondMessage = "yay";
          const std::string message = firstMessage + secondMessage;
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message;
          logger.checkLogLevel(level);
          ASSERT_EQ(2, logger.mMessage.size());
          ASSERT_EQ(expMsg(file, line, firstMessage), logger.mMessage[0]);
          ASSERT_EQ(secondMessage, logger.mMessage[1]);
      }
  }
  
  TEST(Logging, performanceMode_withHugeMessage)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          const std::string file = "file.cpp";
          const int line = 13;
          const std::string message(5000, 'X');
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message;
          logger.checkLogLevel(level);
          const size_t fullMsgCount = 5013 / 255;
          ASSERT_EQ(fullMsgCount + 1, logger.mMessage.size());
          ASSERT_EQ(5013 % 255 - 1, logger.mMessage.back().size());
      }
  }
  
  TEST(Logging, performanceMode_withHugeMessage_butNoLogger)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          mega::SimpleLogger::logger = nullptr;
          const std::string file = "file.cpp";
          const int line = 13;
          const std::string message(5000, 'X');
          mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message;
          // ensure no crash or other funny business
      }
  }
  
  #else
  
  namespace {
  
  class MockLogger : public mega::Logger
  {
  public:
  
      MockLogger()
      {
          mega::SimpleLogger::logger = this;
      }
  
      ~MockLogger()
      {
          mega::SimpleLogger::logger = nullptr;
      }
  
      void log(const char *time, int loglevel, const char *source, const char *message) override
      {
          EXPECT_NE(nullptr, time);
          EXPECT_NE(nullptr, source);
          EXPECT_NE(nullptr, message);
          mLogLevel.insert(loglevel);
          mMessage.push_back(message);
      }
  
      void checkLogLevel(const int expLogLevel) const
      {
          EXPECT_EQ(1, mLogLevel.size());
          EXPECT_EQ(expLogLevel, *mLogLevel.begin());
      }
  
      std::vector<std::string> mMessage;
  
  private:
      std::set<int> mLogLevel;
  };
  
  }
  
  #endif
  
  TEST(Logging, toStr)
  {
      ASSERT_EQ(0, std::strcmp("verbose", mega::SimpleLogger::toStr(mega::LogLevel::logMax)));
      ASSERT_EQ(0, std::strcmp("debug", mega::SimpleLogger::toStr(mega::LogLevel::logDebug)));
      ASSERT_EQ(0, std::strcmp("info", mega::SimpleLogger::toStr(mega::LogLevel::logInfo)));
      ASSERT_EQ(0, std::strcmp("warn", mega::SimpleLogger::toStr(mega::LogLevel::logWarning)));
      ASSERT_EQ(0, std::strcmp("err", mega::SimpleLogger::toStr(mega::LogLevel::logError)));
      ASSERT_EQ(0, std::strcmp("FATAL", mega::SimpleLogger::toStr(mega::LogLevel::logFatal)));
  }
  
  #ifdef NDEBUG
  TEST(Logging, toStr_withBadLogLevel)
  {
      ASSERT_EQ(0, std::strcmp("", mega::SimpleLogger::toStr(static_cast<mega::LogLevel>(42))));
  }
  #endif
  
  TEST(Logging, macroVerbose)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level));
          const std::string msg = "foobar";
          LOG_verbose << msg;
          const auto currentLevel = mega::LogLevel::logMax;
          if (level >= currentLevel)
          {
              logger.checkLogLevel(currentLevel);
              ASSERT_EQ(1, logger.mMessage.size());
              EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos);
          }
          else
          {
              ASSERT_EQ(0, logger.mMessage.size());
          }
      }
  }
  
  TEST(Logging, macroDebug)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level));
          const std::string msg = "foobar";
          LOG_debug << msg;
          const auto currentLevel = mega::LogLevel::logDebug;
          if (level >= currentLevel)
          {
              logger.checkLogLevel(currentLevel);
              ASSERT_EQ(1, logger.mMessage.size());
              EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos);
          }
          else
          {
              ASSERT_EQ(0, logger.mMessage.size());
          }
      }
  }
  
  TEST(Logging, macroInfo)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level));
          const std::string msg = "foobar";
          LOG_info << msg;
          const auto currentLevel = mega::LogLevel::logInfo;
          if (level >= currentLevel)
          {
              logger.checkLogLevel(currentLevel);
              ASSERT_EQ(1, logger.mMessage.size());
              EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos);
          }
          else
          {
              ASSERT_EQ(0, logger.mMessage.size());
          }
      }
  }
  
  TEST(Logging, macroWarn)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level));
          const std::string msg = "foobar";
          LOG_warn << msg;
          const auto currentLevel = mega::LogLevel::logWarning;
          if (level >= currentLevel)
          {
              logger.checkLogLevel(currentLevel);
              ASSERT_EQ(1, logger.mMessage.size());
              EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos);
          }
          else
          {
              ASSERT_EQ(0, logger.mMessage.size());
          }
      }
  }
  
  TEST(Logging, macroErr)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level));
          const std::string msg = "foobar";
          LOG_err << msg;
          const auto currentLevel = mega::LogLevel::logError;
          if (level >= currentLevel)
          {
              logger.checkLogLevel(currentLevel);
              ASSERT_EQ(1, logger.mMessage.size());
              EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos);
          }
          else
          {
              ASSERT_EQ(0, logger.mMessage.size());
          }
      }
  }
  
  TEST(Logging, macroFatal)
  {
      for (int level = 0; level <= mega::LogLevel::logMax; ++level)
      {
          MockLogger logger;
          mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level));
          const std::string msg = "foobar";
          LOG_fatal << msg;
          logger.checkLogLevel(mega::LogLevel::logFatal);
          ASSERT_EQ(1, logger.mMessage.size());
          EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos);
      }
  }
  