// Copyright 2021 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "snapshot/ios/process_snapshot_ios_intermediate_dump.h"

#include <mach-o/loader.h>

#include "base/cxx17_backports.h"
#include "base/files/scoped_file.h"
#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h"
#include "client/annotation.h"
#include "gtest/gtest.h"
#include "minidump/minidump_file_writer.h"
#include "test/errors.h"
#include "test/scoped_temp_dir.h"
#include "test/test_paths.h"
#include "util/file/file_io.h"
#include "util/file/filesystem.h"
#include "util/file/string_file.h"
#include "util/misc/uuid.h"

namespace crashpad {
namespace test {
namespace {

using Key = internal::IntermediateDumpKey;
using internal::IOSIntermediateDumpWriter;
using internal::ProcessSnapshotIOSIntermediateDump;

class ReadToString : public crashpad::MemorySnapshot::Delegate {
 public:
  std::string result;

  bool MemorySnapshotDelegateRead(void* data, size_t size) override {
    result = std::string(reinterpret_cast<const char*>(data), size);
    return true;
  }
};

class ProcessSnapshotIOSIntermediateDumpTest : public testing::Test {
 protected:
  // testing::Test:

  void SetUp() override {
    path_ = temp_dir_.path().Append("dump_file");
    writer_ = std::make_unique<internal::IOSIntermediateDumpWriter>();
    EXPECT_TRUE(writer_->Open(path_));
    ASSERT_TRUE(IsRegularFile(path_));
  }

  void TearDown() override {
    writer_.reset();
    EXPECT_FALSE(IsRegularFile(path_));
  }

  const auto& path() const { return path_; }
  const auto& annotations() const { return annotations_; }
  auto writer() const { return writer_.get(); }

  bool DumpSnapshot(const ProcessSnapshotIOSIntermediateDump& snapshot) {
    MinidumpFileWriter minidump;
    minidump.InitializeFromSnapshot(&snapshot);
    StringFile string_file;
    return minidump.WriteEverything(&string_file);
  }

  void WriteProcessInfo(IOSIntermediateDumpWriter* writer) {
    IOSIntermediateDumpWriter::ScopedMap map(writer, Key::kProcessInfo);
    pid_t pid = 2;
    pid_t parent = 1;
    EXPECT_TRUE(writer->AddProperty(Key::kPID, &pid));
    EXPECT_TRUE(writer->AddProperty(Key::kParentPID, &parent));
    timeval start_time = {12, 0};
    EXPECT_TRUE(writer->AddProperty(Key::kStartTime, &start_time));

    time_value_t user_time = {20, 0};
    time_value_t system_time = {30, 0};
    {
      IOSIntermediateDumpWriter::ScopedMap taskInfo(writer,
                                                    Key::kTaskBasicInfo);
      EXPECT_TRUE(writer->AddProperty(Key::kUserTime, &user_time));
      EXPECT_TRUE(writer->AddProperty(Key::kSystemTime, &system_time));
    }
    {
      IOSIntermediateDumpWriter::ScopedMap taskThreadTimesMap(
          writer, Key::kTaskThreadTimes);
      writer->AddProperty(Key::kUserTime, &user_time);
      writer->AddProperty(Key::kSystemTime, &system_time);
    }

    timeval snapshot_time = {42, 0};
    writer->AddProperty(Key::kSnapshotTime, &snapshot_time);
  }

