/* -*- 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/. */

/*
 * Test many combinations of data structures (both in argument and return
 * position) to ensure they are transmitted correctly.
 */

#include "gtest/gtest.h"

#include "mozilla/_ipdltest/IPDLUnitTest.h"
#include "mozilla/_ipdltest/PTestDataStructuresChild.h"
#include "mozilla/_ipdltest/PTestDataStructuresParent.h"
#include "mozilla/_ipdltest/PTestDataStructuresSubChild.h"
#include "mozilla/_ipdltest/PTestDataStructuresSubParent.h"

using namespace mozilla::ipc;

using RegionArray = nsTArray<nsIntRegion>;

namespace mozilla::_ipdltest {

static const uint32_t nactors = 10;

class TestDataStructuresSubParent : public PTestDataStructuresSubParent {
  NS_INLINE_DECL_REFCOUNTING(TestDataStructuresSubParent, override)

 public:
  explicit TestDataStructuresSubParent(uint32_t i) : mI(i) {}
  uint32_t mI;

 private:
  ~TestDataStructuresSubParent() = default;
};

class TestDataStructuresSubChild : public PTestDataStructuresSubChild {
  NS_INLINE_DECL_REFCOUNTING(TestDataStructuresSubChild, override)

 public:
  explicit TestDataStructuresSubChild(uint32_t i) : mI(i) {}
  uint32_t mI;

 private:
  ~TestDataStructuresSubChild() = default;
};

inline static TestDataStructuresSubParent& Cast(
    PTestDataStructuresSubParent* a) {
  return *static_cast<TestDataStructuresSubParent*>(a);
}

class TestDataStructuresParent : public PTestDataStructuresParent {
  NS_INLINE_DECL_REFCOUNTING(TestDataStructuresParent, override)

 public:
  nsTArray<NotNull<
      SideVariant<PTestDataStructuresSubParent*, PTestDataStructuresSubChild*>>>
      kids;

 private:
  IPCResult RecvTestArrayOfInt(nsTArray<int>&& ia,
                               nsTArray<int>* oa) final override {
    EXPECT_EQ(5u, ia.Length());
    for (int i = 0; i < 5; ++i) EXPECT_EQ(i, ia[i]);

    *oa = std::move(ia);

    return IPC_OK();
  }

  IPCResult RecvTestArrayOfActor(
      nsTArray<NotNull<PTestDataStructuresSubParent*>>&& i1,
      nsTArray<NotNull<PTestDataStructuresSubParent*>>* o1) final override {
    EXPECT_EQ(nactors, i1.Length());
    for (uint32_t i = 0; i < i1.Length(); ++i) EXPECT_EQ(i, Cast(i1[i]).mI);
    *o1 = std::move(i1);
    return IPC_OK();
  }

  IPCResult RecvTestUnion(const IntDouble& i1, const IntDouble& i2,
                          IntDouble* o1, IntDouble* o2) final override {
    EXPECT_EQ(42, i1.get_int());
    EXPECT_EQ(4.0, i2.get_double());

    *o1 = i1;
    *o2 = i2;

    return IPC_OK();
  }

  IPCResult RecvTestArrayOfUnion(nsTArray<IntDouble>&& i1,
                                 nsTArray<IntDouble>* o1) final override {
    EXPECT_EQ(4u, i1.Length());
    EXPECT_EQ(1, i1[0].get_int());
    EXPECT_EQ(2.0, i1[1].get_double());
    EXPECT_EQ(3, i1[2].get_int());
    EXPECT_EQ(4.0, i1[3].get_double());

    *o1 = std::move(i1);

    return IPC_OK();
  }

  IPCResult RecvTestUnionWithArray(const IntDoubleArrays& i1,
                                   const IntDoubleArrays& i2,
                                   const IntDoubleArrays& i3,
                                   IntDoubleArrays* o1, IntDoubleArrays* o2,
                                   IntDoubleArrays* o3) final override {
    EXPECT_EQ(42, i1.get_int());

    const nsTArray<int>& i2a = i2.get_ArrayOfint();
    EXPECT_EQ(3u, i2a.Length());
    EXPECT_EQ(1, i2a[0]);
    EXPECT_EQ(2, i2a[1]);
    EXPECT_EQ(3, i2a[2]);

    const nsTArray<double>& i3a = i3.get_ArrayOfdouble();
    EXPECT_EQ(3u, i3a.Length());
    EXPECT_EQ(1.0, i3a[0]);
    EXPECT_EQ(2.0, i3a[1]);
    EXPECT_EQ(3.0, i3a[2]);

    *o1 = i1;
    *o2 = i2a;
    *o3 = i3a;

    return IPC_OK();
  }

