Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/mozglue/tests/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 188 kB image not shown  

Quelle  TestBaseProfiler.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "BaseProfiler.h"

#include "mozilla/Attributes.h"
#include "mozilla/BaseAndGeckoProfilerDetail.h"
#include "mozilla/BaseProfileJSONWriter.h"
#include "mozilla/BaseProfilerDetail.h"
#include "mozilla/FailureLatch.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/NotNull.h"
#include "mozilla/ProgressLogger.h"
#include "mozilla/ProportionValue.h"

#ifdef MOZ_GECKO_PROFILER
#  include "mozilla/BaseProfilerMarkerTypes.h"
#  include "mozilla/leb128iterator.h"
#  include "mozilla/ModuloBuffer.h"
#  include "mozilla/mozalloc.h"
#  include "mozilla/PowerOfTwo.h"
#  include "mozilla/ProfileBufferChunk.h"
#  include "mozilla/ProfileBufferChunkManagerSingle.h"
#  include "mozilla/ProfileBufferChunkManagerWithLocalLimit.h"
#  include "mozilla/ProfileBufferControlledChunkManager.h"
#  include "mozilla/ProfileChunkedBuffer.h"
#  include "mozilla/Vector.h"
#endif  // MOZ_GECKO_PROFILER

#if defined(_MSC_VER) || defined(__MINGW32__)
#  include <windows.h>
#  include <mmsystem.h>
#  include <process.h>
#else
#  include <errno.h>
#  include <time.h>
#endif

#include <algorithm>
#include <atomic>
#include <iostream>
#include <random>
#include <thread>
#include <type_traits>
#include <utility>

void TestFailureLatch() {
  printf("TestFailureLatch...\n");

  // Test infallible latch.
  {
    mozilla::FailureLatchInfallibleSource& infallibleLatch =
        mozilla::FailureLatchInfallibleSource::Singleton();

    MOZ_RELEASE_ASSERT(!infallibleLatch.Fallible());
    MOZ_RELEASE_ASSERT(!infallibleLatch.Failed());
    MOZ_RELEASE_ASSERT(!infallibleLatch.GetFailure());
    MOZ_RELEASE_ASSERT(&infallibleLatch.SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());
    MOZ_RELEASE_ASSERT(&std::as_const(infallibleLatch).SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());
  }

  // Test failure latch basic functions.
  {
    mozilla::FailureLatchSource failureLatch;

    MOZ_RELEASE_ASSERT(failureLatch.Fallible());
    MOZ_RELEASE_ASSERT(!failureLatch.Failed());
    MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());
    MOZ_RELEASE_ASSERT(&failureLatch.SourceFailureLatch() == &failureLatch);
    MOZ_RELEASE_ASSERT(&std::as_const(failureLatch).SourceFailureLatch() ==
                       &failureLatch);

    failureLatch.SetFailure("error");

    MOZ_RELEASE_ASSERT(failureLatch.Fallible());
    MOZ_RELEASE_ASSERT(failureLatch.Failed());
    MOZ_RELEASE_ASSERT(failureLatch.GetFailure());
    MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);

    failureLatch.SetFailure("later error");

    MOZ_RELEASE_ASSERT(failureLatch.Fallible());
    MOZ_RELEASE_ASSERT(failureLatch.Failed());
    MOZ_RELEASE_ASSERT(failureLatch.GetFailure());
    MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
  }

  // Test SetFailureFrom.
  {
    mozilla::FailureLatchSource failureLatch;

    MOZ_RELEASE_ASSERT(!failureLatch.Failed());
    failureLatch.SetFailureFrom(failureLatch);
    MOZ_RELEASE_ASSERT(!failureLatch.Failed());
    MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());

    // SetFailureFrom with no error.
    {
      mozilla::FailureLatchSource failureLatchInnerOk;
      MOZ_RELEASE_ASSERT(!failureLatchInnerOk.Failed());
      MOZ_RELEASE_ASSERT(!failureLatchInnerOk.GetFailure());

      MOZ_RELEASE_ASSERT(!failureLatch.Failed());
      failureLatch.SetFailureFrom(failureLatchInnerOk);
      MOZ_RELEASE_ASSERT(!failureLatch.Failed());

      MOZ_RELEASE_ASSERT(!failureLatchInnerOk.Failed());
      MOZ_RELEASE_ASSERT(!failureLatchInnerOk.GetFailure());
    }
    MOZ_RELEASE_ASSERT(!failureLatch.Failed());
    MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());

    // SetFailureFrom with error.
    {
      mozilla::FailureLatchSource failureLatchInnerError;
      MOZ_RELEASE_ASSERT(!failureLatchInnerError.Failed());
      MOZ_RELEASE_ASSERT(!failureLatchInnerError.GetFailure());

      failureLatchInnerError.SetFailure("inner error");
      MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
      MOZ_RELEASE_ASSERT(
          strcmp(failureLatchInnerError.GetFailure(), "inner error") == 0);

      MOZ_RELEASE_ASSERT(!failureLatch.Failed());
      failureLatch.SetFailureFrom(failureLatchInnerError);
      MOZ_RELEASE_ASSERT(failureLatch.Failed());

      MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
      MOZ_RELEASE_ASSERT(
          strcmp(failureLatchInnerError.GetFailure(), "inner error") == 0);
    }
    MOZ_RELEASE_ASSERT(failureLatch.Failed());
    MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);

    failureLatch.SetFailureFrom(failureLatch);
    MOZ_RELEASE_ASSERT(failureLatch.Failed());
    MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);

    // SetFailureFrom with error again, ignored.
    {
      mozilla::FailureLatchSource failureLatchInnerError;
      failureLatchInnerError.SetFailure("later inner error");
      MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
      MOZ_RELEASE_ASSERT(strcmp(failureLatchInnerError.GetFailure(),
                                "later inner error") == 0);

      MOZ_RELEASE_ASSERT(failureLatch.Failed());
      failureLatch.SetFailureFrom(failureLatchInnerError);
      MOZ_RELEASE_ASSERT(failureLatch.Failed());

      MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
      MOZ_RELEASE_ASSERT(strcmp(failureLatchInnerError.GetFailure(),
                                "later inner error") == 0);
    }
    MOZ_RELEASE_ASSERT(failureLatch.Failed());
    MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);
  }

  // Test FAILURELATCH_IMPL_PROXY
  {
    class Proxy final : public mozilla::FailureLatch {
     public:
      explicit Proxy(mozilla::FailureLatch& aFailureLatch)
          : mFailureLatch(WrapNotNull(&aFailureLatch)) {}

      void Set(mozilla::FailureLatch& aFailureLatch) {
        mFailureLatch = WrapNotNull(&aFailureLatch);
      }

      FAILURELATCH_IMPL_PROXY(*mFailureLatch)

     private:
      mozilla::NotNull<mozilla::FailureLatch*> mFailureLatch;
    };

    Proxy proxy{mozilla::FailureLatchInfallibleSource::Singleton()};

    MOZ_RELEASE_ASSERT(!proxy.Fallible());
    MOZ_RELEASE_ASSERT(!proxy.Failed());
    MOZ_RELEASE_ASSERT(!proxy.GetFailure());
    MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());
    MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());

    // Error from proxy.
    {
      mozilla::FailureLatchSource failureLatch;
      proxy.Set(failureLatch);
      MOZ_RELEASE_ASSERT(proxy.Fallible());
      MOZ_RELEASE_ASSERT(!proxy.Failed());
      MOZ_RELEASE_ASSERT(!proxy.GetFailure());
      MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
      MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
                         &failureLatch);

      proxy.SetFailure("error");
      MOZ_RELEASE_ASSERT(proxy.Failed());
      MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
      MOZ_RELEASE_ASSERT(failureLatch.Failed());
      MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);

      // Don't forget to stop pointing at soon-to-be-destroyed object.
      proxy.Set(mozilla::FailureLatchInfallibleSource::Singleton());
    }

    // Error from proxy's origin.
    {
      mozilla::FailureLatchSource failureLatch;
      proxy.Set(failureLatch);
      MOZ_RELEASE_ASSERT(proxy.Fallible());
      MOZ_RELEASE_ASSERT(!proxy.Failed());
      MOZ_RELEASE_ASSERT(!proxy.GetFailure());
      MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
      MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
                         &failureLatch);

      failureLatch.SetFailure("error");
      MOZ_RELEASE_ASSERT(proxy.Failed());
      MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
      MOZ_RELEASE_ASSERT(failureLatch.Failed());
      MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);

      // Don't forget to stop pointing at soon-to-be-destroyed object.
      proxy.Set(mozilla::FailureLatchInfallibleSource::Singleton());
    }

    MOZ_RELEASE_ASSERT(!proxy.Fallible());
    MOZ_RELEASE_ASSERT(!proxy.Failed());
    MOZ_RELEASE_ASSERT(!proxy.GetFailure());
    MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());
    MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());
  }

  // Test FAILURELATCH_IMPL_PROXY_OR_INFALLIBLE
  {
    class ProxyOrNull final : public mozilla::FailureLatch {
     public:
      ProxyOrNull() = default;

      void Set(mozilla::FailureLatch* aFailureLatchOrNull) {
        mFailureLatchOrNull = aFailureLatchOrNull;
      }

      FAILURELATCH_IMPL_PROXY_OR_INFALLIBLE(mFailureLatchOrNull, ProxyOrNull)

     private:
      mozilla::FailureLatch* mFailureLatchOrNull = nullptr;
    };

    ProxyOrNull proxy;

    MOZ_RELEASE_ASSERT(!proxy.Fallible());
    MOZ_RELEASE_ASSERT(!proxy.Failed());
    MOZ_RELEASE_ASSERT(!proxy.GetFailure());
    MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());
    MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());

    // Error from proxy.
    {
      mozilla::FailureLatchSource failureLatch;
      proxy.Set(&failureLatch);
      MOZ_RELEASE_ASSERT(proxy.Fallible());
      MOZ_RELEASE_ASSERT(!proxy.Failed());
      MOZ_RELEASE_ASSERT(!proxy.GetFailure());
      MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
      MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
                         &failureLatch);

      proxy.SetFailure("error");
      MOZ_RELEASE_ASSERT(proxy.Failed());
      MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
      MOZ_RELEASE_ASSERT(failureLatch.Failed());
      MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);

      // Don't forget to stop pointing at soon-to-be-destroyed object.
      proxy.Set(nullptr);
    }

    // Error from proxy's origin.
    {
      mozilla::FailureLatchSource failureLatch;
      proxy.Set(&failureLatch);
      MOZ_RELEASE_ASSERT(proxy.Fallible());
      MOZ_RELEASE_ASSERT(!proxy.Failed());
      MOZ_RELEASE_ASSERT(!proxy.GetFailure());
      MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
      MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
                         &failureLatch);

      failureLatch.SetFailure("error");
      MOZ_RELEASE_ASSERT(proxy.Failed());
      MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
      MOZ_RELEASE_ASSERT(failureLatch.Failed());
      MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);

      // Don't forget to stop pointing at soon-to-be-destroyed object.
      proxy.Set(nullptr);
    }

    MOZ_RELEASE_ASSERT(!proxy.Fallible());
    MOZ_RELEASE_ASSERT(!proxy.Failed());
    MOZ_RELEASE_ASSERT(!proxy.GetFailure());
    MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());
    MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
                       &mozilla::FailureLatchInfallibleSource::Singleton());
  }

  printf("TestFailureLatch done\n");
}

