// Copyright 2014 The Crashpad Authors
//
// 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 "util/posix/symbolic_constants_posix.h"

#include <signal.h>
#include <sys/types.h>

#include <iterator>

#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"

#define NUL_TEST_DATA(string) \
  { string, std::size(string) - 1 }

namespace crashpad {
namespace test {
namespace {

constexpr struct {
  int signal;
  const char* full_name;
  const char* short_name;
} kSignalTestData[] = {
    {SIGABRT, "SIGABRT", "ABRT"},    {SIGALRM, "SIGALRM", "ALRM"},
    {SIGBUS, "SIGBUS", "BUS"},       {SIGCHLD, "SIGCHLD", "CHLD"},
    {SIGCONT, "SIGCONT", "CONT"},    {SIGFPE, "SIGFPE", "FPE"},
    {SIGHUP, "SIGHUP", "HUP"},       {SIGILL, "SIGILL", "ILL"},
    {SIGINT, "SIGINT", "INT"},       {SIGIO, "SIGIO", "IO"},
    {SIGKILL, "SIGKILL", "KILL"},    {SIGPIPE, "SIGPIPE", "PIPE"},
    {SIGPROF, "SIGPROF", "PROF"},    {SIGQUIT, "SIGQUIT", "QUIT"},
    {SIGSEGV, "SIGSEGV", "SEGV"},    {SIGSTOP, "SIGSTOP", "STOP"},
    {SIGSYS, "SIGSYS", "SYS"},       {SIGTERM, "SIGTERM", "TERM"},
    {SIGTRAP, "SIGTRAP", "TRAP"},    {SIGTSTP, "SIGTSTP", "TSTP"},
    {SIGTTIN, "SIGTTIN", "TTIN"},    {SIGTTOU, "SIGTTOU", "TTOU"},
    {SIGURG, "SIGURG", "URG"},       {SIGUSR1, "SIGUSR1", "USR1"},
    {SIGUSR2, "SIGUSR2", "USR2"},    {SIGVTALRM, "SIGVTALRM", "VTALRM"},
    {SIGWINCH, "SIGWINCH", "WINCH"}, {SIGXCPU, "SIGXCPU", "XCPU"},
#if BUILDFLAG(IS_APPLE)
    {SIGEMT, "SIGEMT", "EMT"},       {SIGINFO, "SIGINFO", "INFO"},
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
    {SIGPWR, "SIGPWR", "PWR"},
#if !defined(ARCH_CPU_MIPS_FAMILY)
    {SIGSTKFLT, "SIGSTKFLT", "STKFLT"},
#endif
#endif
};

// If |expect| is nullptr, the conversion is expected to fail. If |expect| is
// empty, the conversion is expected to succeed, but the precise returned string
// value is unknown. Otherwise, the conversion is expected to succeed, and
// |expect| contains the precise expected string value to be returned.
//
// Only set kUseFullName or kUseShortName when calling this. Other options are
// exercised directly by this function.
void TestSignalToStringOnce(int value,
                            const char* expect,
                            SymbolicConstantToStringOptions options) {
  std::string actual = SignalToString(value, options | kUnknownIsEmpty);
  std::string actual_numeric =
      SignalToString(value, options | kUnknownIsNumeric);
  if (expect) {
    if (expect[0] == '\0') {
      EXPECT_FALSE(actual.empty()) << "signal " << value;
    } else {
      EXPECT_EQ(actual, expect) << "signal " << value;
    }
    EXPECT_EQ(actual_numeric, actual) << "signal " << value;
  } else {
    EXPECT_TRUE(actual.empty()) << "signal " << value << ", actual " << actual;
    EXPECT_FALSE(actual_numeric.empty())
        << "signal " << value << ", actual_numeric " << actual_numeric;
  }
}

void TestSignalToString(int value,
                        const char* expect_full,
                        const char* expect_short) {
  {
    SCOPED_TRACE("full_name");
    TestSignalToStringOnce(value, expect_full, kUseFullName);
  }

  {
    SCOPED_TRACE("short_name");
    TestSignalToStringOnce(value, expect_short, kUseShortName);
  }
}

TEST(SymbolicConstantsPOSIX, SignalToString) {
  for (size_t index = 0; index < std::size(kSignalTestData); ++index) {
    SCOPED_TRACE(base::StringPrintf("index %zu", index));
    TestSignalToString(kSignalTestData[index].signal,
                       kSignalTestData[index].full_name,
                       kSignalTestData[index].short_name);
  }

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
  // NSIG is 64 to account for real-time signals.
  constexpr int kSignalCount = 32;
#else
  constexpr int kSignalCount = NSIG;
#endif

  for (int signal = 0; signal < kSignalCount + 8; ++signal) {
    SCOPED_TRACE(base::StringPrintf("signal %d", signal));
    if (signal > 0 && signal < kSignalCount) {
      TestSignalToString(signal, "", "");
    } else {
      TestSignalToString(signal, nullptr, nullptr);
    }
  }
}

void TestStringToSignal(const base::StringPiece& string,
                        StringToSymbolicConstantOptions options,
                        bool expect_result,
                        int expect_value) {
  int actual_value;
  bool actual_result = StringToSignal(string, options, &actual_value);
  if (expect_result) {
    EXPECT_TRUE(actual_result) << "string " << string << ", options " << options
                               << ", signal " << expect_value;
    if (actual_result) {
      EXPECT_EQ(actual_value, expect_value) << "string " << string
                                            << ", options " << options;
    }
  } else {
    EXPECT_FALSE(actual_result) << "string " << string << ", options "
                                << options << ", signal " << actual_value;
  }
}

TEST(SymbolicConstantsPOSIX, StringToSignal) {
  static constexpr StringToSymbolicConstantOptions kOptions[] = {
      0,
      kAllowFullName,
      kAllowShortName,
      kAllowFullName | kAllowShortName,
      kAllowNumber,
      kAllowFullName | kAllowNumber,
      kAllowShortName | kAllowNumber,
      kAllowFullName | kAllowShortName | kAllowNumber,
  };

  for (size_t option_index = 0; option_index < std::size(kOptions);
       ++option_index) {
    SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index));
    StringToSymbolicConstantOptions options = kOptions[option_index];
    for (size_t index = 0; index < std::size(kSignalTestData); ++index) {
      SCOPED_TRACE(base::StringPrintf("index %zu", index));
      int signal = kSignalTestData[index].signal;
      {
        SCOPED_TRACE("full_name");
        TestStringToSignal(kSignalTestData[index].full_name,
                           options,
                           options & kAllowFullName,
                           signal);
      }
      {
        SCOPED_TRACE("short_name");
        TestStringToSignal(kSignalTestData[index].short_name,
                           options,
                           options & kAllowShortName,
                           signal);
      }
      {
        SCOPED_TRACE("number");
        std::string number_string = base::StringPrintf("%d", signal);
        TestStringToSignal(
            number_string, options, options & kAllowNumber, signal);
      }
    }