  void WriteSystemInfo(IOSIntermediateDumpWriter* writer) {
    IOSIntermediateDumpWriter::ScopedMap map(writer, Key::kSystemInfo);
    std::string machine_description = "Gibson";
    EXPECT_TRUE(writer->AddProperty(Key::kMachineDescription,
                                    machine_description.c_str(),
                                    machine_description.length()));
    int os_version_major = 1995;
    int os_version_minor = 9;
    int os_version_bugfix = 15;
    EXPECT_TRUE(writer->AddProperty(Key::kOSVersionMajor, &os_version_major));
    EXPECT_TRUE(writer->AddProperty(Key::kOSVersionMinor, &os_version_minor));
    EXPECT_TRUE(writer->AddProperty(Key::kOSVersionBugfix, &os_version_bugfix));
    std::string os_version_build = "Da Vinci";
    writer->AddProperty(Key::kOSVersionBuild,
                        os_version_build.c_str(),
                        os_version_build.length());

    int cpu_count = 1;
    EXPECT_TRUE(writer->AddProperty(Key::kCpuCount, &cpu_count));
    std::string cpu_vendor = "RISC";
    EXPECT_TRUE(writer->AddProperty(
        Key::kCpuVendor, cpu_vendor.c_str(), cpu_vendor.length()));

    bool has_daylight_saving_time = true;
    EXPECT_TRUE(writer->AddProperty(Key::kHasDaylightSavingTime,
                                    &has_daylight_saving_time));
    bool is_daylight_saving_time = true;
    EXPECT_TRUE(writer->AddProperty(Key::kIsDaylightSavingTime,
                                    &is_daylight_saving_time));
    int standard_offset_seconds = 7200;
    EXPECT_TRUE(writer->AddProperty(Key::kStandardOffsetSeconds,
                                    &standard_offset_seconds));
    int daylight_offset_seconds = 3600;
    EXPECT_TRUE(writer->AddProperty(Key::kDaylightOffsetSeconds,
                                    &daylight_offset_seconds));
    std::string standard_name = "Standard";
    EXPECT_TRUE(writer->AddProperty(
        Key::kStandardName, standard_name.c_str(), standard_name.length()));
    std::string daylight_name = "Daylight";
    EXPECT_TRUE(writer->AddProperty(
        Key::kDaylightName, daylight_name.c_str(), daylight_name.length()));

    vm_size_t page_size = getpagesize();
    EXPECT_TRUE(writer->AddProperty(Key::kPageSize, &page_size));
    {
      natural_t count = 0;
      IOSIntermediateDumpWriter::ScopedMap vmStatMap(writer, Key::kVMStat);
      EXPECT_TRUE(writer->AddProperty(Key::kActive, &count));
      EXPECT_TRUE(writer->AddProperty(Key::kInactive, &count));
      EXPECT_TRUE(writer->AddProperty(Key::kWired, &count));
      EXPECT_TRUE(writer->AddProperty(Key::kFree, &count));
    }
  }

  void WriteAnnotations(IOSIntermediateDumpWriter* writer) {
    constexpr char annotation_name[] = "annotation_name";
    constexpr char annotation_value[] = "annotation_value";
    {
      IOSIntermediateDumpWriter::ScopedArray annotationObjectArray(
          writer, Key::kAnnotationObjects);
      {
        IOSIntermediateDumpWriter::ScopedArrayMap annotationMap(writer);
        EXPECT_TRUE(writer->AddPropertyBytes(
            Key::kAnnotationName, annotation_name, strlen(annotation_name)));
        EXPECT_TRUE(writer->AddPropertyBytes(
            Key::kAnnotationValue, annotation_value, strlen(annotation_value)));
        Annotation::Type type = Annotation::Type::kString;
        EXPECT_TRUE(writer->AddProperty(Key::kAnnotationType, &type));
      }
    }
    {
      IOSIntermediateDumpWriter::ScopedArray annotationsSimpleArray(
          writer, Key::kAnnotationsSimpleMap);
      {
        IOSIntermediateDumpWriter::ScopedArrayMap annotationMap(writer);
        EXPECT_TRUE(writer->AddPropertyBytes(
            Key::kAnnotationName, annotation_name, strlen(annotation_name)));
        EXPECT_TRUE(writer->AddPropertyBytes(
            Key::kAnnotationValue, annotation_value, strlen(annotation_value)));
      }
    }

    IOSIntermediateDumpWriter::ScopedMap annotationMap(
        writer, Key::kAnnotationsCrashInfo);
    {
      EXPECT_TRUE(writer->AddPropertyBytes(Key::kAnnotationsCrashInfoMessage1,
                                           annotation_value,
                                           strlen(annotation_value)));
      EXPECT_TRUE(writer->AddPropertyBytes(Key::kAnnotationsCrashInfoMessage2,
                                           annotation_value,
                                           strlen(annotation_value)));
    }
  }