void TestProfilerUtils() {
  printf("TestProfilerUtils...\n");

  {
    using mozilla::baseprofiler::BaseProfilerProcessId;
    using Number = BaseProfilerProcessId::NumberType;
    static constexpr Number scMaxNumber = std::numeric_limits<Number>::max();

    static_assert(
        BaseProfilerProcessId{}.ToNumber() == 0,
        "These tests assume that the unspecified process id number is 0; "
        "if this fails, please update these tests accordingly");

    static_assert(!BaseProfilerProcessId{}.IsSpecified());
    static_assert(!BaseProfilerProcessId::FromNumber(0).IsSpecified());
    static_assert(BaseProfilerProcessId::FromNumber(1).IsSpecified());
    static_assert(BaseProfilerProcessId::FromNumber(123).IsSpecified());
    static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber).IsSpecified());

    static_assert(BaseProfilerProcessId::FromNumber(Number(1)).ToNumber() ==
                  Number(1));
    static_assert(BaseProfilerProcessId::FromNumber(Number(123)).ToNumber() ==
                  Number(123));
    static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber).ToNumber() ==
                  scMaxNumber);

    static_assert(BaseProfilerProcessId{} == BaseProfilerProcessId{});
    static_assert(BaseProfilerProcessId::FromNumber(Number(123)) ==
                  BaseProfilerProcessId::FromNumber(Number(123)));
    static_assert(BaseProfilerProcessId{} !=
                  BaseProfilerProcessId::FromNumber(Number(123)));
    static_assert(BaseProfilerProcessId::FromNumber(Number(123)) !=
                  BaseProfilerProcessId{});
    static_assert(BaseProfilerProcessId::FromNumber(Number(123)) !=
                  BaseProfilerProcessId::FromNumber(scMaxNumber));
    static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber) !=
                  BaseProfilerProcessId::FromNumber(Number(123)));

    // Verify trivial-copyability by memcpy'ing to&from same-size storage.
    static_assert(std::is_trivially_copyable_v<BaseProfilerProcessId>);
    BaseProfilerProcessId pid;
    MOZ_RELEASE_ASSERT(!pid.IsSpecified());
    Number pidStorage;
    static_assert(sizeof(pidStorage) == sizeof(pid));
    // Copy from BaseProfilerProcessId to storage. Note: We cannot assume that
    // this is equal to what ToNumber() gives us. All we can do is verify that
    // copying from storage back to BaseProfilerProcessId works as expected.
    std::memcpy(&pidStorage, &pid, sizeof(pidStorage));
    BaseProfilerProcessId pid2 = BaseProfilerProcessId::FromNumber(2);
    MOZ_RELEASE_ASSERT(pid2.IsSpecified());
    std::memcpy(&pid2, &pidStorage, sizeof(pid));
    MOZ_RELEASE_ASSERT(!pid2.IsSpecified());

    pid = BaseProfilerProcessId::FromNumber(123);
    std::memcpy(&pidStorage, &pid, sizeof(pidStorage));
    pid2 = BaseProfilerProcessId{};
    MOZ_RELEASE_ASSERT(!pid2.IsSpecified());
    std::memcpy(&pid2, &pidStorage, sizeof(pid));
    MOZ_RELEASE_ASSERT(pid2.IsSpecified());
    MOZ_RELEASE_ASSERT(pid2.ToNumber() == 123);

    // No conversions to/from numbers.
    static_assert(!std::is_constructible_v<BaseProfilerProcessId, Number>);
    static_assert(!std::is_assignable_v<BaseProfilerProcessId, Number>);
    static_assert(!std::is_constructible_v<Number, BaseProfilerProcessId>);
    static_assert(!std::is_assignable_v<Number, BaseProfilerProcessId>);

    static_assert(
        std::is_same_v<
            decltype(mozilla::baseprofiler::profiler_current_process_id()),
            BaseProfilerProcessId>);
    MOZ_RELEASE_ASSERT(
        mozilla::baseprofiler::profiler_current_process_id().IsSpecified());
  }

  {
    mozilla::baseprofiler::profiler_init_main_thread_id();

    using mozilla::baseprofiler::BaseProfilerThreadId;
    using Number = BaseProfilerThreadId::NumberType;
    static constexpr Number scMaxNumber = std::numeric_limits<Number>::max();

    static_assert(
        BaseProfilerThreadId{}.ToNumber() == 0,
        "These tests assume that the unspecified thread id number is 0; "
        "if this fails, please update these tests accordingly");

    static_assert(!BaseProfilerThreadId{}.IsSpecified());
    static_assert(!BaseProfilerThreadId::FromNumber(0).IsSpecified());
    static_assert(BaseProfilerThreadId::FromNumber(1).IsSpecified());
    static_assert(BaseProfilerThreadId::FromNumber(123).IsSpecified());
    static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber).IsSpecified());

    static_assert(BaseProfilerThreadId::FromNumber(Number(1)).ToNumber() ==
                  Number(1));
    static_assert(BaseProfilerThreadId::FromNumber(Number(123)).ToNumber() ==
                  Number(123));
    static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber).ToNumber() ==
                  scMaxNumber);

    static_assert(BaseProfilerThreadId{} == BaseProfilerThreadId{});
    static_assert(BaseProfilerThreadId::FromNumber(Number(123)) ==
                  BaseProfilerThreadId::FromNumber(Number(123)));
    static_assert(BaseProfilerThreadId{} !=
                  BaseProfilerThreadId::FromNumber(Number(123)));
    static_assert(BaseProfilerThreadId::FromNumber(Number(123)) !=
                  BaseProfilerThreadId{});
    static_assert(BaseProfilerThreadId::FromNumber(Number(123)) !=
                  BaseProfilerThreadId::FromNumber(scMaxNumber));
    static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber) !=
                  BaseProfilerThreadId::FromNumber(Number(123)));

    // Verify trivial-copyability by memcpy'ing to&from same-size storage.
    static_assert(std::is_trivially_copyable_v<BaseProfilerThreadId>);
    BaseProfilerThreadId tid;
    MOZ_RELEASE_ASSERT(!tid.IsSpecified());
    Number tidStorage;
    static_assert(sizeof(tidStorage) == sizeof(tid));
    // Copy from BaseProfilerThreadId to storage. Note: We cannot assume that
    // this is equal to what ToNumber() gives us. All we can do is verify that
    // copying from storage back to BaseProfilerThreadId works as expected.
    std::memcpy(&tidStorage, &tid, sizeof(tidStorage));
    BaseProfilerThreadId tid2 = BaseProfilerThreadId::FromNumber(2);
    MOZ_RELEASE_ASSERT(tid2.IsSpecified());
    std::memcpy(&tid2, &tidStorage, sizeof(tid));
    MOZ_RELEASE_ASSERT(!tid2.IsSpecified());

    tid = BaseProfilerThreadId::FromNumber(Number(123));
    std::memcpy(&tidStorage, &tid, sizeof(tidStorage));
    tid2 = BaseProfilerThreadId{};
    MOZ_RELEASE_ASSERT(!tid2.IsSpecified());
    std::memcpy(&tid2, &tidStorage, sizeof(tid));
    MOZ_RELEASE_ASSERT(tid2.IsSpecified());
    MOZ_RELEASE_ASSERT(tid2.ToNumber() == Number(123));

    // No conversions to/from numbers.
    static_assert(!std::is_constructible_v<BaseProfilerThreadId, Number>);
    static_assert(!std::is_assignable_v<BaseProfilerThreadId, Number>);
    static_assert(!std::is_constructible_v<Number, BaseProfilerThreadId>);
    static_assert(!std::is_assignable_v<Number, BaseProfilerThreadId>);

    static_assert(std::is_same_v<
                  decltype(mozilla::baseprofiler::profiler_current_thread_id()),
                  BaseProfilerThreadId>);
    BaseProfilerThreadId mainTestThreadId =
        mozilla::baseprofiler::profiler_current_thread_id();
    MOZ_RELEASE_ASSERT(mainTestThreadId.IsSpecified());

    BaseProfilerThreadId mainThreadId =
        mozilla::baseprofiler::profiler_main_thread_id();
    MOZ_RELEASE_ASSERT(mainThreadId.IsSpecified());

    MOZ_RELEASE_ASSERT(mainThreadId == mainTestThreadId,
                       "Test should run on the main thread");
    MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_is_main_thread());

    std::thread testThread([&]() {
      const BaseProfilerThreadId testThreadId =
          mozilla::baseprofiler::profiler_current_thread_id();
      MOZ_RELEASE_ASSERT(testThreadId.IsSpecified());
      MOZ_RELEASE_ASSERT(testThreadId != mainThreadId);
      MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_is_main_thread());
    });
    testThread.join();
  }

  // No conversions between processes and threads.
  static_assert(
      !std::is_constructible_v<mozilla::baseprofiler::BaseProfilerThreadId,
                               mozilla::baseprofiler::BaseProfilerProcessId>);
  static_assert(
      !std::is_assignable_v<mozilla::baseprofiler::BaseProfilerThreadId,
                            mozilla::baseprofiler::BaseProfilerProcessId>);
  static_assert(
      !std::is_constructible_v<mozilla::baseprofiler::BaseProfilerProcessId,
                               mozilla::baseprofiler::BaseProfilerThreadId>);
  static_assert(
      !std::is_assignable_v<mozilla::baseprofiler::BaseProfilerProcessId,
                            mozilla::baseprofiler::BaseProfilerThreadId>);

  printf("TestProfilerUtils done\n");
}