    static constexpr const char* kNegativeTestData[] = {
        "SIGHUP ",
        " SIGINT",
        "QUIT ",
        " ILL",
        "SIGSIGTRAP",
        "SIGABRTRON",
        "FPES",
        "SIGGARBAGE",
        "random",
        "",
    };

    for (size_t index = 0; index < std::size(kNegativeTestData); ++index) {
      SCOPED_TRACE(base::StringPrintf("index %zu", index));
      TestStringToSignal(kNegativeTestData[index], options, false, 0);
    }

    static constexpr struct {
      const char* string;
      size_t length;
    } kNULTestData[] = {
        NUL_TEST_DATA("\0SIGBUS"),
        NUL_TEST_DATA("SIG\0BUS"),
        NUL_TEST_DATA("SIGB\0US"),
        NUL_TEST_DATA("SIGBUS\0"),
        NUL_TEST_DATA("\0BUS"),
        NUL_TEST_DATA("BUS\0"),
        NUL_TEST_DATA("B\0US"),
        NUL_TEST_DATA("\0002"),
        NUL_TEST_DATA("2\0"),
        NUL_TEST_DATA("1\0002"),
    };

    for (size_t index = 0; index < std::size(kNULTestData); ++index) {
      SCOPED_TRACE(base::StringPrintf("index %zu", index));
      base::StringPiece string(kNULTestData[index].string,
                               kNULTestData[index].length);
      TestStringToSignal(string, options, false, 0);
    }
  }

  // Ensure that a NUL is not required at the end of the string.
  {
    SCOPED_TRACE("trailing_NUL_full");
    TestStringToSignal(
        base::StringPiece("SIGBUST", 6), kAllowFullName, true, SIGBUS);
  }
  {
    SCOPED_TRACE("trailing_NUL_short");
    TestStringToSignal(
        base::StringPiece("BUST", 3), kAllowShortName, true, SIGBUS);
  }
}

}  // namespace
}  // namespace test
}  // namespace crashpad