  IPCResult RecvTestArrayOfUnionWithArray(
      nsTArray<IntDoubleArrays>&& i1,
      nsTArray<IntDoubleArrays>* o1) final override {
    EXPECT_EQ(3u, i1.Length());

    IntDoubleArrays id1(i1[0]);
    EXPECT_EQ(42, id1.get_int());

    nsTArray<int> i2a = i1[1].get_ArrayOfint().Clone();
    EXPECT_EQ(3u, i2a.Length());
    EXPECT_EQ(1, i2a[0]);
    EXPECT_EQ(2, i2a[1]);
    EXPECT_EQ(3, i2a[2]);

    nsTArray<double> i3a = i1[2].get_ArrayOfdouble().Clone();
    EXPECT_EQ(3u, i3a.Length());
    EXPECT_EQ(1.0, i3a[0]);
    EXPECT_EQ(2.0, i3a[1]);
    EXPECT_EQ(3.0, i3a[2]);

    o1->AppendElement(id1);
    o1->AppendElement(IntDoubleArrays(i2a));
    o1->AppendElement(IntDoubleArrays(i3a));

    return IPC_OK();
  }

  IPCResult RecvTestStructWithActor(const ActorWrapper& i1,
                                    ActorWrapper* o1) final override {
    EXPECT_FALSE(i1.actor().IsChild()) << "child side should be empty";

    EXPECT_EQ(i1.actor(), kids[0])
        << "should have got back same actor on parent side";

    o1->actor() = kids[0];
    return IPC_OK();
  }

  IPCResult RecvTestUnionWithActors(const Actors& i1, const Actors& i2,
                                    const Actors& i3, Actors* o1, Actors* o2,
                                    Actors* o3) final override {
    EXPECT_EQ(42, i1.get_int());

    nsTArray<int> i2a = i2.get_ArrayOfint().Clone();
    EXPECT_EQ(3u, i2a.Length());
    EXPECT_EQ(1, i2a[0]);
    EXPECT_EQ(2, i2a[1]);
    EXPECT_EQ(3, i2a[2]);

    const auto& a = i3.get_ArrayOfPTestDataStructuresSub();
    EXPECT_EQ(a.Length(), kids.Length());
    for (size_t i = 0; i < a.Length(); ++i) {
      EXPECT_EQ(a[i], kids[i]);
    }

    *o1 = 42;
    *o2 = i2a;
    *o3 = kids.Clone();

    return IPC_OK();
  }

  IPCResult RecvTestArrayOfUnionWithActors(
      nsTArray<Actors>&& i1, nsTArray<Actors>* o1) final override {
    EXPECT_EQ(3u, i1.Length());
    EXPECT_EQ(42, i1[0].get_int());

    const nsTArray<int>& i2a = i1[1].get_ArrayOfint();
    EXPECT_EQ(3u, i2a.Length());
    EXPECT_EQ(1, i2a[0]);
    EXPECT_EQ(2, i2a[1]);
    EXPECT_EQ(3, i2a[2]);

    EXPECT_EQ(kids, i1[2].get_ArrayOfPTestDataStructuresSub());

    *o1 = std::move(i1);

    return IPC_OK();
  }

  IPCResult RecvTestUnions(const Unions& i1, const Unions& i2, const Unions& i3,
                           const Unions& i4, Unions* o1, Unions* o2, Unions* o3,
                           Unions* o4) final override {
    EXPECT_EQ(42, i1.get_int());

    const nsTArray<int>& i2a = i2.get_ArrayOfint();
    EXPECT_EQ(3u, i2a.Length());
    EXPECT_EQ(1, i2a[0]);
    EXPECT_EQ(2, i2a[1]);
    EXPECT_EQ(3, i2a[2]);

    EXPECT_EQ(kids, i3.get_ArrayOfPTestDataStructuresSub());

    const auto& i4a =
        i4.get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSub();
    EXPECT_EQ(kids, i4a);

    *o1 = i1;
    *o2 = i2;
    *o3 = i3;
    *o4 = i4;

    return IPC_OK();
  }