void TestBaseAndProfilerDetail() {
  printf("TestBaseAndProfilerDetail...\n");

  {
    using mozilla::profiler::detail::FilterHasPid;

    const auto pid123 =
        mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(123);
    MOZ_RELEASE_ASSERT(FilterHasPid("pid:123", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid(" ", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("123", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid:", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid=123", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid:123 ", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid: 123", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0123", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0000000000000000000000123", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid:12", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid:1234", pid123));
    MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0", pid123));

    using PidNumber = mozilla::baseprofiler::BaseProfilerProcessId::NumberType;
    const PidNumber maxNumber = std::numeric_limits<PidNumber>::max();
    const auto maxPid =
        mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(maxNumber);
    const std::string maxPidString = "pid:" + std::to_string(maxNumber);
    MOZ_RELEASE_ASSERT(FilterHasPid(maxPidString.c_str(), maxPid));

    const std::string tooBigPidString = maxPidString + "0";
    MOZ_RELEASE_ASSERT(!FilterHasPid(tooBigPidString.c_str(), maxPid));
  }

  {
    using mozilla::profiler::detail::FiltersExcludePid;
    const auto pid123 =
        mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(123);

    MOZ_RELEASE_ASSERT(
        !FiltersExcludePid(mozilla::Span<const char*>{}, pid123));

    {
      const charconst filters[] = {"main"};
      MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
    }

    {
      const charconst filters[] = {"main""pid:123"};
      MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
    }

    {
      const charconst filters[] = {"main""pid:456"};
      MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
    }

    {
      const charconst filters[] = {"pid:123"};
      MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
    }

    {
      const charconst filters[] = {"pid:123""pid:456"};
      MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
    }

    {
      const charconst filters[] = {"pid:456""pid:123"};
      MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
    }

    {
      const charconst filters[] = {"pid:456"};
      MOZ_RELEASE_ASSERT(FiltersExcludePid(filters, pid123));
    }

    {
      const charconst filters[] = {"pid:456""pid:789"};
      MOZ_RELEASE_ASSERT(FiltersExcludePid(filters, pid123));
    }
  }

  printf("TestBaseAndProfilerDetail done\n");
}

void TestSharedMutex() {
  printf("TestSharedMutex...\n");

  mozilla::baseprofiler::detail::BaseProfilerSharedMutex sm;

  // First round of minimal tests in this thread.

  MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());

  sm.LockExclusive();
  MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
  sm.UnlockExclusive();
  MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());

  sm.LockShared();
  MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
  sm.UnlockShared();
  MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());

  {
    mozilla::baseprofiler::detail::BaseProfilerAutoLockExclusive exclusiveLock{
        sm};
    MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
  }
  MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());

  {
    mozilla::baseprofiler::detail::BaseProfilerAutoLockShared sharedLock{sm};
    MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
  }
  MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());

  // The following will run actions between two threads, to verify that
  // exclusive and shared locks work as expected.

  // These actions will happen from top to bottom.
  // This will test all possible lock interactions.
  enum NextAction {                  // State of the lock:
    t1Starting,                      // (x=exclusive, s=shared, ?=blocked)
    t2Starting,                      // t1 t2
    t1LockExclusive,                 // x
    t2LockExclusiveAndBlock,         // x  x? - Can't have two exclusives.
    t1UnlockExclusive,               //    x
    t2UnblockedAfterT1Unlock,        //    x
    t1LockSharedAndBlock,            // s? x - Can't have shared during excl
    t2UnlockExclusive,               // s
    t1UnblockedAfterT2Unlock,        // s
    t2LockShared,                    // s  s - Can have multiple shared locks
    t1UnlockShared,                  //    s
    t2StillLockedShared,             //    s
    t1LockExclusiveAndBlock,         // x? s - Can't have excl during shared
    t2UnlockShared,                  // x
    t1UnblockedAfterT2UnlockShared,  // x
    t2CheckAfterT1Lock,              // x
    t1LastUnlockExclusive,           // (unlocked)
    done
  };

  // Each thread will repeatedly read this `nextAction`, and run actions that
  // target it...
  std::atomic<NextAction> nextAction{static_cast<NextAction>(0)};
  // ... and advance to the next available action (which should usually be for
  // the other thread).
  auto AdvanceAction = [&nextAction]() {
    MOZ_RELEASE_ASSERT(nextAction <= done);
    nextAction = static_cast<NextAction>(static_cast<int>(nextAction) + 1);
  };

  std::thread t1{[&]() {
    for (;;) {
      switch (nextAction) {
        case t1Starting:
          AdvanceAction();
          break;
        case t1LockExclusive:
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          sm.LockExclusive();
          MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
          AdvanceAction();
          break;
        case t1UnlockExclusive:
          MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
          // Advance first, before unlocking, so that t2 sees the new state.
          AdvanceAction();
          sm.UnlockExclusive();
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          break;
        case t1LockSharedAndBlock:
          // Advance action before attempting to lock after t2's exclusive lock.
          AdvanceAction();
          sm.LockShared();
          // We will only acquire the lock after t1 unlocks.
          MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2Unlock);
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          AdvanceAction();
          break;
        case t1UnlockShared:
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          // Advance first, before unlocking, so that t2 sees the new state.
          AdvanceAction();
          sm.UnlockShared();
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          break;
        case t1LockExclusiveAndBlock:
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          // Advance action before attempting to lock after t2's shared lock.
          AdvanceAction();
          sm.LockExclusive();
          // We will only acquire the lock after t2 unlocks.
          MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2UnlockShared);
          MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
          AdvanceAction();
          break;
        case t1LastUnlockExclusive:
          MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
          // Advance first, before unlocking, so that t2 sees the new state.
          AdvanceAction();
          sm.UnlockExclusive();
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          break;
        case done:
          return;
        default:
          // Ignore other actions intended for t2.
          break;
      }
    }
  }};

  std::thread t2{[&]() {
    for (;;) {
      switch (nextAction) {
        case t2Starting:
          AdvanceAction();
          break;
        case t2LockExclusiveAndBlock:
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          // Advance action before attempting to lock after t1's exclusive lock.
          AdvanceAction();
          sm.LockExclusive();
          // We will only acquire the lock after t1 unlocks.
          MOZ_RELEASE_ASSERT(nextAction == t2UnblockedAfterT1Unlock);
          MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
          AdvanceAction();
          break;
        case t2UnlockExclusive:
          MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
          // Advance first, before unlocking, so that t1 sees the new state.
          AdvanceAction();
          sm.UnlockExclusive();
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          break;
        case t2LockShared:
          sm.LockShared();
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          AdvanceAction();
          break;
        case t2StillLockedShared:
          AdvanceAction();
          break;
        case t2UnlockShared:
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          // Advance first, before unlocking, so that t1 sees the new state.
          AdvanceAction();
          sm.UnlockShared();
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          break;
        case t2CheckAfterT1Lock:
          MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
          AdvanceAction();
          break;
        case done:
          return;
        default:
          // Ignore other actions intended for t1.
          break;
      }
    }
  }};

  t1.join();
  t2.join();

  printf("TestSharedMutex done\n");
}