  void WriteModules(IOSIntermediateDumpWriter* writer) {
    IOSIntermediateDumpWriter::ScopedArray moduleArray(writer, Key::kModules);
    for (uint32_t image_index = 0; image_index < 2; ++image_index) {
      IOSIntermediateDumpWriter::ScopedArrayMap modules(writer);

      constexpr char image_file[] = "/path/to/module";
      EXPECT_TRUE(
          writer->AddProperty(Key::kName, image_file, strlen(image_file)));

      uint64_t address = 0;
      uint64_t vmsize = 1;
      uintptr_t imageFileModDate = 2;
      uint32_t current_version = 3;
      uint32_t filetype = MH_DYLIB;
      uint64_t source_version = 5;
      static constexpr uint8_t uuid[16] = {0x00,
                                           0x01,
                                           0x02,
                                           0x03,
                                           0x04,
                                           0x05,
                                           0x06,
                                           0x07,
                                           0x08,
                                           0x09,
                                           0x0a,
                                           0x0b,
                                           0x0c,
                                           0x0d,
                                           0x0e,
                                           0x0f};
      EXPECT_TRUE(writer->AddProperty(Key::kAddress, &address));
      EXPECT_TRUE(writer->AddProperty(Key::kSize, &vmsize));
      EXPECT_TRUE(writer->AddProperty(Key::kTimestamp, &imageFileModDate));
      EXPECT_TRUE(
          writer->AddProperty(Key::kDylibCurrentVersion, &current_version));
      EXPECT_TRUE(writer->AddProperty(Key::kSourceVersion, &source_version));
      EXPECT_TRUE(writer->AddProperty(Key::kUUID, &uuid));
      EXPECT_TRUE(writer->AddProperty(Key::kFileType, &filetype));
      WriteAnnotations(writer);
    }
  }

  void ExpectModules(const std::vector<const ModuleSnapshot*>& modules) {
    for (auto module : modules) {
      EXPECT_EQ(module->GetModuleType(),
                ModuleSnapshot::kModuleTypeSharedLibrary);
      EXPECT_STREQ(module->Name().c_str(), "/path/to/module");
      EXPECT_STREQ(module->DebugFileName().c_str(), "module");
      UUID uuid;
      uint32_t age;
      module->UUIDAndAge(&uuid, &age);
      EXPECT_EQ(uuid.ToString(), "00010203-0405-0607-0809-0a0b0c0d0e0f");

      for (auto annotation : module->AnnotationsVector()) {
        EXPECT_STREQ(annotation.c_str(), "annotation_value");
      }

      for (const auto& it : module->AnnotationsSimpleMap()) {
        EXPECT_STREQ(it.first.c_str(), "annotation_name");
        EXPECT_STREQ(it.second.c_str(), "annotation_value");
      }

      for (auto annotation_object : module->AnnotationObjects()) {
        EXPECT_STREQ(annotation_object.name.c_str(), "annotation_name");
        EXPECT_EQ(annotation_object.type, (short)Annotation::Type::kString);
        EXPECT_STREQ(std::string(reinterpret_cast<const char*>(
                                     annotation_object.value.data()),
                                 annotation_object.value.size())
                         .c_str(),
                     "annotation_value");
      }
    }
  }

  void WriteMachException(IOSIntermediateDumpWriter* writer,
                          bool short_context = false) {
    IOSIntermediateDumpWriter::ScopedMap machExceptionMap(writer,
                                                          Key::kMachException);
    exception_type_t exception = 5;
    mach_exception_data_type_t code[] = {4, 3};
    mach_msg_type_number_t code_count = 2;

#if defined(ARCH_CPU_X86_64)
    thread_state_flavor_t flavor = x86_THREAD_STATE;
    x86_thread_state_t state = {};
    state.tsh.flavor = x86_THREAD_STATE64;
    state.tsh.count = x86_THREAD_STATE64_COUNT;
    state.uts.ts64.__rip = 0xdeadbeef;
    size_t state_length = sizeof(x86_thread_state_t);
#elif defined(ARCH_CPU_ARM64)
    thread_state_flavor_t flavor = ARM_UNIFIED_THREAD_STATE;
    arm_unified_thread_state_t state = {};
    state.ash.flavor = ARM_THREAD_STATE64;
    state.ash.count = ARM_THREAD_STATE64_COUNT;
    state.ts_64.__pc = 0xdeadbeef;
    size_t state_length = sizeof(arm_unified_thread_state_t);
#endif
    EXPECT_TRUE(writer->AddProperty(Key::kException, &exception));
    EXPECT_TRUE(writer->AddProperty(Key::kCodes, code, code_count));
    EXPECT_TRUE(writer->AddProperty(Key::kFlavor, &flavor));

    if (short_context) {
      state_length -= 10;
    }
    EXPECT_TRUE(writer->AddPropertyBytes(
        Key::kState, reinterpret_cast<const void*>(&state), state_length));
    uint64_t thread_id = 1;
    EXPECT_TRUE(writer->AddProperty(Key::kThreadID, &thread_id));
  }

