/* 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 <array>
#include <cstddef>
#include <cstdint>
#include <new>
#include <type_traits>
#include <utility>
#include <vector>

#include "ErrorList.h"
#include "gtest/gtest.h"
#include "mozilla/Assertions.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/SourcePathLiteral.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaTestParent.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "nsCOMPtr.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIFile.h"
#include "nsLiteralString.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsTLiteralString.h"

class nsISupports;

namespace mozilla::dom::quota {

namespace {

void CheckUnknownFileEntry(nsIFile& aBase, const nsAString& aName,
                           const bool aWarnIfFile, const bool aWarnIfDir) {
  nsCOMPtr<nsIFile> file;
  nsresult rv = aBase.Clone(getter_AddRefs(file));
  ASSERT_EQ(rv, NS_OK);

  rv = file->Append(aName);
  ASSERT_EQ(rv, NS_OK);

  rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
  ASSERT_EQ(rv, NS_OK);

  auto okOrErr = WARN_IF_FILE_IS_UNKNOWN(*file);
  ASSERT_TRUE(okOrErr.isOk());

#ifdef DEBUG
  EXPECT_TRUE(okOrErr.inspect() == aWarnIfFile);
#else
  EXPECT_TRUE(okOrErr.inspect() == false);
#endif

  rv = file->Remove(false);
  ASSERT_EQ(rv, NS_OK);

  rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
  ASSERT_EQ(rv, NS_OK);

  okOrErr = WARN_IF_FILE_IS_UNKNOWN(*file);
  ASSERT_TRUE(okOrErr.isOk());

#ifdef DEBUG
  EXPECT_TRUE(okOrErr.inspect() == aWarnIfDir);
#else
  EXPECT_TRUE(okOrErr.inspect() == false);
#endif

  rv = file->Remove(false);
  ASSERT_EQ(rv, NS_OK);
}

}  // namespace

TEST(QuotaCommon_WarnIfFileIsUnknown, Basics)
{
  nsCOMPtr<nsIFile> base;
  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base));
  ASSERT_EQ(rv, NS_OK);

  rv = base->Append(u"mozquotatests"_ns);
  ASSERT_EQ(rv, NS_OK);

  base->Remove(true);

  rv = base->Create(nsIFile::DIRECTORY_TYPE, 0700);
  ASSERT_EQ(rv, NS_OK);

  CheckUnknownFileEntry(*base, u"foo.bar"_ns, true, true);
  CheckUnknownFileEntry(*base, u".DS_Store"_ns, false, true);
  CheckUnknownFileEntry(*base, u".desktop"_ns, false, true);
  CheckUnknownFileEntry(*base, u"desktop.ini"_ns, false, true);
  CheckUnknownFileEntry(*base, u"DESKTOP.INI"_ns, false, true);
  CheckUnknownFileEntry(*base, u"thumbs.db"_ns, false, true);
  CheckUnknownFileEntry(*base, u"THUMBS.DB"_ns, false, true);
  CheckUnknownFileEntry(*base, u".xyz"_ns, false, true);

  rv = base->Remove(true);
  ASSERT_EQ(rv, NS_OK);
}

mozilla::ipc::IPCResult QuotaTestParent::RecvTry_Success_CustomErr_QmIpcFail(
    bool* aTryDidNotReturn) {
  QM_TRY(MOZ_TO_RESULT(NS_OK), QM_IPC_FAIL(this));

  *aTryDidNotReturn = true;

  return IPC_OK();
}

mozilla::ipc::IPCResult QuotaTestParent::RecvTry_Success_CustomErr_IpcFail(
    bool* aTryDidNotReturn) {
  QM_TRY(MOZ_TO_RESULT(NS_OK), IPC_FAIL(this, "Custom why"));

  *aTryDidNotReturn = true;

  return IPC_OK();
}

mozilla::ipc::IPCResult
QuotaTestParent::RecvTryInspect_Success_CustomErr_QmIpcFail(
    bool* aTryDidNotReturn) {
  QM_TRY_INSPECT(const auto& x, (mozilla::Result<int32_t, nsresult>{42}),
                 QM_IPC_FAIL(this));
  (void)x;

  *aTryDidNotReturn = true;

  return IPC_OK();
}

mozilla::ipc::IPCResult
QuotaTestParent::RecvTryInspect_Success_CustomErr_IpcFail(
    bool* aTryDidNotReturn) {
  QM_TRY_INSPECT(const auto& x, (mozilla::Result<int32_t, nsresult>{42}),
                 IPC_FAIL(this, "Custom why"));
  (void)x;

  *aTryDidNotReturn = true;

  return IPC_OK();
}

#ifdef __clang__
#  pragma clang diagnostic push
#  pragma clang diagnostic ignored "-Wunreachable-code"
#endif

TEST(QuotaCommon_Try, Success)
{
  bool tryDidNotReturn = false;

  nsresult rv = [&tryDidNotReturn]() -> nsresult {
    QM_TRY(MOZ_TO_RESULT(NS_OK));

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_Try, Success_CustomErr_QmIpcFail)
{
  auto foo = MakeRefPtr<QuotaTestParent>();

  bool tryDidNotReturn = false;

  auto res = foo->RecvTry_Success_CustomErr_QmIpcFail(&tryDidNotReturn);

  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_TRUE(res);
}

TEST(QuotaCommon_Try, Success_CustomErr_IpcFail)
{
  auto foo = MakeRefPtr<QuotaTestParent>();

  bool tryDidNotReturn = false;

  auto res = foo->RecvTry_Success_CustomErr_IpcFail(&tryDidNotReturn);

  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_TRUE(res);
}

#ifdef DEBUG
TEST(QuotaCommon_Try, Success_CustomErr_AssertUnreachable)
{
  bool tryDidNotReturn = false;

  nsresult rv = [&tryDidNotReturn]() -> nsresult {
    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_ASSERT_UNREACHABLE);

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_Try, Success_NoErr_AssertUnreachable)
{
  bool tryDidNotReturn = false;

  [&tryDidNotReturn]() -> void {
    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_ASSERT_UNREACHABLE_VOID);

    tryDidNotReturn = true;
  }();

  EXPECT_TRUE(tryDidNotReturn);
}
#else
#  if defined(QM_ASSERT_UNREACHABLE) || defined(QM_ASSERT_UNREACHABLE_VOID)
#error QM_ASSERT_UNREACHABLE and QM_ASSERT_UNREACHABLE_VOID should not be defined.
#  endif
#endif

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
TEST(QuotaCommon_Try, Success_CustomErr_DiagnosticAssertUnreachable)
{
  bool tryDidNotReturn = false;

  nsresult rv = [&tryDidNotReturn]() -> nsresult {
    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_DIAGNOSTIC_ASSERT_UNREACHABLE);

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_Try, Success_NoErr_DiagnosticAssertUnreachable)
{
  bool tryDidNotReturn = false;

  [&tryDidNotReturn]() -> void {
    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID);

    tryDidNotReturn = true;
  }();

  EXPECT_TRUE(tryDidNotReturn);
}
#else
#  if defined(QM_DIAGNOSTIC_ASSERT_UNREACHABLE) || \
      defined(QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID)
#error QM_DIAGNOSTIC_ASSERT_UNREACHABLE and QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID should not be defined.
#  endif
#endif

TEST(QuotaCommon_Try, Success_CustomErr_CustomLambda)
{
#define SUBTEST(...)                                                 \
  {                                                                  \
    bool tryDidNotReturn = false;                                    \
                                                                     \
    nsresult rv = [&tryDidNotReturn]() -> nsresult {                 \
      QM_TRY(MOZ_TO_RESULT(NS_OK), [](__VA_ARGS__) { return aRv; }); \
                                                                     \
      tryDidNotReturn = true;                                        \
                                                                     \
      return NS_OK;                                                  \
    }();                                                             \
                                                                     \
    EXPECT_TRUE(tryDidNotReturn);                                    \
    EXPECT_EQ(rv, NS_OK);                                            \
  }

  SUBTEST(const char*, nsresult aRv);
  SUBTEST(nsresult aRv);

#undef SUBTEST
}

TEST(QuotaCommon_Try, Success_WithCleanup)
{
  bool tryCleanupRan = false;
  bool tryDidNotReturn = false;

  nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult {
    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_PROPAGATE,
           [&tryCleanupRan](const auto&) { tryCleanupRan = true; });

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(tryCleanupRan);
  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_Try, Failure_PropagateErr)
{
  bool tryDidNotReturn = false;

  nsresult rv = [&tryDidNotReturn]() -> nsresult {
    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_Try, Failure_CustomErr)
{
  bool tryDidNotReturn = false;

  nsresult rv = [&tryDidNotReturn]() -> nsresult {
    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), NS_ERROR_UNEXPECTED);

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_Try, Failure_CustomErr_CustomLambda)
{
#define SUBTEST(...)                                           \
  {                                                            \
    bool tryDidNotReturn = false;                              \
                                                               \
    nsresult rv = [&tryDidNotReturn]() -> nsresult {           \
      QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE),                  \
             [](__VA_ARGS__) { return NS_ERROR_UNEXPECTED; }); \
                                                               \
      tryDidNotReturn = true;                                  \
                                                               \
      return NS_OK;                                            \
    }();                                                       \
                                                               \
    EXPECT_FALSE(tryDidNotReturn);                             \
    EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);                        \
  }

  SUBTEST(const char* aFunc, nsresult);
  SUBTEST(nsresult rv);

#undef SUBTEST
}

TEST(QuotaCommon_Try, Failure_NoErr)
{
  bool tryDidNotReturn = false;

  [&tryDidNotReturn]() -> void {
    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_VOID);

    tryDidNotReturn = true;
  }();

  EXPECT_FALSE(tryDidNotReturn);
}

TEST(QuotaCommon_Try, Failure_WithCleanup)
{
  bool tryCleanupRan = false;
  bool tryDidNotReturn = false;

  nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult {
    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_PROPAGATE,
           [&tryCleanupRan](const auto& result) {
             EXPECT_EQ(result, NS_ERROR_FAILURE);

             tryCleanupRan = true;
           });

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(tryCleanupRan);
  EXPECT_FALSE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_Try, Failure_WithCleanup_UnwrapErr)
{
  bool tryCleanupRan = false;
  bool tryDidNotReturn = false;

  nsresult rv;

  [&tryCleanupRan, &tryDidNotReturn](nsresult& aRv) -> void {
    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_VOID,
           ([&tryCleanupRan, &aRv](auto& result) {
             EXPECT_EQ(result, NS_ERROR_FAILURE);

             aRv = result;

             tryCleanupRan = true;
           }));

    tryDidNotReturn = true;

    aRv = NS_OK;
  }(rv);

  EXPECT_TRUE(tryCleanupRan);
  EXPECT_FALSE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_Try, Failure_WithCleanupAndPredicate)
{
  auto predicate = []() {
    static bool calledOnce = false;
    const bool result = !calledOnce;
    calledOnce = true;
    return result;
  };

  {
    bool tryDidNotReturn = false;

    nsresult rv = [&predicate, &tryDidNotReturn]() -> nsresult {
      QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_PROPAGATE, QM_NO_CLEANUP,
             predicate);

      tryDidNotReturn = true;

      return NS_OK;
    }();

    EXPECT_FALSE(tryDidNotReturn);
    EXPECT_EQ(rv, NS_ERROR_FAILURE);
  }

  {
    bool tryDidNotReturn = false;

    nsresult rv = [&predicate, &tryDidNotReturn]() -> nsresult {
      QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_PROPAGATE, QM_NO_CLEANUP,
             predicate);

      tryDidNotReturn = true;

      return NS_OK;
    }();

    EXPECT_FALSE(tryDidNotReturn);
    EXPECT_EQ(rv, NS_ERROR_FAILURE);
  }
}

TEST(QuotaCommon_Try, SameLine)
{
  // clang-format off
  QM_TRY(MOZ_TO_RESULT(NS_OK), QM_VOID); QM_TRY(MOZ_TO_RESULT(NS_OK), QM_VOID);
  // clang-format on
}

TEST(QuotaCommon_Try, NestingMadness_Success)
{
  bool nestedTryDidNotReturn = false;
  bool tryDidNotReturn = false;

  nsresult rv = [&nestedTryDidNotReturn, &tryDidNotReturn]() -> nsresult {
    QM_TRY(([&nestedTryDidNotReturn]() -> Result<Ok, nsresult> {
      QM_TRY(MOZ_TO_RESULT(NS_OK));

      nestedTryDidNotReturn = true;

      return Ok();
    }()));

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(nestedTryDidNotReturn);
  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_Try, NestingMadness_Failure)
{
  bool nestedTryDidNotReturn = false;
  bool tryDidNotReturn = false;

  nsresult rv = [&nestedTryDidNotReturn, &tryDidNotReturn]() -> nsresult {
    QM_TRY(([&nestedTryDidNotReturn]() -> Result<Ok, nsresult> {
      QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));

      nestedTryDidNotReturn = true;

      return Ok();
    }()));

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(nestedTryDidNotReturn);
  EXPECT_FALSE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_Try, NestingMadness_Multiple_Success)
{
  bool nestedTry1DidNotReturn = false;
  bool nestedTry2DidNotReturn = false;
  bool tryDidNotReturn = false;

  nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
                 &tryDidNotReturn]() -> nsresult {
    QM_TRY(([&nestedTry1DidNotReturn,
             &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
      QM_TRY(MOZ_TO_RESULT(NS_OK));

      nestedTry1DidNotReturn = true;

      QM_TRY(MOZ_TO_RESULT(NS_OK));

      nestedTry2DidNotReturn = true;

      return Ok();
    }()));

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(nestedTry1DidNotReturn);
  EXPECT_TRUE(nestedTry2DidNotReturn);
  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_Try, NestingMadness_Multiple_Failure1)
{
  bool nestedTry1DidNotReturn = false;
  bool nestedTry2DidNotReturn = false;
  bool tryDidNotReturn = false;

  nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
                 &tryDidNotReturn]() -> nsresult {
    QM_TRY(([&nestedTry1DidNotReturn,
             &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
      QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));

      nestedTry1DidNotReturn = true;

      QM_TRY(MOZ_TO_RESULT(NS_OK));

      nestedTry2DidNotReturn = true;

      return Ok();
    }()));

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(nestedTry1DidNotReturn);
  EXPECT_FALSE(nestedTry2DidNotReturn);
  EXPECT_FALSE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_Try, NestingMadness_Multiple_Failure2)
{
  bool nestedTry1DidNotReturn = false;
  bool nestedTry2DidNotReturn = false;
  bool tryDidNotReturn = false;

  nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
                 &tryDidNotReturn]() -> nsresult {
    QM_TRY(([&nestedTry1DidNotReturn,
             &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
      QM_TRY(MOZ_TO_RESULT(NS_OK));

      nestedTry1DidNotReturn = true;

      QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));

      nestedTry2DidNotReturn = true;

      return Ok();
    }()));

    tryDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(nestedTry1DidNotReturn);
  EXPECT_FALSE(nestedTry2DidNotReturn);
  EXPECT_FALSE(tryDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryInspect, Success)
{
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
    EXPECT_EQ(x, 42);

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_TryInspect, Success_CustomErr_QmIpcFail)
{
  auto foo = MakeRefPtr<QuotaTestParent>();

  bool tryDidNotReturn = false;

  auto res = foo->RecvTryInspect_Success_CustomErr_QmIpcFail(&tryDidNotReturn);

  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_TRUE(res);
}

TEST(QuotaCommon_TryInspect, Success_CustomErr_IpcFail)
{
  auto foo = MakeRefPtr<QuotaTestParent>();

  bool tryDidNotReturn = false;

  auto res = foo->RecvTryInspect_Success_CustomErr_IpcFail(&tryDidNotReturn);

  EXPECT_TRUE(tryDidNotReturn);
  EXPECT_TRUE(res);
}

#ifdef DEBUG
TEST(QuotaCommon_TryInspect, Success_CustomErr_AssertUnreachable)
{
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}),
                   QM_ASSERT_UNREACHABLE);
    EXPECT_EQ(x, 42);

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_TryInspect, Success_NoErr_AssertUnreachable)
{
  bool tryInspectDidNotReturn = false;

  [&tryInspectDidNotReturn]() -> void {
    QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}),
                   QM_ASSERT_UNREACHABLE_VOID);
    EXPECT_EQ(x, 42);

    tryInspectDidNotReturn = true;
  }();

  EXPECT_TRUE(tryInspectDidNotReturn);
}
#endif

TEST(QuotaCommon_TryInspect, Success_CustomErr_CustomLambda)
{
#define SUBTEST(...)                                                 \
  {                                                                  \
    bool tryInspectDidNotReturn = false;                             \
                                                                     \
    nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {          \
      QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}), \
                     [](__VA_ARGS__) { return aRv; });               \
      EXPECT_EQ(x, 42);                                              \
                                                                     \
      tryInspectDidNotReturn = true;                                 \
                                                                     \
      return NS_OK;                                                  \
    }();                                                             \
                                                                     \
    EXPECT_TRUE(tryInspectDidNotReturn);                             \
    EXPECT_EQ(rv, NS_OK);                                            \
  }

  SUBTEST(const char*, nsresult aRv);
  SUBTEST(nsresult aRv);

#undef SUBTEST
}

TEST(QuotaCommon_TryInspect, Success_WithCleanup)
{
  bool tryInspectCleanupRan = false;
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&tryInspectCleanupRan, &tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(
        const auto& x, (Result<int32_t, nsresult>{42}), QM_PROPAGATE,
        [&tryInspectCleanupRan](const auto&) { tryInspectCleanupRan = true; });
    EXPECT_EQ(x, 42);

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(tryInspectCleanupRan);
  EXPECT_TRUE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_TryInspect, Failure_PropagateErr)
{
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(const auto& x,
                   (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
    (void)x;

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryInspect, Failure_CustomErr)
{
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(const auto& x,
                   (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
                   NS_ERROR_UNEXPECTED);
    (void)x;

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_TryInspect, Failure_CustomErr_CustomLambda)
{
#define SUBTEST(...)                                                     \
  {                                                                      \
    bool tryInspectDidNotReturn = false;                                 \
                                                                         \
    nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {              \
      QM_TRY_INSPECT(const auto& x,                                      \
                     (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), \
                     [](__VA_ARGS__) { return NS_ERROR_UNEXPECTED; });   \
      (void)x;                                                           \
                                                                         \
      tryInspectDidNotReturn = true;                                     \
                                                                         \
      return NS_OK;                                                      \
    }();                                                                 \
                                                                         \
    EXPECT_FALSE(tryInspectDidNotReturn);                                \
    EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);                                  \
  }

  SUBTEST(const char*, nsresult);
  SUBTEST(nsresult);

#undef SUBTEST
}

TEST(QuotaCommon_TryInspect, Failure_NoErr)
{
  bool tryInspectDidNotReturn = false;

  [&tryInspectDidNotReturn]() -> void {
    QM_TRY_INSPECT(const auto& x,
                   (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), QM_VOID);
    (void)x;

    tryInspectDidNotReturn = true;
  }();

  EXPECT_FALSE(tryInspectDidNotReturn);
}

TEST(QuotaCommon_TryInspect, Failure_WithCleanup)
{
  bool tryInspectCleanupRan = false;
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&tryInspectCleanupRan, &tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(const auto& x,
                   (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
                   QM_PROPAGATE, [&tryInspectCleanupRan](const auto& result) {
                     EXPECT_EQ(result, NS_ERROR_FAILURE);

                     tryInspectCleanupRan = true;
                   });
    (void)x;

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(tryInspectCleanupRan);
  EXPECT_FALSE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryInspect, Failure_WithCleanup_UnwrapErr)
{
  bool tryInspectCleanupRan = false;
  bool tryInspectDidNotReturn = false;

  nsresult rv;

  [&tryInspectCleanupRan, &tryInspectDidNotReturn](nsresult& aRv) -> void {
    QM_TRY_INSPECT(const auto& x,
                   (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), QM_VOID,
                   ([&tryInspectCleanupRan, &aRv](auto& result) {
                     EXPECT_EQ(result, NS_ERROR_FAILURE);

                     aRv = result;

                     tryInspectCleanupRan = true;
                   }));
    (void)x;

    tryInspectDidNotReturn = true;

    aRv = NS_OK;
  }(rv);

  EXPECT_TRUE(tryInspectCleanupRan);
  EXPECT_FALSE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryInspect, ConstDecl)
{
  QM_TRY_INSPECT(const int32_t& x, (Result<int32_t, nsresult>{42}), QM_VOID);

  static_assert(std::is_same_v<decltype(x), const int32_t&>);

  EXPECT_EQ(x, 42);
}

TEST(QuotaCommon_TryInspect, SameScopeDecl)
{
  QM_TRY_INSPECT(const int32_t& x, (Result<int32_t, nsresult>{42}), QM_VOID);
  EXPECT_EQ(x, 42);

  QM_TRY_INSPECT(const int32_t& y, (Result<int32_t, nsresult>{42}), QM_VOID);
  EXPECT_EQ(y, 42);
}

TEST(QuotaCommon_TryInspect, SameLine)
{
  // clang-format off
  QM_TRY_INSPECT(const auto &x, (Result<int32_t, nsresult>{42}), QM_VOID); QM_TRY_INSPECT(const auto &y, (Result<int32_t, nsresult>{42}), QM_VOID);
  // clang-format on

  EXPECT_EQ(x, 42);
  EXPECT_EQ(y, 42);
}

TEST(QuotaCommon_TryInspect, NestingMadness_Success)
{
  bool nestedTryInspectDidNotReturn = false;
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&nestedTryInspectDidNotReturn,
                 &tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(
        const auto& x,
        ([&nestedTryInspectDidNotReturn]() -> Result<int32_t, nsresult> {
          QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));

          nestedTryInspectDidNotReturn = true;

          return x;
        }()));
    EXPECT_EQ(x, 42);

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(nestedTryInspectDidNotReturn);
  EXPECT_TRUE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_TryInspect, NestingMadness_Failure)
{
  bool nestedTryInspectDidNotReturn = false;
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&nestedTryInspectDidNotReturn,
                 &tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(
        const auto& x,
        ([&nestedTryInspectDidNotReturn]() -> Result<int32_t, nsresult> {
          QM_TRY_INSPECT(const auto& x,
                         (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));

          nestedTryInspectDidNotReturn = true;

          return x;
        }()));
    (void)x;

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(nestedTryInspectDidNotReturn);
  EXPECT_FALSE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Success)
{
  bool nestedTryInspect1DidNotReturn = false;
  bool nestedTryInspect2DidNotReturn = false;
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
                 &tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(
        const auto& z,
        ([&nestedTryInspect1DidNotReturn,
          &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
          QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));

          nestedTryInspect1DidNotReturn = true;

          QM_TRY_INSPECT(const auto& y, (Result<int32_t, nsresult>{42}));

          nestedTryInspect2DidNotReturn = true;

          return x + y;
        }()));
    EXPECT_EQ(z, 84);

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(nestedTryInspect1DidNotReturn);
  EXPECT_TRUE(nestedTryInspect2DidNotReturn);
  EXPECT_TRUE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_OK);
}

TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Failure1)
{
  bool nestedTryInspect1DidNotReturn = false;
  bool nestedTryInspect2DidNotReturn = false;
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
                 &tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(
        const auto& z,
        ([&nestedTryInspect1DidNotReturn,
          &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
          QM_TRY_INSPECT(const auto& x,
                         (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));

          nestedTryInspect1DidNotReturn = true;

          QM_TRY_INSPECT(const auto& y, (Result<int32_t, nsresult>{42}));

          nestedTryInspect2DidNotReturn = true;

          return x + y;
        }()));
    (void)z;

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(nestedTryInspect1DidNotReturn);
  EXPECT_FALSE(nestedTryInspect2DidNotReturn);
  EXPECT_FALSE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Failure2)
{
  bool nestedTryInspect1DidNotReturn = false;
  bool nestedTryInspect2DidNotReturn = false;
  bool tryInspectDidNotReturn = false;

  nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
                 &tryInspectDidNotReturn]() -> nsresult {
    QM_TRY_INSPECT(
        const auto& z,
        ([&nestedTryInspect1DidNotReturn,
          &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
          QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));

          nestedTryInspect1DidNotReturn = true;

          QM_TRY_INSPECT(const auto& y,
                         (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));

          nestedTryInspect2DidNotReturn = true;

          return x + y;
        }()));
    (void)z;

    tryInspectDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(nestedTryInspect1DidNotReturn);
  EXPECT_FALSE(nestedTryInspect2DidNotReturn);
  EXPECT_FALSE(tryInspectDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

// We are not repeating all QM_TRY_INSPECT test cases for QM_TRY_UNWRAP, since
// they are largely based on the same implementation. We just add some where
// inspecting and unwrapping differ.

TEST(QuotaCommon_TryUnwrap, NonConstDecl)
{
  QM_TRY_UNWRAP(int32_t x, (Result<int32_t, nsresult>{42}), QM_VOID);

  static_assert(std::is_same_v<decltype(x), int32_t>);

  EXPECT_EQ(x, 42);
}

TEST(QuotaCommon_TryUnwrap, RvalueDecl)
{
  QM_TRY_UNWRAP(int32_t&& x, (Result<int32_t, nsresult>{42}), QM_VOID);

  static_assert(std::is_same_v<decltype(x), int32_t&&>);

  EXPECT_EQ(x, 42);
}

TEST(QuotaCommon_TryUnwrap, ParenDecl)
{
  QM_TRY_UNWRAP(
      (auto&& [x, y]),
      (Result<std::pair<int32_t, bool>, nsresult>{std::pair{42, true}}),
      QM_VOID);

  static_assert(std::is_same_v<decltype(x), int32_t>);
  static_assert(std::is_same_v<decltype(y), bool>);

  EXPECT_EQ(x, 42);
  EXPECT_EQ(y, true);
}

TEST(QuotaCommon_TryReturn, Success)
{
  bool tryReturnDidNotReturn = false;

  auto res = [&tryReturnDidNotReturn] {
    QM_TRY_RETURN((Result<int32_t, nsresult>{42}));

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), 42);
}

TEST(QuotaCommon_TryReturn, Success_nsresult)
{
  bool tryReturnDidNotReturn = false;

  auto res = [&tryReturnDidNotReturn] {
    QM_TRY_RETURN(MOZ_TO_RESULT(NS_OK));

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isOk());
}

#ifdef DEBUG
TEST(QuotaCommon_TryReturn, Success_CustomErr_AssertUnreachable)
{
  bool tryReturnDidNotReturn = false;

  auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
    QM_TRY_RETURN((Result<int32_t, nsresult>{42}), QM_ASSERT_UNREACHABLE);

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), 42);
}
#endif

TEST(QuotaCommon_TryReturn, Success_CustomErr_CustomLambda)
{
#define SUBTEST(...)                                                \
  {                                                                 \
    bool tryReturnDidNotReturn = false;                             \
                                                                    \
    auto res = [&tryReturnDidNotReturn]() -> Result<Ok, nsresult> { \
      QM_TRY_RETURN(MOZ_TO_RESULT(NS_OK),                           \
                    [](__VA_ARGS__) { return Err(aRv); });          \
                                                                    \
      tryReturnDidNotReturn = true;                                 \
    }();                                                            \
                                                                    \
    EXPECT_FALSE(tryReturnDidNotReturn);                            \
    EXPECT_TRUE(res.isOk());                                        \
  }

  SUBTEST(const char*, nsresult aRv);
  SUBTEST(nsresult aRv);

#undef SUBTEST
}

TEST(QuotaCommon_TryReturn, Success_WithCleanup)
{
  bool tryReturnCleanupRan = false;
  bool tryReturnDidNotReturn = false;

  auto res = [&tryReturnCleanupRan,
              &tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
    QM_TRY_RETURN(
        (Result<int32_t, nsresult>{42}), QM_PROPAGATE,
        [&tryReturnCleanupRan](const auto&) { tryReturnCleanupRan = true; });

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(tryReturnCleanupRan);
  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), 42);
}

TEST(QuotaCommon_TryReturn, Failure_PropagateErr)
{
  bool tryReturnDidNotReturn = false;

  auto res = [&tryReturnDidNotReturn] {
    QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryReturn, Failure_PropagateErr_nsresult)
{
  bool tryReturnDidNotReturn = false;

  auto res = [&tryReturnDidNotReturn] {
    QM_TRY_RETURN(MOZ_TO_RESULT(NS_ERROR_FAILURE));

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryReturn, Failure_CustomErr)
{
  bool tryReturnDidNotReturn = false;

  auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
    QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
                  Err(NS_ERROR_UNEXPECTED));

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_TryReturn, Failure_CustomErr_CustomLambda)
{
#define SUBTEST(...)                                                       \
  {                                                                        \
    bool tryReturnDidNotReturn = false;                                    \
                                                                           \
    auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {   \
      QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),    \
                    [](__VA_ARGS__) { return Err(NS_ERROR_UNEXPECTED); }); \
                                                                           \
      tryReturnDidNotReturn = true;                                        \
    }();                                                                   \
                                                                           \
    EXPECT_FALSE(tryReturnDidNotReturn);                                   \
    EXPECT_TRUE(res.isErr());                                              \
    EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);                       \
  }

  SUBTEST(const char*, nsresult);
  SUBTEST(nsresult);

#undef SUBTEST
}

TEST(QuotaCommon_TryReturn, Failure_WithCleanup)
{
  bool tryReturnCleanupRan = false;
  bool tryReturnDidNotReturn = false;

  auto res = [&tryReturnCleanupRan,
              &tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
    QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
                  QM_PROPAGATE, [&tryReturnCleanupRan](const auto& result) {
                    EXPECT_EQ(result, NS_ERROR_FAILURE);

                    tryReturnCleanupRan = true;
                  });

    tryReturnDidNotReturn = true;
  }();

  EXPECT_TRUE(tryReturnCleanupRan);
  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
}

TEST(QuotaCommon_TryReturn, SameLine)
{
  // clang-format off
  auto res1 = [] { QM_TRY_RETURN((Result<int32_t, nsresult>{42})); }(); auto res2 = []() -> Result<int32_t, nsresult> { QM_TRY_RETURN((Result<int32_t, nsresult>{42})); }();
  // clang-format on

  EXPECT_TRUE(res1.isOk());
  EXPECT_EQ(res1.unwrap(), 42);
  EXPECT_TRUE(res2.isOk());
  EXPECT_EQ(res2.unwrap(), 42);
}

TEST(QuotaCommon_TryReturn, NestingMadness_Success)
{
  bool nestedTryReturnDidNotReturn = false;
  bool tryReturnDidNotReturn = false;

  auto res = [&nestedTryReturnDidNotReturn, &tryReturnDidNotReturn] {
    QM_TRY_RETURN(([&nestedTryReturnDidNotReturn] {
      QM_TRY_RETURN((Result<int32_t, nsresult>{42}));

      nestedTryReturnDidNotReturn = true;
    }()));

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(nestedTryReturnDidNotReturn);
  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), 42);
}

TEST(QuotaCommon_TryReturn, NestingMadness_Failure)
{
  bool nestedTryReturnDidNotReturn = false;
  bool tryReturnDidNotReturn = false;

  auto res = [&nestedTryReturnDidNotReturn, &tryReturnDidNotReturn] {
    QM_TRY_RETURN(([&nestedTryReturnDidNotReturn] {
      QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));

      nestedTryReturnDidNotReturn = true;
    }()));

    tryReturnDidNotReturn = true;
  }();

  EXPECT_FALSE(nestedTryReturnDidNotReturn);
  EXPECT_FALSE(tryReturnDidNotReturn);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
}

TEST(QuotaCommon_Fail, ReturnValue)
{
  bool failDidNotReturn = false;

  nsresult rv = [&failDidNotReturn]() -> nsresult {
    QM_FAIL(NS_ERROR_FAILURE);

    failDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_FALSE(failDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_Fail, ReturnValue_WithCleanup)
{
  bool failCleanupRan = false;
  bool failDidNotReturn = false;

  nsresult rv = [&failCleanupRan, &failDidNotReturn]() -> nsresult {
    QM_FAIL(NS_ERROR_FAILURE, [&failCleanupRan]() { failCleanupRan = true; });

    failDidNotReturn = true;

    return NS_OK;
  }();

  EXPECT_TRUE(failCleanupRan);
  EXPECT_FALSE(failDidNotReturn);
  EXPECT_EQ(rv, NS_ERROR_FAILURE);
}

TEST(QuotaCommon_WarnOnlyTry, Success)
{
  bool warnOnlyTryDidNotReturn = false;

  const auto res =
      [&warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_WARNONLY_TRY(OkIf(true));

    warnOnlyTryDidNotReturn = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_TRUE(warnOnlyTryDidNotReturn);
}

TEST(QuotaCommon_WarnOnlyTry, Success_WithCleanup)
{
  bool warnOnlyTryCleanupRan = false;
  bool warnOnlyTryDidNotReturn = false;

  const auto res =
      [&warnOnlyTryCleanupRan,
       &warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_WARNONLY_TRY(OkIf(true), [&warnOnlyTryCleanupRan](const auto&) {
      warnOnlyTryCleanupRan = true;
    });

    warnOnlyTryDidNotReturn = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_FALSE(warnOnlyTryCleanupRan);
  EXPECT_TRUE(warnOnlyTryDidNotReturn);
}

TEST(QuotaCommon_WarnOnlyTry, Failure)
{
  bool warnOnlyTryDidNotReturn = false;

  const auto res =
      [&warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_WARNONLY_TRY(OkIf(false));

    warnOnlyTryDidNotReturn = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_TRUE(warnOnlyTryDidNotReturn);
}

TEST(QuotaCommon_WarnOnlyTry, Failure_WithCleanup)
{
  bool warnOnlyTryCleanupRan = false;
  bool warnOnlyTryDidNotReturn = false;

  const auto res =
      [&warnOnlyTryCleanupRan,
       &warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_WARNONLY_TRY(OkIf(false), ([&warnOnlyTryCleanupRan](const auto&) {
                      warnOnlyTryCleanupRan = true;
                    }));

    warnOnlyTryDidNotReturn = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_TRUE(warnOnlyTryCleanupRan);
  EXPECT_TRUE(warnOnlyTryDidNotReturn);
}

TEST(QuotaCommon_WarnOnlyTryUnwrap, Success)
{
  bool warnOnlyTryUnwrapDidNotReturn = false;

  const auto res = [&warnOnlyTryUnwrapDidNotReturn]()
      -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{42}));
    EXPECT_TRUE(x);
    EXPECT_EQ(*x, 42);

    warnOnlyTryUnwrapDidNotReturn = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
}

TEST(QuotaCommon_WarnOnlyTryUnwrap, Success_WithCleanup)
{
  bool warnOnlyTryUnwrapCleanupRan = false;
  bool warnOnlyTryUnwrapDidNotReturn = false;

  const auto res = [&warnOnlyTryUnwrapCleanupRan,
                    &warnOnlyTryUnwrapDidNotReturn]()
      -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{42}),
                           [&warnOnlyTryUnwrapCleanupRan](const auto&) {
                             warnOnlyTryUnwrapCleanupRan = true;
                           });
    EXPECT_TRUE(x);
    EXPECT_EQ(*x, 42);

    warnOnlyTryUnwrapDidNotReturn = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_FALSE(warnOnlyTryUnwrapCleanupRan);
  EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
}

TEST(QuotaCommon_WarnOnlyTryUnwrap, Failure)
{
  bool warnOnlyTryUnwrapDidNotReturn = false;

  const auto res = [&warnOnlyTryUnwrapDidNotReturn]()
      -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_WARNONLY_TRY_UNWRAP(const auto x,
                           (Result<int32_t, NotOk>{Err(NotOk{})}));
    EXPECT_FALSE(x);

    warnOnlyTryUnwrapDidNotReturn = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
}

TEST(QuotaCommon_WarnOnlyTryUnwrap, Failure_WithCleanup)
{
  bool warnOnlyTryUnwrapCleanupRan = false;
  bool warnOnlyTryUnwrapDidNotReturn = false;

  const auto res = [&warnOnlyTryUnwrapCleanupRan,
                    &warnOnlyTryUnwrapDidNotReturn]()
      -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{Err(NotOk{})}),
                           [&warnOnlyTryUnwrapCleanupRan](const auto&) {
                             warnOnlyTryUnwrapCleanupRan = true;
                           });
    EXPECT_FALSE(x);

    warnOnlyTryUnwrapDidNotReturn = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_TRUE(warnOnlyTryUnwrapCleanupRan);
  EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
}

TEST(QuotaCommon_OrElseWarn, Success)
{
  bool fallbackRun = false;
  bool tryContinued = false;

  const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_TRY(QM_OR_ELSE_WARN(OkIf(true), ([&fallbackRun](const NotOk) {
                             fallbackRun = true;
                             return mozilla::Result<mozilla::Ok, NotOk>{
                                 mozilla::Ok{}};
                           })));

    tryContinued = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_FALSE(fallbackRun);
  EXPECT_TRUE(tryContinued);
}

TEST(QuotaCommon_OrElseWarn, Failure_MappedToSuccess)
{
  bool fallbackRun = false;
  bool tryContinued = false;

  // XXX Consider allowing to set a custom error handler, so that we can
  // actually assert that a warning was emitted.
  const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_TRY(QM_OR_ELSE_WARN(OkIf(false), ([&fallbackRun](const NotOk) {
                             fallbackRun = true;
                             return mozilla::Result<mozilla::Ok, NotOk>{
                                 mozilla::Ok{}};
                           })));
    tryContinued = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_TRUE(fallbackRun);
  EXPECT_TRUE(tryContinued);
}

TEST(QuotaCommon_OrElseWarn, Failure_MappedToError)
{
  bool fallbackRun = false;
  bool tryContinued = false;

  // XXX Consider allowing to set a custom error handler, so that we can
  // actually assert that a warning was emitted.
  const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_TRY(QM_OR_ELSE_WARN(OkIf(false), ([&fallbackRun](const NotOk) {
                             fallbackRun = true;
                             return mozilla::Result<mozilla::Ok, NotOk>{
                                 NotOk{}};
                           })));
    tryContinued = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isErr());
  EXPECT_TRUE(fallbackRun);
  EXPECT_FALSE(tryContinued);
}

TEST(QuotaCommon_OrElseWarnIf, Success)
{
  bool predicateRun = false;
  bool fallbackRun = false;
  bool tryContinued = false;

  const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_TRY(QM_OR_ELSE_WARN_IF(
        OkIf(true),
        [&predicateRun](const NotOk) {
          predicateRun = true;
          return false;
        },
        ([&fallbackRun](const NotOk) {
          fallbackRun = true;
          return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
        })));

    tryContinued = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_FALSE(predicateRun);
  EXPECT_FALSE(fallbackRun);
  EXPECT_TRUE(tryContinued);
}

TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsFalse)
{
  bool predicateRun = false;
  bool fallbackRun = false;
  bool tryContinued = false;

  const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_TRY(QM_OR_ELSE_WARN_IF(
        OkIf(false),
        [&predicateRun](const NotOk) {
          predicateRun = true;
          return false;
        },
        ([&fallbackRun](const NotOk) {
          fallbackRun = true;
          return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
        })));

    tryContinued = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isErr());
  EXPECT_TRUE(predicateRun);
  EXPECT_FALSE(fallbackRun);
  EXPECT_FALSE(tryContinued);
}

TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsTrue_MappedToSuccess)
{
  bool predicateRun = false;
  bool fallbackRun = false;
  bool tryContinued = false;

  const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_TRY(QM_OR_ELSE_WARN_IF(
        OkIf(false),
        [&predicateRun](const NotOk) {
          predicateRun = true;
          return true;
        },
        ([&fallbackRun](const NotOk) {
          fallbackRun = true;
          return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
        })));

    tryContinued = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isOk());
  EXPECT_TRUE(predicateRun);
  EXPECT_TRUE(fallbackRun);
  EXPECT_TRUE(tryContinued);
}

TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsTrue_MappedToError)
{
  bool predicateRun = false;
  bool fallbackRun = false;
  bool tryContinued = false;

  const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
    QM_TRY(QM_OR_ELSE_WARN_IF(
        OkIf(false),
        [&predicateRun](const NotOk) {
          predicateRun = true;
          return true;
        },
        ([&fallbackRun](const NotOk) {
          fallbackRun = true;
          return mozilla::Result<mozilla::Ok, NotOk>{mozilla::NotOk{}};
        })));

    tryContinued = true;
    return mozilla::Ok{};
  }();

  EXPECT_TRUE(res.isErr());
  EXPECT_TRUE(predicateRun);
  EXPECT_TRUE(fallbackRun);
  EXPECT_FALSE(tryContinued);
}

TEST(QuotaCommon_OkIf, True)
{
  auto res = OkIf(true);

  EXPECT_TRUE(res.isOk());
}

TEST(QuotaCommon_OkIf, False)
{
  auto res = OkIf(false);

  EXPECT_TRUE(res.isErr());
}

TEST(QuotaCommon_OkToOk, Bool_True)
{
  auto res = OkToOk<true>(Ok());
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), true);
}

TEST(QuotaCommon_OkToOk, Bool_False)
{
  auto res = OkToOk<false>(Ok());
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), false);
}

TEST(QuotaCommon_OkToOk, Int_42)
{
  auto res = OkToOk<42>(Ok());
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), 42);
}

TEST(QuotaCommon_ErrToOkOrErr, Bool_True)
{
  auto res = ErrToOkOrErr<NS_ERROR_FAILURE, true>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), true);
}

TEST(QuotaCommon_ErrToOkOrErr, Bool_True_Err)
{
  auto res = ErrToOkOrErr<NS_ERROR_FAILURE, true>(NS_ERROR_UNEXPECTED);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_ErrToOkOrErr, Bool_False)
{
  auto res = ErrToOkOrErr<NS_ERROR_FAILURE, false>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), false);
}

TEST(QuotaCommon_ErrToOkOrErr, Bool_False_Err)
{
  auto res = ErrToOkOrErr<NS_ERROR_FAILURE, false>(NS_ERROR_UNEXPECTED);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_ErrToOkOrErr, Int_42)
{
  auto res = ErrToOkOrErr<NS_ERROR_FAILURE, 42>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), 42);
}

TEST(QuotaCommon_ErrToOkOrErr, Int_42_Err)
{
  auto res = ErrToOkOrErr<NS_ERROR_FAILURE, 42>(NS_ERROR_UNEXPECTED);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_ErrToOkOrErr, NsCOMPtr_nullptr)
{
  auto res = ErrToOkOrErr<NS_ERROR_FAILURE, nullptr, nsCOMPtr<nsISupports>>(
      NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), nullptr);
}

TEST(QuotaCommon_ErrToOkOrErr, NsCOMPtr_nullptr_Err)
{
  auto res = ErrToOkOrErr<NS_ERROR_FAILURE, nullptr, nsCOMPtr<nsISupports>>(
      NS_ERROR_UNEXPECTED);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_ErrToDefaultOkOrErr, Ok)
{
  auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, Ok>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
}

TEST(QuotaCommon_ErrToDefaultOkOrErr, Ok_Err)
{
  auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, Ok>(NS_ERROR_UNEXPECTED);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_ErrToDefaultOkOrErr, NsCOMPtr)
{
  auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, nsCOMPtr<nsISupports>>(
      NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), nullptr);
}

TEST(QuotaCommon_ErrToDefaultOkOrErr, NsCOMPtr_Err)
{
  auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, nsCOMPtr<nsISupports>>(
      NS_ERROR_UNEXPECTED);
  EXPECT_TRUE(res.isErr());
  EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
}

TEST(QuotaCommon_IsSpecificError, Match)
{
  EXPECT_TRUE(IsSpecificError<NS_ERROR_FAILURE>(NS_ERROR_FAILURE));
}

TEST(QuotaCommon_IsSpecificError, Mismatch)
{
  EXPECT_FALSE(IsSpecificError<NS_ERROR_FAILURE>(NS_ERROR_UNEXPECTED));
}

TEST(QuotaCommon_ErrToOk, Bool_True)
{
  auto res = ErrToOk<true>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), true);
}

TEST(QuotaCommon_ErrToOk, Bool_False)
{
  auto res = ErrToOk<false>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), false);
}

TEST(QuotaCommon_ErrToOk, Int_42)
{
  auto res = ErrToOk<42>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), 42);
}

TEST(QuotaCommon_ErrToOk, NsCOMPtr_nullptr)
{
  auto res = ErrToOk<nullptr, nsCOMPtr<nsISupports>>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), nullptr);
}

TEST(QuotaCommon_ErrToDefaultOk, Ok)
{
  auto res = ErrToDefaultOk<Ok>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
}

TEST(QuotaCommon_ErrToDefaultOk, NsCOMPtr)
{
  auto res = ErrToDefaultOk<nsCOMPtr<nsISupports>>(NS_ERROR_FAILURE);
  EXPECT_TRUE(res.isOk());
  EXPECT_EQ(res.unwrap(), nullptr);
}

class StringPairParameterized
    : public ::testing::TestWithParam<std::pair<const char*, const char*>> {};

TEST_P(StringPairParameterized, AnonymizedOriginString) {
  const auto [in, expectedAnonymized] = GetParam();
  const auto anonymized = AnonymizedOriginString(nsDependentCString(in));
  EXPECT_STREQ(anonymized.get(), expectedAnonymized);
}

INSTANTIATE_TEST_SUITE_P(
    QuotaCommon, StringPairParameterized,
    ::testing::Values(
        // XXX Do we really want to anonymize about: origins?
        std::pair("about:home", "about:aaaa"),
        std::pair("https://foo.bar.com", "https://aaa.aaa.aaa"),
        std::pair("https://foo.bar.com:8000", "https://aaa.aaa.aaa:DDDD"),
        std::pair("file://UNIVERSAL_FILE_ORIGIN",
                  "file://aaaaaaaaa_aaaa_aaaaaa")));

// BEGIN COPY FROM mfbt/tests/TestResult.cpp
struct Failed {};

static GenericErrorResult<Failed> Fail() { return Err(Failed()); }

static Result<Ok, Failed> Task1(bool pass) {
  if (!pass) {
    return Fail();  // implicit conversion from GenericErrorResult to Result
  }
  return Ok();
}
// END COPY FROM mfbt/tests/TestResult.cpp

static Result<bool, Failed> Condition(bool aNoError, bool aResult) {
  return Task1(aNoError).map([aResult](auto) { return aResult; });
}

TEST(QuotaCommon_CollectWhileTest, NoFailures)
{
  const size_t loopCount = 5;
  size_t conditionExecutions = 0;
  size_t bodyExecutions = 0;
  auto result = CollectWhile(
      [&conditionExecutions] {
        ++conditionExecutions;
        return Condition(true, conditionExecutions <= loopCount);
      },
      [&bodyExecutions] {
        ++bodyExecutions;
        return Task1(true);
      });
  static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
  MOZ_RELEASE_ASSERT(result.isOk());
  MOZ_RELEASE_ASSERT(loopCount == bodyExecutions);
  MOZ_RELEASE_ASSERT(1 + loopCount == conditionExecutions);
}

TEST(QuotaCommon_CollectWhileTest, BodyFailsImmediately)
{
  size_t conditionExecutions = 0;
  size_t bodyExecutions = 0;
  auto result = CollectWhile(
      [&conditionExecutions] {
        ++conditionExecutions;
        return Condition(true, true);
      },
      [&bodyExecutions] {
        ++bodyExecutions;
        return Task1(false);
      });
  static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
  MOZ_RELEASE_ASSERT(result.isErr());
  MOZ_RELEASE_ASSERT(1 == bodyExecutions);
  MOZ_RELEASE_ASSERT(1 == conditionExecutions);
}

TEST(QuotaCommon_CollectWhileTest, BodyFailsOnSecondExecution)
{
  size_t conditionExecutions = 0;
  size_t bodyExecutions = 0;
  auto result = CollectWhile(
      [&conditionExecutions] {
        ++conditionExecutions;
        return Condition(true, true);
      },
      [&bodyExecutions] {
        ++bodyExecutions;
        return Task1(bodyExecutions < 2);
      });
  static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
  MOZ_RELEASE_ASSERT(result.isErr());
  MOZ_RELEASE_ASSERT(2 == bodyExecutions);
  MOZ_RELEASE_ASSERT(2 == conditionExecutions);
}

TEST(QuotaCommon_CollectWhileTest, ConditionFailsImmediately)
{
  size_t conditionExecutions = 0;
  size_t bodyExecutions = 0;
  auto result = CollectWhile(
      [&conditionExecutions] {
        ++conditionExecutions;
        return Condition(false, true);
      },
      [&bodyExecutions] {
        ++bodyExecutions;
        return Task1(true);
      });
  static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
  MOZ_RELEASE_ASSERT(result.isErr());
  MOZ_RELEASE_ASSERT(0 == bodyExecutions);
  MOZ_RELEASE_ASSERT(1 == conditionExecutions);
}

TEST(QuotaCommon_CollectWhileTest, ConditionFailsOnSecondExecution)
{
  size_t conditionExecutions = 0;
  size_t bodyExecutions = 0;
  auto result = CollectWhile(
      [&conditionExecutions] {
        ++conditionExecutions;
        return Condition(conditionExecutions < 2, true);
      },
      [&bodyExecutions] {
        ++bodyExecutions;
        return Task1(true);
      });
  static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
  MOZ_RELEASE_ASSERT(result.isErr());
  MOZ_RELEASE_ASSERT(1 == bodyExecutions);
  MOZ_RELEASE_ASSERT(2 == conditionExecutions);
}

TEST(QuotaCommon_CollectEachInRange, Success)
{
  size_t bodyExecutions = 0;
  const auto result = CollectEachInRange(
      std::array<int, 5>{{1, 2, 3, 4, 5}},
      [&bodyExecutions](const int val) -> Result<Ok, nsresult> {
        ++bodyExecutions;
        return Ok{};
      });

  MOZ_RELEASE_ASSERT(result.isOk());
  MOZ_RELEASE_ASSERT(5 == bodyExecutions);
}

TEST(QuotaCommon_CollectEachInRange, FailureShortCircuit)
{
  size_t bodyExecutions = 0;
  const auto result = CollectEachInRange(
      std::array<int, 5>{{1, 2, 3, 4, 5}},
      [&bodyExecutions](const int val) -> Result<Ok, nsresult> {
        ++bodyExecutions;
        return val == 3 ? Err(NS_ERROR_FAILURE) : Result<Ok, nsresult>{Ok{}};
      });

  MOZ_RELEASE_ASSERT(result.isErr());
  MOZ_RELEASE_ASSERT(NS_ERROR_FAILURE == result.inspectErr());
  MOZ_RELEASE_ASSERT(3 == bodyExecutions);
}

TEST(QuotaCommon_ReduceEach, Success)
{
  const auto result = ReduceEach(
      [i = int{0}]() mutable -> Result<int, Failed> {
        if (i < 5) {
          return ++i;
        }
        return 0;
      },
      0, [](int val, int add) -> Result<int, Failed> { return val + add; });
  static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);

  MOZ_RELEASE_ASSERT(result.isOk());
  MOZ_RELEASE_ASSERT(15 == result.inspect());
}

TEST(QuotaCommon_ReduceEach, StepError)
{
  const auto result = ReduceEach(
      [i = int{0}]() mutable -> Result<int, Failed> {
        if (i < 5) {
          return ++i;
        }
        return 0;
      },
      0,
      [](int val, int add) -> Result<int, Failed> {
        if (val > 2) {
          return Err(Failed{});
        }
        return val + add;
      });
  static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);

  MOZ_RELEASE_ASSERT(result.isErr());
}

TEST(QuotaCommon_ReduceEach, GeneratorError)
{
  size_t generatorExecutions = 0;
  const auto result = ReduceEach(
      [i = int{0}, &generatorExecutions]() mutable -> Result<int, Failed> {
        ++generatorExecutions;
        if (i < 1) {
          return ++i;
        }
        return Err(Failed{});
      },
      0,
      [](int val, int add) -> Result<int, Failed> {
        if (val > 2) {
          return Err(Failed{});
        }
        return val + add;
      });
  static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);

  MOZ_RELEASE_ASSERT(result.isErr());
  MOZ_RELEASE_ASSERT(2 == generatorExecutions);
}

TEST(QuotaCommon_Reduce, Success)
{
  const auto range = std::vector{0, 1, 2, 3, 4, 5};
  const auto result = Reduce(
      range, 0, [](int val, Maybe<const int&> add) -> Result<int, Failed> {
        return val + add.ref();
      });
  static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);

  MOZ_RELEASE_ASSERT(result.isOk());
  MOZ_RELEASE_ASSERT(15 == result.inspect());
}

TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, NoFailures)
{
  uint32_t tries = 0;

  auto res = CallWithDelayedRetriesIfAccessDenied(
      [&tries]() -> Result<Ok, nsresult> {
        ++tries;
        return Ok{};
      },
      10, 2);

  EXPECT_EQ(tries, 1u);
  EXPECT_TRUE(res.isOk());
}

TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, PermanentFailures)
{
  uint32_t tries = 0;

  auto res = CallWithDelayedRetriesIfAccessDenied(
      [&tries]() -> Result<Ok, nsresult> {
        ++tries;
        return Err(NS_ERROR_FILE_IS_LOCKED);
      },
      10, 2);

  EXPECT_EQ(tries, 11u);
  EXPECT_TRUE(res.isErr());
}

TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, FailuresAndSuccess)
{
  uint32_t tries = 0;

  auto res = CallWithDelayedRetriesIfAccessDenied(
      [&tries]() -> Result<Ok, nsresult> {
        if (++tries == 5) {
          return Ok{};
        }
        return Err(NS_ERROR_FILE_ACCESS_DENIED);
      },
      10, 2);

  EXPECT_EQ(tries, 5u);
  EXPECT_TRUE(res.isOk());
}

TEST(QuotaCommon_MakeSourceFileRelativePath, ThisSourceFile)
{
  static constexpr auto thisSourceFileRelativePath =
      "dom/quota/test/gtest/TestQuotaCommon.cpp"_sp;

  const nsCString sourceFileRelativePath{
      mozilla::dom::quota::detail::MakeSourceFileRelativePath(
          nsLiteralCString(__FILE__))};

  EXPECT_STREQ(sourceFileRelativePath.get(), thisSourceFileRelativePath.get());
}

static nsCString MakeTreePath(const nsACString& aBasePath,
                              const nsACString& aRelativePath) {
  nsCString path{aBasePath};

  path.Append("/"_sp);
  path.Append(aRelativePath);

  return path;
}

static nsCString MakeSourceTreePath(const nsACString& aRelativePath) {
  return MakeTreePath(mozilla::dom::quota::detail::GetSourceTreeBase(),
                      aRelativePath);
}

static nsCString MakeObjdirDistIncludeTreePath(
    const nsACString& aRelativePath) {
  return MakeTreePath(
      mozilla::dom::quota::detail::GetObjdirDistIncludeTreeBase(),
      aRelativePath);
}

TEST(QuotaCommon_MakeSourceFileRelativePath, DomQuotaSourceFile)
{
  static constexpr auto domQuotaSourceFileRelativePath =
      "dom/quota/ActorsParent.cpp"_sp;

  const nsCString sourceFileRelativePath{
      mozilla::dom::quota::detail::MakeSourceFileRelativePath(
          MakeSourceTreePath(domQuotaSourceFileRelativePath))};

  EXPECT_STREQ(sourceFileRelativePath.get(),
               domQuotaSourceFileRelativePath.get());
}

TEST(QuotaCommon_MakeSourceFileRelativePath, DomQuotaSourceFile_Exported)
{
  static constexpr auto mozillaDomQuotaSourceFileRelativePath =
      "mozilla/dom/quota/QuotaCommon.h"_sp;

  static constexpr auto domQuotaSourceFileRelativePath =
      "dom/quota/QuotaCommon.h"_sp;

  const nsCString sourceFileRelativePath{
      mozilla::dom::quota::detail::MakeSourceFileRelativePath(
          MakeObjdirDistIncludeTreePath(
              mozillaDomQuotaSourceFileRelativePath))};

  EXPECT_STREQ(sourceFileRelativePath.get(),
               domQuotaSourceFileRelativePath.get());
}

TEST(QuotaCommon_MakeSourceFileRelativePath, DomIndexedDBSourceFile)
{
  static constexpr auto domIndexedDBSourceFileRelativePath =
      "dom/indexedDB/ActorsParent.cpp"_sp;

  const nsCString sourceFileRelativePath{
      mozilla::dom::quota::detail::MakeSourceFileRelativePath(
          MakeSourceTreePath(domIndexedDBSourceFileRelativePath))};

  EXPECT_STREQ(sourceFileRelativePath.get(),
               domIndexedDBSourceFileRelativePath.get());
}

TEST(QuotaCommon_MakeSourceFileRelativePath,
     DomLocalstorageSourceFile_Exported_Mapped)
{
  static constexpr auto mozillaDomSourceFileRelativePath =
      "mozilla/dom/LocalStorageCommon.h"_sp;

  static constexpr auto domLocalstorageSourceFileRelativePath =
      "dom/localstorage/LocalStorageCommon.h"_sp;

  const nsCString sourceFileRelativePath{
      mozilla::dom::quota::detail::MakeSourceFileRelativePath(
          MakeObjdirDistIncludeTreePath(mozillaDomSourceFileRelativePath))};

  EXPECT_STREQ(sourceFileRelativePath.get(),
               domLocalstorageSourceFileRelativePath.get());
}

TEST(QuotaCommon_MakeSourceFileRelativePath, NonDomSourceFile)
{
  static constexpr auto nonDomSourceFileRelativePath =
      "storage/mozStorageService.cpp"_sp;

  const nsCString sourceFileRelativePath{
      mozilla::dom::quota::detail::MakeSourceFileRelativePath(
          MakeSourceTreePath(nonDomSourceFileRelativePath))};

  EXPECT_STREQ(sourceFileRelativePath.get(),
               nonDomSourceFileRelativePath.get());
}

TEST(QuotaCommon_MakeSourceFileRelativePath, OtherSourceFile)
{
  constexpr auto otherSourceFilePath = "/foo/bar/Test.cpp"_sp;
  const nsCString sourceFileRelativePath{
      mozilla::dom::quota::detail::MakeSourceFileRelativePath(
          otherSourceFilePath)};

  EXPECT_STREQ(sourceFileRelativePath.get(), "Test.cpp");
}

#ifdef __clang__
#  pragma clang diagnostic pop
#endif

}  // namespace mozilla::dom::quota