void TestProportionValue() {
  printf("TestProportionValue...\n");

  using mozilla::ProportionValue;

#define STATIC_ASSERT_EQ(a, b) \
  static_assert((a) == (b));   \
  MOZ_RELEASE_ASSERT((a) == (b));

#define STATIC_ASSERT(e) STATIC_ASSERT_EQ(e, true)

  // Conversion from&to double.
  STATIC_ASSERT_EQ(ProportionValue().ToDouble(), 0.0);
  STATIC_ASSERT_EQ(ProportionValue(0.0).ToDouble(), 0.0);
  STATIC_ASSERT_EQ(ProportionValue(0.5).ToDouble(), 0.5);
  STATIC_ASSERT_EQ(ProportionValue(1.0).ToDouble(), 1.0);

  // Clamping.
  STATIC_ASSERT_EQ(
      ProportionValue(std::numeric_limits<double>::min()).ToDouble(), 0.0);
  STATIC_ASSERT_EQ(
      ProportionValue(std::numeric_limits<long double>::min()).ToDouble(), 0.0);
  STATIC_ASSERT_EQ(ProportionValue(-1.0).ToDouble(), 0.0);
  STATIC_ASSERT_EQ(ProportionValue(-0.01).ToDouble(), 0.0);
  STATIC_ASSERT_EQ(ProportionValue(-0.0).ToDouble(), 0.0);
  STATIC_ASSERT_EQ(ProportionValue(1.01).ToDouble(), 1.0);
  STATIC_ASSERT_EQ(
      ProportionValue(std::numeric_limits<double>::max()).ToDouble(), 1.0);

  // User-defined literal.
  {
    using namespace mozilla::literals::ProportionValue_literals;
    STATIC_ASSERT_EQ(0_pc, ProportionValue(0.0));
    STATIC_ASSERT_EQ(0._pc, ProportionValue(0.0));
    STATIC_ASSERT_EQ(50_pc, ProportionValue(0.5));
    STATIC_ASSERT_EQ(50._pc, ProportionValue(0.5));
    STATIC_ASSERT_EQ(100_pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(100._pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(101_pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(100.01_pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(1000_pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(1000._pc, ProportionValue(1.0));
  }
  {
    // ProportionValue_literals is an inline namespace of mozilla::literals, so
    // it's optional.
    using namespace mozilla::literals;
    STATIC_ASSERT_EQ(0_pc, ProportionValue(0.0));
    STATIC_ASSERT_EQ(0._pc, ProportionValue(0.0));
    STATIC_ASSERT_EQ(50_pc, ProportionValue(0.5));
    STATIC_ASSERT_EQ(50._pc, ProportionValue(0.5));
    STATIC_ASSERT_EQ(100_pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(100._pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(101_pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(100.01_pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(1000_pc, ProportionValue(1.0));
    STATIC_ASSERT_EQ(1000._pc, ProportionValue(1.0));
  }

  // Invalid construction, conversion to double NaN.
  MOZ_RELEASE_ASSERT(std::isnan(ProportionValue::MakeInvalid().ToDouble()));

  using namespace mozilla::literals::ProportionValue_literals;

  // Conversion to&from underlying integral number.
  STATIC_ASSERT_EQ(
      ProportionValue::FromUnderlyingType((0_pc).ToUnderlyingType()).ToDouble(),
      0.0);
  STATIC_ASSERT_EQ(
      ProportionValue::FromUnderlyingType((50_pc).ToUnderlyingType())
          .ToDouble(),
      0.5);
  STATIC_ASSERT_EQ(
      ProportionValue::FromUnderlyingType((100_pc).ToUnderlyingType())
          .ToDouble(),
      1.0);
  STATIC_ASSERT(ProportionValue::FromUnderlyingType(
                    ProportionValue::MakeInvalid().ToUnderlyingType())
                    .IsInvalid());

  // IsExactlyZero.
  STATIC_ASSERT(ProportionValue().IsExactlyZero());
  STATIC_ASSERT((0_pc).IsExactlyZero());
  STATIC_ASSERT(!(50_pc).IsExactlyZero());
  STATIC_ASSERT(!(100_pc).IsExactlyZero());
  STATIC_ASSERT(!ProportionValue::MakeInvalid().IsExactlyZero());

  // IsExactlyOne.
  STATIC_ASSERT(!ProportionValue().IsExactlyOne());
  STATIC_ASSERT(!(0_pc).IsExactlyOne());
  STATIC_ASSERT(!(50_pc).IsExactlyOne());
  STATIC_ASSERT((100_pc).IsExactlyOne());
  STATIC_ASSERT(!ProportionValue::MakeInvalid().IsExactlyOne());

  // IsValid.
  STATIC_ASSERT(ProportionValue().IsValid());
  STATIC_ASSERT((0_pc).IsValid());
  STATIC_ASSERT((50_pc).IsValid());
  STATIC_ASSERT((100_pc).IsValid());
  STATIC_ASSERT(!ProportionValue::MakeInvalid().IsValid());

  // IsInvalid.
  STATIC_ASSERT(!ProportionValue().IsInvalid());
  STATIC_ASSERT(!(0_pc).IsInvalid());
  STATIC_ASSERT(!(50_pc).IsInvalid());
  STATIC_ASSERT(!(100_pc).IsInvalid());
  STATIC_ASSERT(ProportionValue::MakeInvalid().IsInvalid());

  // Addition.
  STATIC_ASSERT_EQ((0_pc + 0_pc).ToDouble(), 0.0);
  STATIC_ASSERT_EQ((0_pc + 100_pc).ToDouble(), 1.0);
  STATIC_ASSERT_EQ((100_pc + 0_pc).ToDouble(), 1.0);
  STATIC_ASSERT_EQ((100_pc + 100_pc).ToDouble(), 1.0);
  STATIC_ASSERT((ProportionValue::MakeInvalid() + 50_pc).IsInvalid());
  STATIC_ASSERT((50_pc + ProportionValue::MakeInvalid()).IsInvalid());

  // Subtraction.
  STATIC_ASSERT_EQ((0_pc - 0_pc).ToDouble(), 0.0);
  STATIC_ASSERT_EQ((0_pc - 100_pc).ToDouble(), 0.0);
  STATIC_ASSERT_EQ((100_pc - 0_pc).ToDouble(), 1.0);
  STATIC_ASSERT_EQ((100_pc - 100_pc).ToDouble(), 0.0);
  STATIC_ASSERT((ProportionValue::MakeInvalid() - 50_pc).IsInvalid());
  STATIC_ASSERT((50_pc - ProportionValue::MakeInvalid()).IsInvalid());

  // Multiplication.
  STATIC_ASSERT_EQ((0_pc * 0_pc).ToDouble(), 0.0);
  STATIC_ASSERT_EQ((0_pc * 100_pc).ToDouble(), 0.0);
  STATIC_ASSERT_EQ((50_pc * 50_pc).ToDouble(), 0.25);
  STATIC_ASSERT_EQ((50_pc * 100_pc).ToDouble(), 0.5);
  STATIC_ASSERT_EQ((100_pc * 50_pc).ToDouble(), 0.5);
  STATIC_ASSERT_EQ((100_pc * 0_pc).ToDouble(), 0.0);
  STATIC_ASSERT_EQ((100_pc * 100_pc).ToDouble(), 1.0);
  STATIC_ASSERT((ProportionValue::MakeInvalid() * 50_pc).IsInvalid());
  STATIC_ASSERT((50_pc * ProportionValue::MakeInvalid()).IsInvalid());

  // Division by a positive integer value.
  STATIC_ASSERT_EQ((100_pc / 1u).ToDouble(), 1.0);
  STATIC_ASSERT_EQ((100_pc / 2u).ToDouble(), 0.5);
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(6u) / 2u).ToUnderlyingType(), 3u);
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(5u) / 2u).ToUnderlyingType(), 2u);
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(1u) / 2u).ToUnderlyingType(), 0u);
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(0u) / 2u).ToUnderlyingType(), 0u);
  STATIC_ASSERT((100_pc / 0u).IsInvalid());
  STATIC_ASSERT((ProportionValue::MakeInvalid() / 2u).IsInvalid());

  // Multiplication by a positive integer value.
  STATIC_ASSERT_EQ((100_pc * 1u).ToDouble(), 1.0);
  STATIC_ASSERT_EQ((50_pc * 1u).ToDouble(), 0.5);
  STATIC_ASSERT_EQ((50_pc * 2u).ToDouble(), 1.0);
  STATIC_ASSERT_EQ((50_pc * 3u).ToDouble(), 1.0);  // Clamped.
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(1u) * 2u).ToUnderlyingType(), 2u);
  STATIC_ASSERT((ProportionValue::MakeInvalid() * 2u).IsInvalid());

  // Verifying PV - u < (PV / u) * u <= PV, with n=3, PV between 6 and 9 :
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(6u) / 3u).ToUnderlyingType(), 2u);
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(7u) / 3u).ToUnderlyingType(), 2u);
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(8u) / 3u).ToUnderlyingType(), 2u);
  STATIC_ASSERT_EQ(
      (ProportionValue::FromUnderlyingType(9u) / 3u).ToUnderlyingType(), 3u);

  // Direct comparisons.
  STATIC_ASSERT_EQ(0_pc, 0_pc);
  STATIC_ASSERT(0_pc == 0_pc);
  STATIC_ASSERT(!(0_pc == 100_pc));
  STATIC_ASSERT(0_pc != 100_pc);
  STATIC_ASSERT(!(0_pc != 0_pc));
  STATIC_ASSERT(0_pc < 100_pc);
  STATIC_ASSERT(!(0_pc < 0_pc));
  STATIC_ASSERT(0_pc <= 0_pc);
  STATIC_ASSERT(0_pc <= 100_pc);
  STATIC_ASSERT(!(100_pc <= 0_pc));
  STATIC_ASSERT(100_pc > 0_pc);
  STATIC_ASSERT(!(100_pc > 100_pc));
  STATIC_ASSERT(100_pc >= 0_pc);
  STATIC_ASSERT(100_pc >= 100_pc);
  STATIC_ASSERT(!(0_pc >= 100_pc));
  // 0.5 is binary-friendly, so we can double it and compare it exactly.
  STATIC_ASSERT_EQ(50_pc + 50_pc, 100_pc);