  IPCResult RecvTestArrayOfUnions(nsTArray<Unions>&& i1,
                                  nsTArray<Unions>* o1) final override {
    EXPECT_EQ(42, i1[0].get_int());

    const nsTArray<int>& i2a = i1[1].get_ArrayOfint();
    EXPECT_EQ(3u, i2a.Length());
    EXPECT_EQ(1, i2a[0]);
    EXPECT_EQ(2, i2a[1]);
    EXPECT_EQ(3, i2a[2]);

    EXPECT_EQ(kids, i1[2].get_ArrayOfPTestDataStructuresSub());

    const auto& i4a =
        i1[3].get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSub();
    EXPECT_EQ(kids, i4a);

    *o1 = std::move(i1);

    return IPC_OK();
  }

  IPCResult RecvTestStruct(const SIntDouble& i, SIntDouble* o) final override {
    EXPECT_EQ(1, i.i());
    EXPECT_EQ(2.0, i.d());
    *o = i;
    return IPC_OK();
  }

  IPCResult RecvTestStructWithArrays(const SIntDoubleArrays& i,
                                     SIntDoubleArrays* o) final override {
    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);

    nsTArray<double> ad;
    ad.AppendElement(.5);
    ad.AppendElement(1.0);
    ad.AppendElement(2.0);

    EXPECT_EQ(42, i.i());
    EXPECT_EQ(ai, i.ai());
    EXPECT_EQ(ad, i.ad());

    *o = i;

    return IPC_OK();
  }

  IPCResult RecvTestStructWithActors(const SActors& i,
                                     SActors* o) final override {
    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);

    EXPECT_EQ(42, i.i());
    EXPECT_EQ(ai, i.ai());
    EXPECT_EQ(kids, i.ap());

    *o = i;

    return IPC_OK();
  }

  IPCResult RecvTestStructs(const Structs& i, Structs* o) final override {
    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);

    EXPECT_EQ(42, i.i());
    EXPECT_EQ(ai, i.ai());
    EXPECT_EQ(kids, i.ap());

    const SActors& ia = i.aa()[0];
    EXPECT_EQ(42, ia.i());
    EXPECT_EQ(ai, ia.ai());
    EXPECT_EQ(kids, ia.ap());

    *o = i;

    return IPC_OK();
  }

  IPCResult RecvTestUnionWithStructs(
      const WithStructs& i1, const WithStructs& i2, const WithStructs& i3,
      const WithStructs& i4, const WithStructs& i5, WithStructs* o1,
      WithStructs* o2, WithStructs* o3, WithStructs* o4,
      WithStructs* o5) final override {
    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);

    EXPECT_EQ(i1, int(42));
    EXPECT_EQ(i2.get_ArrayOfint(), ai);
    EXPECT_EQ(i3.get_ArrayOfPTestDataStructuresSub(), kids);

    const SActors& ia = i4.get_ArrayOfSActors()[0];
    EXPECT_EQ(42, ia.i());
    EXPECT_EQ(ai, ia.ai());
    EXPECT_EQ(kids, ia.ap());

    const Structs& is = i5.get_ArrayOfStructs()[0];
    EXPECT_EQ(42, is.i());
    EXPECT_EQ(ai, is.ai());
    EXPECT_EQ(kids, is.ap());

    const SActors& isa = is.aa()[0];
    EXPECT_EQ(42, isa.i());
    EXPECT_EQ(ai, isa.ai());
    EXPECT_EQ(kids, isa.ap());

    *o1 = i1;
    *o2 = i2;
    *o3 = i3;
    *o4 = i4;
    *o5 = i5;

    return IPC_OK();
  }

  IPCResult RecvTestStructWithUnions(const WithUnions& i,
                                     WithUnions* o) final override {
    EXPECT_EQ(i.i(), 42);

    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);
    EXPECT_EQ(ai, i.ai());

    EXPECT_EQ(i.ap(), kids);

    EXPECT_EQ(kids, i.aa()[0].get_ArrayOfPTestDataStructuresSub());

    const nsTArray<Unions>& iau = i.au();
    EXPECT_EQ(iau[0], 42);
    EXPECT_EQ(ai, iau[1].get_ArrayOfint());
    EXPECT_EQ(kids, iau[2].get_ArrayOfPTestDataStructuresSub());
    EXPECT_EQ(
        kids,
        iau[3].get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSub());

    *o = i;

    return IPC_OK();
  }

  IPCResult RecvTestUnionWithCxx(nsTArray<Op>&& sa) final override {
    EXPECT_EQ(sa.Length(), (size_t)1);
    EXPECT_EQ(Op::TSetAttrs, sa[0].type());
    return IPC_OK();
  }

  IPCResult RecvTestNsIntRegion(RegionArray&& ra) final override {
    for (RegionArray::index_type i = 0; i < ra.Length(); ++i) {
      // if |ra| has been realloc()d and given a different allocator
      // chunk, this loop will nondeterministically crash or iloop.
      for (auto iter = ra[i].RectIter(); !iter.Done(); iter.Next()) {
        (void)iter.Get();
      }
    }
    return IPC_OK();
  }

  ~TestDataStructuresParent() = default;
};