  void WriteThreads(IOSIntermediateDumpWriter* writer) {
    vm_address_t stack_region_address = 0;
    IOSIntermediateDumpWriter::ScopedArray threadArray(writer, Key::kThreads);
    for (uint64_t thread_id = 1; thread_id < 3; thread_id++) {
      IOSIntermediateDumpWriter::ScopedArrayMap threadMap(writer);
      EXPECT_TRUE(writer->AddProperty(Key::kThreadID, &thread_id));

      integer_t suspend_count = 666;
      integer_t importance = 5;
      uint64_t thread_handle = thread_id;
      EXPECT_TRUE(writer->AddProperty(Key::kSuspendCount, &suspend_count));
      EXPECT_TRUE(writer->AddProperty(Key::kPriority, &importance));
      EXPECT_TRUE(writer->AddProperty(Key::kThreadDataAddress, &thread_handle));

#if defined(ARCH_CPU_X86_64)
      x86_thread_state64_t thread_state = {};
      thread_state.__rip = 0xdeadbeef;
      x86_float_state64_t float_state = {};
      x86_debug_state64_t debug_state = {};
#elif defined(ARCH_CPU_ARM64)
      arm_thread_state64_t thread_state = {};
      thread_state.__pc = 0xdeadbeef;
      arm_neon_state64_t float_state = {};
      arm_debug_state64_t debug_state = {};
#endif
      EXPECT_TRUE(writer->AddProperty(Key::kThreadState, &thread_state));
      EXPECT_TRUE(writer->AddProperty(Key::kFloatState, &float_state));
      EXPECT_TRUE(writer->AddProperty(Key::kDebugState, &debug_state));

      // Non-overlapping stack_region_address.
      stack_region_address += 10;
      EXPECT_TRUE(
          writer->AddProperty(Key::kStackRegionAddress, &stack_region_address));
      EXPECT_TRUE(
          writer->AddPropertyBytes(Key::kStackRegionData, "stack_data", 10));
      {
        IOSIntermediateDumpWriter::ScopedArray memoryRegions(
            writer, Key::kThreadContextMemoryRegions);
        {
          IOSIntermediateDumpWriter::ScopedArrayMap memoryRegion(writer);
          const vm_address_t memory_region_address = 0;
          EXPECT_TRUE(writer->AddProperty(
              Key::kThreadContextMemoryRegionAddress, &memory_region_address));
          EXPECT_TRUE(writer->AddPropertyBytes(
              Key::kThreadContextMemoryRegionData, "string", 6));
        }
      }
    }
  }

  void ExpectMachException(const ExceptionSnapshot& exception) {
    EXPECT_EQ(exception.ThreadID(), 1u);
    EXPECT_EQ(exception.Exception(), 5u);
    EXPECT_TRUE(exception.Context()->Is64Bit());
    EXPECT_EQ(exception.Context()->InstructionPointer(), 0xdeadbeef);
    EXPECT_EQ(exception.ExceptionInfo(), 4u);
    EXPECT_EQ(exception.ExceptionAddress(), 0xdeadbeef);
    EXPECT_EQ(exception.Codes()[0], 5u);
    EXPECT_EQ(exception.Codes()[1], 4u);
    EXPECT_EQ(exception.Codes()[2], 3u);
  }

  void ExpectThreads(const std::vector<const ThreadSnapshot*>& threads) {
    uint64_t thread_id = 1;
    for (auto thread : threads) {
      EXPECT_EQ(thread->ThreadID(), thread_id);
      EXPECT_EQ(thread->SuspendCount(), 666);
      EXPECT_EQ(thread->Priority(), 5);
      EXPECT_EQ(thread->ThreadSpecificDataAddress(), thread_id++);
      ReadToString delegate;
      for (auto memory : thread->ExtraMemory()) {
        memory->Read(&delegate);
        EXPECT_STREQ(delegate.result.c_str(), "string");
      }

      thread->Stack()->Read(&delegate);
      EXPECT_STREQ(delegate.result.c_str(), "stack_data");

      EXPECT_TRUE(thread->Context()->Is64Bit());
      EXPECT_EQ(thread->Context()->InstructionPointer(), 0xdeadbeef);
    }
  }