#undef STATIC_ASSERT_EQ

  printf("TestProportionValue done\n");
}

template <typename Arg0, typename... Args>
bool AreAllEqual(Arg0&& aArg0, Args&&... aArgs) {
  return ((aArg0 == aArgs) && ...);
}

void TestProgressLogger() {
  printf("TestProgressLogger...\n");

  using mozilla::ProgressLogger;
  using mozilla::ProportionValue;
  using namespace mozilla::literals::ProportionValue_literals;

  auto progressRefPtr = mozilla::MakeRefPtr<ProgressLogger::SharedProgress>();
  MOZ_RELEASE_ASSERT(progressRefPtr);
  MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());

  {
    ProgressLogger pl(progressRefPtr, "Started""All done");
    MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());
    MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyZero());
    MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
                                   pl.GetLastGlobalLocation(), "Started"));

    // At this top level, the scale is 1:1.
    pl.SetLocalProgress(10_pc, "Top 10%");
    MOZ_RELEASE_ASSERT(
        AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 10_pc));
    MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
                                   pl.GetLastGlobalLocation(), "Top 10%"));

    pl.SetLocalProgress(0_pc, "Restarted");
    MOZ_RELEASE_ASSERT(
        AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 0_pc));
    MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
                                   pl.GetLastGlobalLocation(), "Restarted"));

    {
      // Create a sub-logger for the whole global range. Notice that this is
      // moving the current progress back to 0.
      ProgressLogger plSub1 =
          pl.CreateSubLoggerFromTo(0_pc, "Sub1 started", 100_pc, "Sub1 ended");
      MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());
      MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyZero());
      MOZ_RELEASE_ASSERT(plSub1.GetGlobalProgress().IsExactlyZero());
      MOZ_RELEASE_ASSERT(AreAllEqual(
          progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
          plSub1.GetLastGlobalLocation(), "Sub1 started"));

      // At this level, the scale is still 1:1.
      plSub1.SetLocalProgress(10_pc, "Sub1 10%");
      MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
                                     pl.GetGlobalProgress(),
                                     plSub1.GetGlobalProgress(), 10_pc));
      MOZ_RELEASE_ASSERT(AreAllEqual(
          progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
          plSub1.GetLastGlobalLocation(), "Sub1 10%"));

      {
        // Create a sub-logger half the global range.
        //   0              0.25   0.375    0.5    0.625    0.75             1
        //   |---------------|-------|-------|-------|-------|---------------|
        // plSub2:           0      0.25    0.5     0.75     1
        ProgressLogger plSub2 = plSub1.CreateSubLoggerFromTo(
            25_pc, "Sub2 started", 75_pc, "Sub2 ended");
        MOZ_RELEASE_ASSERT(AreAllEqual(
            progressRefPtr->Progress(), pl.GetGlobalProgress(),
            plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 25_pc));
        MOZ_RELEASE_ASSERT(AreAllEqual(
            progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
            plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
            "Sub2 started"));

        plSub2.SetLocalProgress(25_pc, "Sub2 25%");
        MOZ_RELEASE_ASSERT(AreAllEqual(
            progressRefPtr->Progress(), pl.GetGlobalProgress(),
            plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 37.5_pc));
        MOZ_RELEASE_ASSERT(AreAllEqual(
            progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
            plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
            "Sub2 25%"));

        plSub2.SetLocalProgress(50_pc, "Sub2 50%");
        MOZ_RELEASE_ASSERT(AreAllEqual(
            progressRefPtr->Progress(), pl.GetGlobalProgress(),
            plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 50_pc));
        MOZ_RELEASE_ASSERT(AreAllEqual(
            progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
            plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
            "Sub2 50%"));

        {
          // Create a sub-logger half the parent range.
          //   0              0.25   0.375    0.5    0.625    0.75             1
          //   |---------------|-------|-------|-------|-------|---------------|
          // plSub2:           0      0.25    0.5     0.75     1
          // plSub3:                           0      0.5      1
          ProgressLogger plSub3 = plSub2.CreateSubLoggerTo(
              "Sub3 started", 100_pc, ProgressLogger::NO_LOCATION_UPDATE);
          MOZ_RELEASE_ASSERT(AreAllEqual(
              progressRefPtr->Progress(), pl.GetGlobalProgress(),
              plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(),
              plSub3.GetGlobalProgress(), 50_pc));
          MOZ_RELEASE_ASSERT(AreAllEqual(
              progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
              plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
              plSub3.GetLastGlobalLocation(), "Sub3 started"));

          plSub3.SetLocalProgress(50_pc, "Sub3 50%");
          MOZ_RELEASE_ASSERT(AreAllEqual(
              progressRefPtr->Progress(), pl.GetGlobalProgress(),
              plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(),
              plSub3.GetGlobalProgress(), 62.5_pc));
          MOZ_RELEASE_ASSERT(AreAllEqual(
              progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
              plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
              plSub3.GetLastGlobalLocation(), "Sub3 50%"));
        }  // End of plSub3

        // When plSub3 ends, progress moves to its 100%, which is also plSub2's
        // 100%, which is plSub1's and the global progress of 75%
        MOZ_RELEASE_ASSERT(AreAllEqual(
            progressRefPtr->Progress(), pl.GetGlobalProgress(),
            plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 75_pc));
        // But location is still at the last explicit update.
        MOZ_RELEASE_ASSERT(AreAllEqual(
            progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
            plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
            "Sub3 50%"));
      }  // End of plSub2

      MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
                                     pl.GetGlobalProgress(),
                                     plSub1.GetGlobalProgress(), 75_pc));
      MOZ_RELEASE_ASSERT(AreAllEqual(
          progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
          plSub1.GetLastGlobalLocation(), "Sub2 ended"));
    }  // End of plSub1

    MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyOne());
    MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyOne());
    MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
                                   pl.GetLastGlobalLocation(), "Sub1 ended"));

    const auto loopStart = 75_pc;
    const auto loopEnd = 87.5_pc;
    const uint32_t loopCount = 8;
    uint32_t expectedIndex = 0u;
    auto expectedIterationStart = loopStart;
    const auto iterationIncrement = (loopEnd - loopStart) / loopCount;
    for (auto&& [index, loopPL] : pl.CreateLoopSubLoggersFromTo(
             loopStart, loopEnd, loopCount, "looping...")) {
      MOZ_RELEASE_ASSERT(index == expectedIndex);
      ++expectedIndex;
      MOZ_RELEASE_ASSERT(
          AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(),
                      loopPL.GetGlobalProgress(), expectedIterationStart));
      MOZ_RELEASE_ASSERT(AreAllEqual(
          progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
          loopPL.GetLastGlobalLocation(), "looping..."));

      loopPL.SetLocalProgress(50_pc, "half");
      MOZ_RELEASE_ASSERT(loopPL.GetGlobalProgress() ==
                         expectedIterationStart + iterationIncrement / 2u);
      MOZ_RELEASE_ASSERT(
          AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(),
                      loopPL.GetGlobalProgress(),
                      expectedIterationStart + iterationIncrement / 2u));
      MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
                                     pl.GetLastGlobalLocation(),
                                     loopPL.GetLastGlobalLocation(), "half"));

      expectedIterationStart = expectedIterationStart + iterationIncrement;
    }
    MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
                                   pl.GetGlobalProgress(),
                                   expectedIterationStart));
    MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
                                   pl.GetLastGlobalLocation(), "looping..."));
  }  // End of pl
  MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyOne());
  MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), "All done"));

  printf("TestProgressLogger done\n");
}