class TestDataStructuresChild : public PTestDataStructuresChild {
  NS_INLINE_DECL_REFCOUNTING(TestDataStructuresChild, override)

 private:
  nsTArray<NotNull<
      SideVariant<PTestDataStructuresSubParent*, PTestDataStructuresSubChild*>>>
      kids;
  nsTArray<NotNull<PTestDataStructuresSubChild*>> kidsDirect;

  already_AddRefed<PTestDataStructuresSubChild>
  AllocPTestDataStructuresSubChild(const int& i) final override {
    auto child = MakeRefPtr<TestDataStructuresSubChild>(i);
    kids.AppendElement(WrapNotNull(child));
    kidsDirect.AppendElement(WrapNotNull(child));
    return child.forget();
  }

  IPCResult RecvStart() final override {
    TestArrayOfInt();
    TestArrayOfActor();
    TestUnion();
    TestArrayOfUnion();
    TestUnionWithArray();
    TestArrayOfUnionWithArray();
    TestStructWithActor();
    TestUnionWithActors();
    TestArrayOfUnionWithActors();
    TestUnions();
    TestArrayOfUnions();
    TestStruct();
    TestStructWithArrays();
    TestStructWithActors();
    TestStructs();
    TestUnionWithStructs();
    TestStructWithUnions();
    TestUnionWithCxx();
    TestNsIntRegion();

    auto actors = kidsDirect.Clone();
    for (auto& actor : actors) {
      EXPECT_TRUE(PTestDataStructuresSubChild::Send__delete__(actor));
    }

    Close();

    return IPC_OK();
  }

  void TestArrayOfInt() {
    nsTArray<int> ia;

    for (int i = 0; i < 5; ++i) ia.AppendElement(i);

    nsTArray<int> oa;
    EXPECT_TRUE(SendTestArrayOfInt(ia, &oa));

    EXPECT_EQ(ia, oa);
  }

  void TestArrayOfActor() {
    nsTArray<NotNull<PTestDataStructuresSubChild*>> oa;
    EXPECT_TRUE(SendTestArrayOfActor(kidsDirect, &oa));
    EXPECT_EQ(kidsDirect, oa);
  }

  void TestUnion() {
    int i1i = 42;
    double i2d = 4.0;
    IntDouble i1(i1i);
    IntDouble i2(i2d);
    IntDouble o1, o2;

    SendTestUnion(i1, i2, &o1, &o2);

    EXPECT_EQ(i1i, o1.get_int());
    EXPECT_EQ(i2d, o2.get_double());
  }