  void ExpectSystem(const SystemSnapshot& system) {
    EXPECT_EQ(system.CPUCount(), 1u);
    EXPECT_STREQ(system.CPUVendor().c_str(), "RISC");
    int major;
    int minor;
    int bugfix;
    std::string build;
    system.OSVersion(&major, &minor, &bugfix, &build);
    EXPECT_EQ(major, 1995);
    EXPECT_EQ(minor, 9);
    EXPECT_EQ(bugfix, 15);
    EXPECT_STREQ(build.c_str(), "Da Vinci");
    EXPECT_STREQ(system.OSVersionFull().c_str(), "1995.9.15 Da Vinci");
    EXPECT_STREQ(system.MachineDescription().c_str(), "Gibson");

    SystemSnapshot::DaylightSavingTimeStatus dst_status;
    int standard_offset_seconds;
    int daylight_offset_seconds;
    std::string standard_name;
    std::string daylight_name;

    system.TimeZone(&dst_status,
                    &standard_offset_seconds,
                    &daylight_offset_seconds,
                    &standard_name,
                    &daylight_name);
    EXPECT_EQ(standard_offset_seconds, 7200);
    EXPECT_EQ(daylight_offset_seconds, 3600);
    EXPECT_STREQ(standard_name.c_str(), "Standard");
    EXPECT_STREQ(daylight_name.c_str(), "Daylight");
  }

  void ExpectSnapshot(const ProcessSnapshot& snapshot) {
    EXPECT_EQ(snapshot.ProcessID(), 2);
    EXPECT_EQ(snapshot.ParentProcessID(), 1);

    timeval snapshot_time;
    snapshot.SnapshotTime(&snapshot_time);
    EXPECT_EQ(snapshot_time.tv_sec, 42);
    EXPECT_EQ(snapshot_time.tv_usec, 0);

    timeval start_time;
    snapshot.ProcessStartTime(&start_time);
    EXPECT_EQ(start_time.tv_sec, 12);
    EXPECT_EQ(start_time.tv_usec, 0);

    timeval user_time, system_time;
    snapshot.ProcessCPUTimes(&user_time, &system_time);
    EXPECT_EQ(user_time.tv_sec, 40);
    EXPECT_EQ(user_time.tv_usec, 0);
    EXPECT_EQ(system_time.tv_sec, 60);
    EXPECT_EQ(system_time.tv_usec, 0);

    ExpectSystem(*snapshot.System());
    ExpectThreads(snapshot.Threads());
    ExpectModules(snapshot.Modules());
    ExpectMachException(*snapshot.Exception());
  }