#ifdef MOZ_GECKO_PROFILER

MOZ_MAYBE_UNUSED static void SleepMilli(unsigned aMilliseconds) {
#  if defined(_MSC_VER) || defined(__MINGW32__)
  Sleep(aMilliseconds);
#  else
  struct timespec ts = {/* .tv_sec */ static_cast<time_t>(aMilliseconds / 1000),
                        /* ts.tv_nsec */ long(aMilliseconds % 1000) * 1000000};
  struct timespec tr = {0, 0};
  while (nanosleep(&ts, &tr)) {
    if (errno == EINTR) {
      ts = tr;
    } else {
      printf("nanosleep() -> %s\n", strerror(errno));
      exit(1);
    }
  }
#  endif
}

MOZ_MAYBE_UNUSED static void WaitUntilTimeStampChanges(
    const mozilla::TimeStamp& aTimeStampToCompare = mozilla::TimeStamp::Now()) {
  while (aTimeStampToCompare == mozilla::TimeStamp::Now()) {
    SleepMilli(1);
  }
}

using namespace mozilla;

void TestPowerOfTwoMask() {
  printf("TestPowerOfTwoMask...\n");

  static_assert(MakePowerOfTwoMask<uint32_t, 0>().MaskValue() == 0);
  constexpr PowerOfTwoMask<uint32_t> c0 = MakePowerOfTwoMask<uint32_t, 0>();
  MOZ_RELEASE_ASSERT(c0.MaskValue() == 0);

  static_assert(MakePowerOfTwoMask<uint32_t, 0xFFu>().MaskValue() == 0xFFu);
  constexpr PowerOfTwoMask<uint32_t> cFF =
      MakePowerOfTwoMask<uint32_t, 0xFFu>();
  MOZ_RELEASE_ASSERT(cFF.MaskValue() == 0xFFu);

  static_assert(MakePowerOfTwoMask<uint32_t, 0xFFFFFFFFu>().MaskValue() ==
                0xFFFFFFFFu);
  constexpr PowerOfTwoMask<uint32_t> cFFFFFFFF =
      MakePowerOfTwoMask<uint32_t, 0xFFFFFFFFu>();
  MOZ_RELEASE_ASSERT(cFFFFFFFF.MaskValue() == 0xFFFFFFFFu);

  struct TestDataU32 {
    uint32_t mInput;
    uint32_t mMask;
  };
  // clang-format off
  TestDataU32 tests[] = {
    { 0, 0 },
    { 1, 1 },
    { 2, 3 },
    { 3, 3 },
    { 4, 7 },
    { 5, 7 },
    { (1u << 31) - 1, (1u << 31) - 1 },
    { (1u << 31), uint32_t(-1) },
    { (1u << 31) + 1, uint32_t(-1) },
    { uint32_t(-1), uint32_t(-1) }
  };
  // clang-format on
  for (const TestDataU32& test : tests) {
    PowerOfTwoMask<uint32_t> p2m(test.mInput);
    MOZ_RELEASE_ASSERT(p2m.MaskValue() == test.mMask);
    for (const TestDataU32& inner : tests) {
      if (p2m.MaskValue() != uint32_t(-1)) {
        MOZ_RELEASE_ASSERT((inner.mInput % p2m) ==
                           (inner.mInput % (p2m.MaskValue() + 1)));
      }
      MOZ_RELEASE_ASSERT((inner.mInput & p2m) == (inner.mInput % p2m));
      MOZ_RELEASE_ASSERT((p2m & inner.mInput) == (inner.mInput & p2m));
    }
  }

  printf("TestPowerOfTwoMask done\n");
}

void TestPowerOfTwo() {
  printf("TestPowerOfTwo...\n");

  static_assert(MakePowerOfTwo<uint32_t, 1>().Value() == 1);
  constexpr PowerOfTwo<uint32_t> c1 = MakePowerOfTwo<uint32_t, 1>();
  MOZ_RELEASE_ASSERT(c1.Value() == 1);
  static_assert(MakePowerOfTwo<uint32_t, 1>().Mask().MaskValue() == 0);

  static_assert(MakePowerOfTwo<uint32_t, 128>().Value() == 128);
  constexpr PowerOfTwo<uint32_t> c128 = MakePowerOfTwo<uint32_t, 128>();
  MOZ_RELEASE_ASSERT(c128.Value() == 128);
  static_assert(MakePowerOfTwo<uint32_t, 128>().Mask().MaskValue() == 127);

  static_assert(MakePowerOfTwo<uint32_t, 0x80000000u>().Value() == 0x80000000u);
  constexpr PowerOfTwo<uint32_t> cMax = MakePowerOfTwo<uint32_t, 0x80000000u>();
  MOZ_RELEASE_ASSERT(cMax.Value() == 0x80000000u);
  static_assert(MakePowerOfTwo<uint32_t, 0x80000000u>().Mask().MaskValue() ==
                0x7FFFFFFFu);

  struct TestDataU32 {
    uint32_t mInput;
    uint32_t mValue;
    uint32_t mMask;
  };
  // clang-format off
  TestDataU32 tests[] = {
    { 0, 1, 0 },
    { 1, 1, 0 },
    { 2, 2, 1 },
    { 3, 4, 3 },
    { 4, 4, 3 },
    { 5, 8, 7 },
    { (1u << 31) - 1, (1u << 31), (1u << 31) - 1 },
    { (1u << 31), (1u << 31), (1u << 31) - 1 },
    { (1u << 31) + 1, (1u << 31), (1u << 31) - 1 },
    { uint32_t(-1), (1u << 31), (1u << 31) - 1 }
  };
  // clang-format on
  for (const TestDataU32& test : tests) {
    PowerOfTwo<uint32_t> p2(test.mInput);
    MOZ_RELEASE_ASSERT(p2.Value() == test.mValue);
    MOZ_RELEASE_ASSERT(p2.MaskValue() == test.mMask);
    PowerOfTwoMask<uint32_t> p2m = p2.Mask();
    MOZ_RELEASE_ASSERT(p2m.MaskValue() == test.mMask);
    for (const TestDataU32& inner : tests) {
      MOZ_RELEASE_ASSERT((inner.mInput % p2) == (inner.mInput % p2.Value()));
    }
  }

  printf("TestPowerOfTwo done\n");
}