  void TestArrayOfUnion() {
    nsTArray<IntDouble> i1;
    i1.AppendElement(IntDouble(int(1)));
    i1.AppendElement(IntDouble(2.0));
    i1.AppendElement(IntDouble(int(3)));
    i1.AppendElement(IntDouble(4.0));

    nsTArray<IntDouble> o1;
    EXPECT_TRUE(SendTestArrayOfUnion(i1, &o1));

    // TODO Union::operator==()
    EXPECT_EQ(i1.Length(), o1.Length());
    EXPECT_EQ(1, o1[0].get_int());
    EXPECT_EQ(2.0, o1[1].get_double());
    EXPECT_EQ(3, o1[2].get_int());
    EXPECT_EQ(4.0, o1[3].get_double());
  }

  void TestUnionWithArray() {
    IntDoubleArrays i1(int(42));
    nsTArray<int> i2;
    i2.AppendElement(1);
    i2.AppendElement(2);
    i2.AppendElement(3);
    nsTArray<double> i3;
    i3.AppendElement(1.0);
    i3.AppendElement(2.0);
    i3.AppendElement(3.0);

    IntDoubleArrays o1, o2, o3;
    EXPECT_TRUE(SendTestUnionWithArray(i1, IntDoubleArrays(i2),
                                       IntDoubleArrays(i3), &o1, &o2, &o3));

    EXPECT_EQ(42, o1.get_int());
    EXPECT_EQ(i2, o2.get_ArrayOfint());
    EXPECT_EQ(i3, o3.get_ArrayOfdouble());
  }

  void TestArrayOfUnionWithArray() {
    IntDoubleArrays id1(int(42));
    nsTArray<int> id2;
    id2.AppendElement(1);
    id2.AppendElement(2);
    id2.AppendElement(3);
    nsTArray<double> id3;
    id3.AppendElement(1.0);
    id3.AppendElement(2.0);
    id3.AppendElement(3.0);

    nsTArray<IntDoubleArrays> i1;
    i1.AppendElement(id1);
    i1.AppendElement(IntDoubleArrays(id2));
    i1.AppendElement(IntDoubleArrays(id3));

    nsTArray<IntDoubleArrays> o1;
    EXPECT_TRUE(SendTestArrayOfUnionWithArray(i1, &o1));

    EXPECT_EQ(3u, o1.Length());
    IntDoubleArrays od1(o1[0]);
    nsTArray<int> od2 = o1[1].get_ArrayOfint().Clone();
    nsTArray<double> od3 = o1[2].get_ArrayOfdouble().Clone();

    EXPECT_EQ(42, od1.get_int());
    EXPECT_EQ(id2, od2);
    EXPECT_EQ(id3, od3);
  }

  void TestStructWithActor() {
    ActorWrapper iaw(kidsDirect[0]);

    ActorWrapper oaw;
    EXPECT_TRUE(SendTestStructWithActor(iaw, &oaw));

    EXPECT_TRUE(oaw.actor().IsChild());
    EXPECT_EQ(oaw.actor().AsChild(), kidsDirect[0]);
  }

  void TestUnionWithActors() {
    Actors i1(42);
    nsTArray<int> i2a;
    i2a.AppendElement(1);
    i2a.AppendElement(2);
    i2a.AppendElement(3);

    Actors o1, o2, o3;
    EXPECT_TRUE(
        SendTestUnionWithActors(i1, Actors(i2a), Actors(kids), &o1, &o2, &o3));

    EXPECT_EQ(42, o1.get_int());
    EXPECT_EQ(i2a, o2.get_ArrayOfint());
    EXPECT_EQ(kids, o3.get_ArrayOfPTestDataStructuresSub());
  }

  void TestArrayOfUnionWithActors() {
    Actors i1e(42);
    nsTArray<int> i2a;
    i2a.AppendElement(1);
    i2a.AppendElement(2);
    i2a.AppendElement(3);

    nsTArray<Actors> i1;
    i1.AppendElement(i1e);
    i1.AppendElement(i2a);
    i1.AppendElement(kids);

    nsTArray<Actors> o1;
    EXPECT_TRUE(SendTestArrayOfUnionWithActors(i1, &o1));

    EXPECT_EQ(3u, o1.Length());
    EXPECT_EQ(42, o1[0].get_int());
    EXPECT_EQ(i2a, o1[1].get_ArrayOfint());
    EXPECT_EQ(kids, o1[2].get_ArrayOfPTestDataStructuresSub());
  }