 private:
  std::unique_ptr<internal::IOSIntermediateDumpWriter> writer_;
  ScopedTempDir temp_dir_;
  base::FilePath path_;
  std::map<std::string, std::string> annotations_;
};

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, InitializeNoFile) {
  const base::FilePath file;
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  EXPECT_FALSE(process_snapshot.InitializeWithFilePath(file, annotations()));
  EXPECT_TRUE(LoggingRemoveFile(path()));
  EXPECT_FALSE(IsRegularFile(path()));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, InitializeEmpty) {
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  EXPECT_FALSE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, InitializeMinimumDump) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    { IOSIntermediateDumpWriter::ScopedMap map(writer(), Key::kSystemInfo); }
    { IOSIntermediateDumpWriter::ScopedMap map(writer(), Key::kProcessInfo); }
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
  EXPECT_TRUE(DumpSnapshot(process_snapshot));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, MissingSystemDump) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    { IOSIntermediateDumpWriter::ScopedMap map(writer(), Key::kProcessInfo); }
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_FALSE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, MissingProcessDump) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    { IOSIntermediateDumpWriter::ScopedMap map(writer(), Key::kSystemInfo); }
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_FALSE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, EmptySignalDump) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    WriteSystemInfo(writer());
    WriteProcessInfo(writer());
    {
      IOSIntermediateDumpWriter::ScopedMap map(writer(), Key::kSignalException);
      uint64_t thread_id = 1;
      EXPECT_TRUE(writer()->AddProperty(Key::kThreadID, &thread_id));
    }
    {
      IOSIntermediateDumpWriter::ScopedArray threadArray(writer(),
                                                         Key::kThreads);
      IOSIntermediateDumpWriter::ScopedArrayMap threadMap(writer());
      uint64_t thread_id = 1;
      writer()->AddProperty(Key::kThreadID, &thread_id);
    }
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
  EXPECT_TRUE(DumpSnapshot(process_snapshot));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, EmptyMachDump) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    WriteSystemInfo(writer());
    WriteProcessInfo(writer());
    {
      IOSIntermediateDumpWriter::ScopedMap map(writer(), Key::kMachException);
      uint64_t thread_id = 1;
      EXPECT_TRUE(writer()->AddProperty(Key::kThreadID, &thread_id));
    }
    {
      IOSIntermediateDumpWriter::ScopedArray threadArray(writer(),
                                                         Key::kThreads);
      IOSIntermediateDumpWriter::ScopedArrayMap threadMap(writer());
      uint64_t thread_id = 1;
      writer()->AddProperty(Key::kThreadID, &thread_id);
    }
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
  EXPECT_TRUE(DumpSnapshot(process_snapshot));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, EmptyExceptionDump) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    WriteSystemInfo(writer());
    WriteProcessInfo(writer());
    {
      IOSIntermediateDumpWriter::ScopedMap map(writer(), Key::kNSException);
      uint64_t thread_id = 1;
      EXPECT_TRUE(writer()->AddProperty(Key::kThreadID, &thread_id));
    }
    {
      IOSIntermediateDumpWriter::ScopedArray threadArray(writer(),
                                                         Key::kThreads);
      IOSIntermediateDumpWriter::ScopedArrayMap threadMap(writer());
      uint64_t thread_id = 1;
      writer()->AddProperty(Key::kThreadID, &thread_id);
    }
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
  EXPECT_TRUE(DumpSnapshot(process_snapshot));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, EmptyUncaughtNSExceptionDump) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    WriteSystemInfo(writer());
    WriteProcessInfo(writer());
    {
      IOSIntermediateDumpWriter::ScopedMap map(writer(), Key::kNSException);
      uint64_t thread_id = 1;
      EXPECT_TRUE(writer()->AddProperty(Key::kThreadID, &thread_id));
    }
    {
      IOSIntermediateDumpWriter::ScopedArray threadArray(writer(),
                                                         Key::kThreads);
      IOSIntermediateDumpWriter::ScopedArrayMap threadMap(writer());
      uint64_t thread_id = 1;
      writer()->AddProperty(Key::kThreadID, &thread_id);
      const uint64_t frames[] = {0, 0};
      const size_t num_frames = 2;
      writer()->AddProperty(
          Key::kThreadUncaughtNSExceptionFrames, frames, num_frames);
    }
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
  EXPECT_TRUE(DumpSnapshot(process_snapshot));
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, ShortContext) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    WriteSystemInfo(writer());
    WriteProcessInfo(writer());
    WriteThreads(writer());
    WriteModules(writer());
    WriteMachException(writer(), true /* short_context=true*/);
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
  EXPECT_TRUE(DumpSnapshot(process_snapshot));
  ExpectSnapshot(process_snapshot);
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, FullReport) {
  {
    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer());
    uint8_t version = 1;
    EXPECT_TRUE(writer()->AddProperty(Key::kVersion, &version));
    WriteSystemInfo(writer());
    WriteProcessInfo(writer());
    WriteThreads(writer());
    WriteModules(writer());
    WriteMachException(writer());
  }
  ProcessSnapshotIOSIntermediateDump process_snapshot;
  ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
  EXPECT_FALSE(IsRegularFile(path()));
  EXPECT_TRUE(DumpSnapshot(process_snapshot));
  ExpectSnapshot(process_snapshot);
}

TEST_F(ProcessSnapshotIOSIntermediateDumpTest, FuzzTestCases) {
  base::FilePath fuzz_path = TestPaths::TestDataRoot().Append(FILE_PATH_LITERAL(
      "snapshot/ios/testdata/crash-1fa088dda0adb41459d063078a0f384a0bb8eefa"));
  crashpad::internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
  EXPECT_TRUE(process_snapshot.InitializeWithFilePath(fuzz_path, {}));
  EXPECT_TRUE(LoggingRemoveFile(path()));

  auto map = process_snapshot.AnnotationsSimpleMap();
  ASSERT_TRUE(map.find("crashpad_intermediate_dump_incomplete") != map.end());
  EXPECT_EQ(map["crashpad_intermediate_dump_incomplete"], "yes");

  fuzz_path = TestPaths::TestDataRoot().Append(
      FILE_PATH_LITERAL("snapshot/ios/testdata/crash-5726011582644224"));
  crashpad::internal::ProcessSnapshotIOSIntermediateDump process_snapshot2;
  EXPECT_TRUE(process_snapshot2.InitializeWithFilePath(fuzz_path, {}));
  map = process_snapshot2.AnnotationsSimpleMap();
  ASSERT_TRUE(map.find("crashpad_intermediate_dump_incomplete") != map.end());
  EXPECT_EQ(map["crashpad_intermediate_dump_incomplete"], "yes");
}

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