void TestLEB128() {
  printf("TestLEB128...\n");

  MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint8_t>() == 2);
  MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint16_t>() == 3);
  MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint32_t>() == 5);
  MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint64_t>() == 10);

  struct TestDataU64 {
    uint64_t mValue;
    unsigned mSize;
    const char* mBytes;
  };
  // clang-format off
  TestDataU64 tests[] = {
    // Small numbers should keep their normal byte representation.
    {                  0u,  1, "\0" },
    {                  1u,  1, "\x01" },

    // 0111 1111 (127, or 0x7F) is the highest number that fits into a single
    // LEB128 byte. It gets encoded as 0111 1111, note the most significant bit
    // is off.
    {               0x7Fu,  1, "\x7F" },

    // Next number: 128, or 0x80.
    //   Original data representation:  1000 0000
    //     Broken up into groups of 7:         1  0000000
    // Padded with 0 (msB) or 1 (lsB):  00000001 10000000
    //            Byte representation:  0x01     0x80
    //            Little endian order:  -> 0x80 0x01
    {               0x80u,  2, "\x80\x01" },

    // Next: 129, or 0x81 (showing that we don't lose low bits.)
    //   Original data representation:  1000 0001
    //     Broken up into groups of 7:         1  0000001
    // Padded with 0 (msB) or 1 (lsB):  00000001 10000001
    //            Byte representation:  0x01     0x81
    //            Little endian order:  -> 0x81 0x01
    {               0x81u,  2, "\x81\x01" },

    // Highest 8-bit number: 255, or 0xFF.
    //   Original data representation:  1111 1111
    //     Broken up into groups of 7:         1  1111111
    // Padded with 0 (msB) or 1 (lsB):  00000001 11111111
    //            Byte representation:  0x01     0xFF
    //            Little endian order:  -> 0xFF 0x01
    {               0xFFu,  2, "\xFF\x01" },

    // Next: 256, or 0x100.
    //   Original data representation:  1 0000 0000
    //     Broken up into groups of 7:        10  0000000
    // Padded with 0 (msB) or 1 (lsB):  00000010 10000000
    //            Byte representation:  0x10     0x80
    //            Little endian order:  -> 0x80 0x02
    {              0x100u,  2, "\x80\x02" },

    // Highest 32-bit number: 0xFFFFFFFF (8 bytes, all bits set).
    // Original: 1111 1111 1111 1111 1111 1111 1111 1111
    // Groups:     1111  1111111  1111111  1111111  1111111
    // Padded: 00001111 11111111 11111111 11111111 11111111
    // Bytes:  0x0F     0xFF     0xFF     0xFF     0xFF
    // Little Endian: -> 0xFF 0xFF 0xFF 0xFF 0x0F
    {         0xFFFFFFFFu,  5, "\xFF\xFF\xFF\xFF\x0F" },

    // Highest 64-bit number: 0xFFFFFFFFFFFFFFFF (16 bytes, all bits set).
    // 64 bits, that's 9 groups of 7 bits, plus 1 (most significant) bit.
    { 0xFFFFFFFFFFFFFFFFu, 10, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01" }
  };
  // clang-format on

  for (const TestDataU64& test : tests) {
    MOZ_RELEASE_ASSERT(ULEB128Size(test.mValue) == test.mSize);
    // Prepare a buffer that can accomodate the largest-possible LEB128.
    uint8_t buffer[ULEB128MaxSize<uint64_t>()];
    // Use a pointer into the buffer as iterator.
    uint8_t* p = buffer;
    // And write the LEB128.
    WriteULEB128(test.mValue, p);
    // Pointer (iterator) should have advanced just past the expected LEB128
    // size.
    MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
    // Check expected bytes.
    for (unsigned i = 0; i < test.mSize; ++i) {
      MOZ_RELEASE_ASSERT(buffer[i] == uint8_t(test.mBytes[i]));
    }

    // Move pointer (iterator) back to start of buffer.
    p = buffer;
    // And read the LEB128 we wrote above.
    uint64_t read = ReadULEB128<uint64_t>(p);
    // Pointer (iterator) should have also advanced just past the expected
    // LEB128 size.
    MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
    // And check the read value.
    MOZ_RELEASE_ASSERT(read == test.mValue);

    // Testing ULEB128 reader.
    ULEB128Reader<uint64_t> reader;
    MOZ_RELEASE_ASSERT(!reader.IsComplete());
    // Move pointer back to start of buffer.
    p = buffer;
    for (;;) {
      // Read a byte and feed it to the reader.
      if (reader.FeedByteIsComplete(*p++)) {
        break;
      }
      // Not complete yet, we shouldn't have reached the end pointer.
      MOZ_RELEASE_ASSERT(!reader.IsComplete());
      MOZ_RELEASE_ASSERT(p < buffer + test.mSize);
    }
    MOZ_RELEASE_ASSERT(reader.IsComplete());
    // Pointer should have advanced just past the expected LEB128 size.
    MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
    // And check the read value.
    MOZ_RELEASE_ASSERT(reader.Value() == test.mValue);

    // And again after a Reset.
    reader.Reset();
    MOZ_RELEASE_ASSERT(!reader.IsComplete());
    p = buffer;
    for (;;) {
      if (reader.FeedByteIsComplete(*p++)) {
        break;
      }
      MOZ_RELEASE_ASSERT(!reader.IsComplete());
      MOZ_RELEASE_ASSERT(p < buffer + test.mSize);
    }
    MOZ_RELEASE_ASSERT(reader.IsComplete());
    MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
    MOZ_RELEASE_ASSERT(reader.Value() == test.mValue);
  }

  printf("TestLEB128 done\n");
}

struct StringWriteFunc final : public JSONWriteFunc {
  std::string mString;

  void Write(const mozilla::Span<const char>& aStr) final {
    mString.append(aStr.data(), aStr.size());
  }
};

void CheckJSON(mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
               const char* aExpected, int aLine) {
  const std::string& actual =
      static_cast<StringWriteFunc&>(aWriter.WriteFunc()).mString;
  if (strcmp(aExpected, actual.c_str()) != 0) {
    fprintf(stderr,
            "---- EXPECTED ---- (line %d)\n<<<%s>>>\n"
            "---- ACTUAL ----\n<<<%s>>>\n",
            aLine, aExpected, actual.c_str());
    MOZ_RELEASE_ASSERT(false"expected and actual output don't match");
  }
}