  void TestUnions() {
    Unions i1(int(42));

    nsTArray<int> i2a;
    i2a.AppendElement(1);
    i2a.AppendElement(2);
    i2a.AppendElement(3);

    nsTArray<Actors> i4a;
    i4a.AppendElement(kids);

    Unions o1, o2, o3, o4;
    EXPECT_TRUE(SendTestUnions(i1, Unions(i2a), Unions(kids), Unions(i4a), &o1,
                               &o2, &o3, &o4));

    EXPECT_EQ(42, o1.get_int());
    EXPECT_EQ(i2a, o2.get_ArrayOfint());
    EXPECT_EQ(kids, o3.get_ArrayOfPTestDataStructuresSub());
    EXPECT_EQ(kids,
              o4.get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSub());
  }

  void TestArrayOfUnions() {
    Unions i1a(int(42));

    nsTArray<int> i2a;
    i2a.AppendElement(1);
    i2a.AppendElement(2);
    i2a.AppendElement(3);

    nsTArray<Actors> i4a;
    i4a.AppendElement(kids);

    nsTArray<Unions> i1;
    i1.AppendElement(i1a);
    i1.AppendElement(Unions(i2a));
    i1.AppendElement(Unions(kids));
    i1.AppendElement(Unions(i4a));

    nsTArray<Unions> o1;
    EXPECT_TRUE(SendTestArrayOfUnions(i1, &o1));

    EXPECT_EQ(4u, o1.Length());
    EXPECT_EQ(42, o1[0].get_int());
    EXPECT_EQ(i2a, o1[1].get_ArrayOfint());
    EXPECT_EQ(kids, o1[2].get_ArrayOfPTestDataStructuresSub());
    EXPECT_EQ(kids,
              o1[3].get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSub());
  }

  void TestStruct() {
    SIntDouble i(1, 2.0);
    SIntDouble o;

    EXPECT_TRUE(SendTestStruct(i, &o));

    EXPECT_EQ(o.i(), 1);
    EXPECT_EQ(o.d(), 2.0);
  }

  void TestStructWithArrays() {
    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);

    nsTArray<double> ad;
    ad.AppendElement(.5);
    ad.AppendElement(1.0);
    ad.AppendElement(2.0);

    SIntDoubleArrays i(42, ai, ad);
    SIntDoubleArrays o;

    EXPECT_TRUE(SendTestStructWithArrays(i, &o));