void TestJSONTimeOutput() {
  printf("TestJSONTimeOutput...\n");

#  define TEST(in, out)                                     \
    do {                                                    \
      mozilla::baseprofiler::SpliceableJSONWriter writer(   \
          mozilla::MakeUnique<StringWriteFunc>(),           \
          FailureLatchInfallibleSource::Singleton());       \
      writer.Start();                                       \
      writer.TimeDoubleMsProperty("time_ms", (in));         \
      writer.End();                                         \
      CheckJSON(writer, "{\"time_ms\":" out "}", __LINE__); \
    } while (false);

  TEST(0, "0");

  TEST(0.000'000'1, "0");
  TEST(0.000'000'4, "0");
  TEST(0.000'000'499, "0");
  TEST(0.000'000'5, "0.000001");
  TEST(0.000'001, "0.000001");
  TEST(0.000'01, "0.00001");
  TEST(0.000'1, "0.0001");
  TEST(0.001, "0.001");
  TEST(0.01, "0.01");
  TEST(0.1, "0.1");
  TEST(1, "1");
  TEST(2, "2");
  TEST(10, "10");
  TEST(100, "100");
  TEST(1'000, "1000");
  TEST(10'000, "10000");
  TEST(100'000, "100000");
  TEST(1'000'000, "1000000");
  // 2^53-2 ns in ms. 2^53-1 is the highest integer value representable in
  // double, -1 again because we're adding 0.5 before truncating.
  // That's 104 days, after which the nanosecond precision would decrease.
  TEST(9'007'199'254.740'990, "9007199254.74099");

  TEST(-0.000'000'1, "0");
  TEST(-0.000'000'4, "0");
  TEST(-0.000'000'499, "0");
  TEST(-0.000'000'5, "-0.000001");
  TEST(-0.000'001, "-0.000001");
  TEST(-0.000'01, "-0.00001");
  TEST(-0.000'1, "-0.0001");
  TEST(-0.001, "-0.001");
  TEST(-0.01, "-0.01");
  TEST(-0.1, "-0.1");
  TEST(-1, "-1");
  TEST(-2, "-2");
  TEST(-10, "-10");
  TEST(-100, "-100");
  TEST(-1'000, "-1000");
  TEST(-10'000, "-10000");
  TEST(-100'000, "-100000");
  TEST(-1'000'000, "-1000000");
  TEST(-9'007'199'254.740'990, "-9007199254.74099");

#  undef TEST

  printf("TestJSONTimeOutput done\n");
}

template <uint8_t byte, uint8_t... tail>
constexpr bool TestConstexprULEB128Reader(ULEB128Reader<uint64_t>& aReader) {
  if (aReader.IsComplete()) {
    return false;
  }
  const bool isComplete = aReader.FeedByteIsComplete(byte);
  if (aReader.IsComplete() != isComplete) {
    return false;
  }
  if constexpr (sizeof...(tail) == 0) {
    return isComplete;
  } else {
    if (isComplete) {
      return false;
    }
    return TestConstexprULEB128Reader<tail...>(aReader);
  }
}

template <uint64_t expected, uint8_t... bytes>
constexpr bool TestConstexprULEB128Reader() {
  ULEB128Reader<uint64_t> reader;
  if (!TestConstexprULEB128Reader<bytes...>(reader)) {
    return false;
  }
  if (!reader.IsComplete()) {
    return false;
  }
  if (reader.Value() != expected) {
    return false;
  }

  reader.Reset();
  if (!TestConstexprULEB128Reader<bytes...>(reader)) {
    return false;
  }
  if (!reader.IsComplete()) {
    return false;
  }
  if (reader.Value() != expected) {
    return false;
  }

  return true;
}

static_assert(TestConstexprULEB128Reader<0x0u, 0x0u>());
static_assert(!TestConstexprULEB128Reader<0x0u, 0x0u, 0x0u>());
static_assert(TestConstexprULEB128Reader<0x1u, 0x1u>());
static_assert(TestConstexprULEB128Reader<0x7Fu, 0x7Fu>());
static_assert(TestConstexprULEB128Reader<0x80u, 0x80u, 0x01u>());
static_assert(!TestConstexprULEB128Reader<0x80u, 0x80u>());
static_assert(!TestConstexprULEB128Reader<0x80u, 0x01u>());
static_assert(TestConstexprULEB128Reader<0x81u, 0x81u, 0x01u>());
static_assert(TestConstexprULEB128Reader<0xFFu, 0xFFu, 0x01u>());
static_assert(TestConstexprULEB128Reader<0x100u, 0x80u, 0x02u>());
static_assert(TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu,
                                         0xFFu, 0x0Fu>());
static_assert(
    !TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu>());
static_assert(!TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu,
                                          0xFFu, 0xFFu, 0x0Fu>());
static_assert(
    TestConstexprULEB128Reader<0xFFFFFFFFFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
                               0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0x01u>());
static_assert(
    !TestConstexprULEB128Reader<0xFFFFFFFFFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
                                0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu>());

static void TestChunk() {
  printf("TestChunk...\n");

  static_assert(!std::is_default_constructible_v<ProfileBufferChunk>,
                "ProfileBufferChunk should not be default-constructible");
  static_assert(
      !std::is_constructible_v<ProfileBufferChunk, ProfileBufferChunk::Length>,
      "ProfileBufferChunk should not be constructible from Length");

  static_assert(
      sizeof(ProfileBufferChunk::Header) ==
          sizeof(ProfileBufferChunk::Header::mOffsetFirstBlock) +
              sizeof(ProfileBufferChunk::Header::mOffsetPastLastBlock) +
              sizeof(ProfileBufferChunk::Header::mStartTimeStamp) +
              sizeof(ProfileBufferChunk::Header::mDoneTimeStamp) +
              sizeof(ProfileBufferChunk::Header::mBufferBytes) +
              sizeof(ProfileBufferChunk::Header::mBlockCount) +
              sizeof(ProfileBufferChunk::Header::mRangeStart) +
              sizeof(ProfileBufferChunk::Header::mProcessId) +
              sizeof(ProfileBufferChunk::Header::mPADDING),
      "ProfileBufferChunk::Header may have unwanted padding, please review");
  // Note: The above static_assert is an attempt at keeping
  // ProfileBufferChunk::Header tightly packed, but some changes could make this
  // impossible to achieve (most probably due to alignment) -- Just do your
  // best!

  constexpr ProfileBufferChunk::Length TestLen = 1000;

  // Basic allocations of different sizes.
  for (ProfileBufferChunk::Length len = 0; len <= TestLen; ++len) {
    auto chunk = ProfileBufferChunk::Create(len);
    static_assert(
        std::is_same_v<decltype(chunk), UniquePtr<ProfileBufferChunk>>,
        "ProfileBufferChunk::Create() should return a "
        "UniquePtr");
    MOZ_RELEASE_ASSERT(!!chunk, "OOM!?");
    MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= len);
    MOZ_RELEASE_ASSERT(chunk->ChunkBytes() >=
                       len + ProfileBufferChunk::SizeofChunkMetadata());
    MOZ_RELEASE_ASSERT(chunk->RemainingBytes() == chunk->BufferBytes());
    MOZ_RELEASE_ASSERT(chunk->OffsetFirstBlock() == 0);
    MOZ_RELEASE_ASSERT(chunk->OffsetPastLastBlock() == 0);
    MOZ_RELEASE_ASSERT(chunk->BlockCount() == 0);
    MOZ_RELEASE_ASSERT(chunk->ProcessId() == 0);
    MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0);
    MOZ_RELEASE_ASSERT(chunk->BufferSpan().LengthBytes() ==
                       chunk->BufferBytes());
    MOZ_RELEASE_ASSERT(!chunk->GetNext());
    MOZ_RELEASE_ASSERT(!chunk->ReleaseNext());
    MOZ_RELEASE_ASSERT(chunk->Last() == chunk.get());
  }

  // Allocate the main test Chunk.
  auto chunkA = ProfileBufferChunk::Create(TestLen);
  MOZ_RELEASE_ASSERT(!!chunkA, "OOM!?");
  MOZ_RELEASE_ASSERT(chunkA->BufferBytes() >= TestLen);
  MOZ_RELEASE_ASSERT(chunkA->ChunkBytes() >=
                     TestLen + ProfileBufferChunk::SizeofChunkMetadata());
  MOZ_RELEASE_ASSERT(!chunkA->GetNext());
  MOZ_RELEASE_ASSERT(!chunkA->ReleaseNext());

  constexpr ProfileBufferIndex chunkARangeStart = 12345;
  chunkA->SetRangeStart(chunkARangeStart);
  MOZ_RELEASE_ASSERT(chunkA->RangeStart() == chunkARangeStart);

  // Get a read-only span over its buffer.
  auto bufferA = chunkA->BufferSpan();
  static_assert(
      std::is_same_v<decltype(bufferA), Span<const ProfileBufferChunk::Byte>>,
      "BufferSpan() should return a Span");
  MOZ_RELEASE_ASSERT(bufferA.LengthBytes() == chunkA->BufferBytes());

  // Add the initial tail block.
  constexpr ProfileBufferChunk::Length initTailLen = 10;
  auto initTail = chunkA->ReserveInitialBlockAsTail(initTailLen);
  static_assert(
      std::is_same_v<decltype(initTail), Span<ProfileBufferChunk::Byte>>,
      "ReserveInitialBlockAsTail() should return a Span");
  MOZ_RELEASE_ASSERT(initTail.LengthBytes() == initTailLen);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=93 G=92

¤ Dauer der Verarbeitung: 0.28 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.