    EXPECT_EQ(42, o.i());
    EXPECT_EQ(ai, o.ai());
    EXPECT_EQ(ad, o.ad());
  }

  void TestStructWithActors() {
    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);

    SActors i;
    i.i() = 42;
    i.ai() = ai.Clone();
    i.ap() = kids.Clone();

    SActors o;
    EXPECT_TRUE(SendTestStructWithActors(i, &o));

    EXPECT_EQ(42, o.i());
    EXPECT_EQ(ai, o.ai());
    EXPECT_EQ(kids, o.ap());
  }

  void TestStructs() {
    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);

    SActors ia;
    ia.i() = 42;
    ia.ai() = ai.Clone();
    ia.ap() = kids.Clone();
    nsTArray<SActors> aa;
    aa.AppendElement(ia);

    Structs i;
    i.i() = 42;
    i.ai() = ai.Clone();
    i.ap() = kids.Clone();
    i.aa() = aa.Clone();

    Structs o;
    EXPECT_TRUE(SendTestStructs(i, &o));

    EXPECT_EQ(42, o.i());
    EXPECT_EQ(ai, o.ai());
    EXPECT_EQ(kids, o.ap());

    const SActors& os = o.aa()[0];
    EXPECT_EQ(42, os.i());
    EXPECT_EQ(ai, os.ai());
    EXPECT_EQ(kids, os.ap());
  }

  void TestUnionWithStructs() {
    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);

    SActors ia;
    ia.i() = 42;
    ia.ai() = ai.Clone();
    ia.ap() = kids.Clone();
    nsTArray<SActors> iaa;
    iaa.AppendElement(ia);

    Structs is;
    is.i() = 42;
    is.ai() = ai.Clone();
    is.ap() = kids.Clone();
    is.aa() = iaa.Clone();
    nsTArray<Structs> isa;
    isa.AppendElement(is);

    WithStructs o1, o2, o3, o4, o5;
    EXPECT_TRUE(SendTestUnionWithStructs(
        WithStructs(42), WithStructs(ai), WithStructs(kids), WithStructs(iaa),
        WithStructs(isa), &o1, &o2, &o3, &o4, &o5));

    EXPECT_EQ(o1, int(42));
    EXPECT_EQ(o2.get_ArrayOfint(), ai);
    EXPECT_EQ(o3.get_ArrayOfPTestDataStructuresSub(), kids);

    const SActors& oa = o4.get_ArrayOfSActors()[0];
    EXPECT_EQ(42, oa.i());
    EXPECT_EQ(ai, oa.ai());
    EXPECT_EQ(kids, oa.ap());

    const Structs& os = o5.get_ArrayOfStructs()[0];
    EXPECT_EQ(42, os.i());
    EXPECT_EQ(ai, os.ai());
    EXPECT_EQ(kids, os.ap());

    const SActors& osa = os.aa()[0];
    EXPECT_EQ(42, osa.i());
    EXPECT_EQ(ai, osa.ai());
    EXPECT_EQ(kids, osa.ap());
  }

  void TestStructWithUnions() {
    WithUnions i;

    i.i() = 42;

    nsTArray<int> ai;
    ai.AppendElement(1);
    ai.AppendElement(2);
    ai.AppendElement(3);
    i.ai() = ai.Clone();

    i.ap() = kids.Clone();

    nsTArray<Actors> iaa;
    iaa.AppendElement(kids);
    i.aa() = iaa.Clone();

    nsTArray<Unions> iau;
    iau.AppendElement(int(42));
    iau.AppendElement(ai);
    iau.AppendElement(kids);
    iau.AppendElement(iaa);
    i.au() = iau.Clone();

    WithUnions o;
    EXPECT_TRUE(SendTestStructWithUnions(i, &o));

    EXPECT_EQ(42, o.i());
    EXPECT_EQ(o.ai(), ai);
    EXPECT_EQ(o.ap(), kids);

    const Actors& oaa = o.aa()[0];
    EXPECT_EQ(oaa.get_ArrayOfPTestDataStructuresSub(), kids);

    const nsTArray<Unions>& oau = o.au();
    EXPECT_EQ(oau[0], 42);
    EXPECT_EQ(oau[1].get_ArrayOfint(), ai);
    EXPECT_EQ(oau[2].get_ArrayOfPTestDataStructuresSub(), kids);
    EXPECT_EQ(oau[3].get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSub(),
              kids);
  }

  void TestUnionWithCxx() {
    Attrs attrs;
    attrs.common() = CommonAttrs(true);
    attrs.specific() = BarAttrs(1.0f);

    nsTArray<Op> ops;
    ops.AppendElement(SetAttrs(kids[0], attrs));

    EXPECT_TRUE(SendTestUnionWithCxx(ops));
  }

  void TestNsIntRegion() {
    const int nelements = 1000;
    RegionArray ra;
    // big enough to hopefully force a realloc to a different chunk of
    // memory on the receiving side, if the workaround isn't working
    // correctly.  But SetCapacity() here because we don't want to
    // crash on the sending side.
    ra.SetCapacity(nelements);
    for (int i = 0; i < nelements; ++i) {
      nsIntRegion r;
      r.Or(nsIntRect(0, 0, 10, 10), nsIntRect(10, 10, 10, 10));
      ra.AppendElement(r);
    }

    EXPECT_TRUE(SendTestNsIntRegion(ra));
  }

  ~TestDataStructuresChild() = default;
};

IPDL_TEST(TestDataStructures) {
  for (uint32_t i = 0; i < nactors; ++i) {
    auto sub = MakeRefPtr<TestDataStructuresSubParent>(i);
    EXPECT_TRUE(mActor->SendPTestDataStructuresSubConstructor(sub, i))
        << "can't alloc actor";
    mActor->kids.AppendElement(WrapNotNull(sub));
  }

  EXPECT_TRUE(mActor->SendStart()) << "can't send Start()";
}

}  // namespace mozilla::_ipdltest
