// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

#include <list>

#include "libcef_dll/wrapper/cef_browser_info_map.h"
#include "tests/gtest/include/gtest/gtest.h"

namespace {

struct MyObject {
  MyObject(int val = 0) : member(val) {}
  int member;
};

int g_destruct_ct = 0;

struct MyObjectTraits {
  static void Destruct(MyObject info) { g_destruct_ct++; }
};

typedef CefBrowserInfoMap<int, MyObject, MyObjectTraits> MyObjectMap;

class MyVisitor : public MyObjectMap::Visitor {
 public:
  MyVisitor(bool remove = false,
            int remove_browser_id = 0,
            InfoIdType remove_info_id = 0)
      : remove_(remove),
        remove_browser_id_(remove_browser_id),
        remove_info_id_(remove_info_id) {}

  bool OnNextInfo(int browser_id,
                  InfoIdType info_id,
                  InfoObjectType info,
                  bool* remove) override {
    Info info_rec;
    info_rec.browser_id = browser_id;
    info_rec.info_id = info_id;
    info_rec.info = info;
    info_list_.push_back(info_rec);

    // Based on test configuration remove no objects, all objects, or only the
    // specified object.
    *remove = remove_ ||
              (browser_id == remove_browser_id_ && info_id == remove_info_id_);
    return true;
  }

  // Returns true if the specified info was passed to OnNextInfo. Removes the
  // record if found.
  bool Exists(int browser_id, InfoIdType info_id, InfoObjectType info) {
    InfoList::iterator it = info_list_.begin();
    for (; it != info_list_.end(); ++it) {
      const Info& found_info = *it;
      if (browser_id == found_info.browser_id &&
          info_id == found_info.info_id &&
          info.member == found_info.info.member) {
        info_list_.erase(it);
        return true;
      }
    }
    return false;
  }

  size_t info_size() const { return info_list_.size(); }

 private:
  bool remove_;
  int remove_browser_id_;
  InfoIdType remove_info_id_;

  struct Info {
    int browser_id;
    InfoIdType info_id;
    InfoObjectType info;
  };

  // Track calls to OnNextInfo.
  typedef std::list<Info> InfoList;
  InfoList info_list_;
};

}  // namespace

TEST(BrowserInfoMapTest, AddSingleBrowser) {
  MyObjectMap map;
  const int kBrowserId = 1;

  g_destruct_ct = 0;

  EXPECT_EQ(0U, map.size());
  EXPECT_EQ(0U, map.size(kBrowserId));

  MyObject obj1(1);
  map.Add(kBrowserId, 1, obj1);
  EXPECT_EQ(1U, map.size());
  EXPECT_EQ(1U, map.size(kBrowserId));

  MyObject obj2(2);
  map.Add(kBrowserId, 2, obj2);
  EXPECT_EQ(2U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId));

  MyObject obj3(3);
  map.Add(kBrowserId, 3, obj3);
  EXPECT_EQ(3U, map.size());
  EXPECT_EQ(3U, map.size(kBrowserId));

  EXPECT_EQ(0, g_destruct_ct);

  map.clear(kBrowserId);
  EXPECT_EQ(0U, map.size());
  EXPECT_EQ(0U, map.size(kBrowserId));

  EXPECT_EQ(3, g_destruct_ct);
}

TEST(BrowserInfoMapTest, AddMultipleBrowsers) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  EXPECT_EQ(0U, map.size());
  EXPECT_EQ(0U, map.size(kBrowserId1));
  EXPECT_EQ(0U, map.size(kBrowserId2));

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);
  EXPECT_EQ(1U, map.size());
  EXPECT_EQ(1U, map.size(kBrowserId1));
  EXPECT_EQ(0U, map.size(kBrowserId2));

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);
  EXPECT_EQ(2U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(0U, map.size(kBrowserId2));

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);
  EXPECT_EQ(3U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(1U, map.size(kBrowserId2));

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);
  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  EXPECT_EQ(0, g_destruct_ct);

  map.clear(kBrowserId1);
  EXPECT_EQ(2U, map.size());
  EXPECT_EQ(0U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  EXPECT_EQ(2, g_destruct_ct);

  map.clear(kBrowserId2);
  EXPECT_EQ(0U, map.size());
  EXPECT_EQ(0U, map.size(kBrowserId1));
  EXPECT_EQ(0U, map.size(kBrowserId2));

  EXPECT_EQ(4, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindSingleBrowser) {
  MyObjectMap map;
  const int kBrowserId = 1;

  g_destruct_ct = 0;

  // obj1 not added yet.
  MyObject nf1 = map.Find(kBrowserId, 1, nullptr);
  EXPECT_EQ(0, nf1.member);

  MyObject obj1(1);
  map.Add(kBrowserId, 1, obj1);

  // obj1 should exist.
  MyObject f1 = map.Find(kBrowserId, 1, nullptr);
  EXPECT_EQ(obj1.member, f1.member);

  // obj2 not added yet.
  MyObject nf2 = map.Find(kBrowserId, 2, nullptr);
  EXPECT_EQ(0, nf2.member);

  MyObject obj2(2);
  map.Add(kBrowserId, 2, obj2);

  // obj2 should exist.
  MyObject f2 = map.Find(kBrowserId, 2, nullptr);
  EXPECT_EQ(obj2.member, f2.member);

  // find obj1 again.
  MyObject f1b = map.Find(kBrowserId, 1, nullptr);
  EXPECT_EQ(obj1.member, f1b.member);

  // find obj2 again.
  MyObject f2b = map.Find(kBrowserId, 2, nullptr);
  EXPECT_EQ(obj2.member, f2b.member);

  // doesn't exist.
  MyObject nf3 = map.Find(kBrowserId, 3, nullptr);
  EXPECT_EQ(0, nf3.member);
  MyObject nf4 = map.Find(10, 1, nullptr);
  EXPECT_EQ(0, nf4.member);
  MyObject nf5 = map.Find(10, 3, nullptr);
  EXPECT_EQ(0, nf5.member);

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  EXPECT_EQ(2, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindMultipleBrowsers) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  // obj1 not added yet.
  MyObject nf1 = map.Find(kBrowserId1, 1, nullptr);
  EXPECT_EQ(0, nf1.member);

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  // obj1 should exist.
  MyObject f1 = map.Find(kBrowserId1, 1, nullptr);
  EXPECT_EQ(obj1.member, f1.member);

  // obj2 not added yet.
  MyObject nf2 = map.Find(kBrowserId1, 2, nullptr);
  EXPECT_EQ(0, nf2.member);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  // obj2 should exist.
  MyObject f2 = map.Find(kBrowserId1, 2, nullptr);
  EXPECT_EQ(obj2.member, f2.member);

  // obj3 not added yet.
  MyObject nf3 = map.Find(kBrowserId2, 3, nullptr);
  EXPECT_EQ(0, nf3.member);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  // obj3 should exist.
  MyObject f3 = map.Find(kBrowserId2, 3, nullptr);
  EXPECT_EQ(obj3.member, f3.member);

  // obj4 not added yet.
  MyObject nf4 = map.Find(kBrowserId2, 4, nullptr);
  EXPECT_EQ(0, nf4.member);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  // obj4 should exist.
  MyObject f4 = map.Find(kBrowserId2, 4, nullptr);
  EXPECT_EQ(obj4.member, f4.member);

  // obj1-3 should exist.
  MyObject f1b = map.Find(kBrowserId1, 1, nullptr);
  EXPECT_EQ(obj1.member, f1b.member);
  MyObject f2b = map.Find(kBrowserId1, 2, nullptr);
  EXPECT_EQ(obj2.member, f2b.member);
  MyObject f3b = map.Find(kBrowserId2, 3, nullptr);
  EXPECT_EQ(obj3.member, f3b.member);

  // wrong browser
  MyObject nf5 = map.Find(kBrowserId1, 4, nullptr);
  EXPECT_EQ(0, nf5.member);
  MyObject nf6 = map.Find(kBrowserId2, 1, nullptr);
  EXPECT_EQ(0, nf6.member);

  // deosn't exist
  MyObject nf7 = map.Find(kBrowserId2, 5, nullptr);
  EXPECT_EQ(0, nf7.member);
  MyObject nf8 = map.Find(8, 1, nullptr);
  EXPECT_EQ(0, nf8.member);
  MyObject nf9 = map.Find(8, 10, nullptr);
  EXPECT_EQ(0, nf9.member);

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  EXPECT_EQ(4, g_destruct_ct);
}

TEST(BrowserInfoMapTest, Find) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // should only visit the single object
  MyVisitor visitor;
  map.Find(kBrowserId2, 4, &visitor);
  EXPECT_EQ(1U, visitor.info_size());
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 4, obj4));
  EXPECT_EQ(0U, visitor.info_size());

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  EXPECT_EQ(4, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindAndRemove) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // should only visit and remove the single object.
  MyVisitor visitor(true);
  map.Find(kBrowserId1, 2, &visitor);
  EXPECT_EQ(1U, visitor.info_size());
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 2, obj2));
  EXPECT_EQ(0U, visitor.info_size());

  EXPECT_EQ(3U, map.size());
  EXPECT_EQ(1U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // visited object shouldn't exist
  MyObject nf2 = map.Find(kBrowserId1, 2, nullptr);
  EXPECT_EQ(0, nf2.member);

  // other objects should exist
  MyObject nf1 = map.Find(kBrowserId1, 1, nullptr);
  EXPECT_EQ(obj1.member, nf1.member);
  MyObject nf3 = map.Find(kBrowserId2, 3, nullptr);
  EXPECT_EQ(obj3.member, nf3.member);
  MyObject nf4 = map.Find(kBrowserId2, 4, nullptr);
  EXPECT_EQ(obj4.member, nf4.member);

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  // should destruct the remaining 3 objects
  EXPECT_EQ(3, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindAll) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // should visit all objects
  MyVisitor visitor;
  map.FindAll(&visitor);
  EXPECT_EQ(4U, visitor.info_size());
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 1, obj1));
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 2, obj2));
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 3, obj3));
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 4, obj4));
  // should be no unexpected visits
  EXPECT_EQ(0U, visitor.info_size());

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  EXPECT_EQ(4, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindAllByBrowser) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // should only visit browser2 objects
  MyVisitor visitor;
  map.FindAll(kBrowserId2, &visitor);
  EXPECT_EQ(2U, visitor.info_size());
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 3, obj3));
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 4, obj4));
  // should be no unexpected visits
  EXPECT_EQ(0U, visitor.info_size());

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  EXPECT_EQ(4, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindAllAndRemoveAll) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // should visit all objects
  MyVisitor visitor(true);
  map.FindAll(&visitor);
  EXPECT_EQ(4U, visitor.info_size());
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 1, obj1));
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 2, obj2));
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 3, obj3));
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 4, obj4));
  // should be no unexpected visits
  EXPECT_EQ(0U, visitor.info_size());

  EXPECT_EQ(0U, map.size());
  EXPECT_EQ(0U, map.size(kBrowserId1));
  EXPECT_EQ(0U, map.size(kBrowserId2));

  EXPECT_EQ(0, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindAllAndRemoveOne) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // should visit all objects and remove one
  MyVisitor visitor(false, kBrowserId2, 3);
  map.FindAll(&visitor);
  EXPECT_EQ(4U, visitor.info_size());
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 1, obj1));
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 2, obj2));
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 3, obj3));
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 4, obj4));
  // should be no unexpected visits
  EXPECT_EQ(0U, visitor.info_size());

  EXPECT_EQ(3U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(1U, map.size(kBrowserId2));

  // removed object shouldn't exist
  MyObject nf3 = map.Find(kBrowserId2, 3, nullptr);
  EXPECT_EQ(0, nf3.member);

  // other objects should exist
  MyObject f1 = map.Find(kBrowserId1, 1, nullptr);
  EXPECT_EQ(obj1.member, f1.member);
  MyObject f2 = map.Find(kBrowserId1, 2, nullptr);
  EXPECT_EQ(obj2.member, f2.member);
  MyObject f4 = map.Find(kBrowserId2, 4, nullptr);
  EXPECT_EQ(obj4.member, f4.member);

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  // should destruct the remaining 3 objects
  EXPECT_EQ(3, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindAllAndRemoveAllByBrowser) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // should only visit browser1 objects
  MyVisitor visitor(true);
  map.FindAll(kBrowserId1, &visitor);
  EXPECT_EQ(2U, visitor.info_size());
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 1, obj1));
  EXPECT_TRUE(visitor.Exists(kBrowserId1, 2, obj2));
  // should be no unexpected visits
  EXPECT_EQ(0U, visitor.info_size());

  EXPECT_EQ(2U, map.size());
  EXPECT_EQ(0U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // browser1 objects shouldn't exist
  MyObject nf1 = map.Find(kBrowserId1, 1, nullptr);
  EXPECT_EQ(0, nf1.member);
  MyObject nf2 = map.Find(kBrowserId1, 2, nullptr);
  EXPECT_EQ(0, nf2.member);

  // browser 2 objects should exist
  MyObject f3 = map.Find(kBrowserId2, 3, nullptr);
  EXPECT_EQ(obj3.member, f3.member);
  MyObject f4 = map.Find(kBrowserId2, 4, nullptr);
  EXPECT_EQ(obj4.member, f4.member);

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  // should destruct the remaining 2 objects
  EXPECT_EQ(2, g_destruct_ct);
}

TEST(BrowserInfoMapTest, FindAllAndRemoveOneByBrowser) {
  MyObjectMap map;
  const int kBrowserId1 = 1;
  const int kBrowserId2 = 2;

  g_destruct_ct = 0;

  MyObject obj1(1);
  map.Add(kBrowserId1, 1, obj1);

  MyObject obj2(2);
  map.Add(kBrowserId1, 2, obj2);

  MyObject obj3(3);
  map.Add(kBrowserId2, 3, obj3);

  MyObject obj4(4);
  map.Add(kBrowserId2, 4, obj4);

  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(2U, map.size(kBrowserId2));

  // should visit browser2 objects and remove one
  MyVisitor visitor(false, kBrowserId2, 4);
  map.FindAll(kBrowserId2, &visitor);
  EXPECT_EQ(2U, visitor.info_size());
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 3, obj3));
  EXPECT_TRUE(visitor.Exists(kBrowserId2, 4, obj4));
  // should be no unexpected visits
  EXPECT_EQ(0U, visitor.info_size());

  EXPECT_EQ(3U, map.size());
  EXPECT_EQ(2U, map.size(kBrowserId1));
  EXPECT_EQ(1U, map.size(kBrowserId2));

  // removed object shouldn't exist
  MyObject nf4 = map.Find(kBrowserId2, 4, nullptr);
  EXPECT_EQ(0, nf4.member);

  // other objects should exist
  MyObject f1 = map.Find(kBrowserId1, 1, nullptr);
  EXPECT_EQ(obj1.member, f1.member);
  MyObject f2 = map.Find(kBrowserId1, 2, nullptr);
  EXPECT_EQ(obj2.member, f2.member);
  MyObject f3 = map.Find(kBrowserId2, 3, nullptr);
  EXPECT_EQ(obj3.member, f3.member);

  EXPECT_EQ(0, g_destruct_ct);
  map.clear();
  // should destruct the remaining 3 objects
  EXPECT_EQ(3, g_destruct_ct);
}

namespace {

class MyHeapObject {
 public:
  MyHeapObject(int* destroy_ct) : destroy_ct_(destroy_ct) {}
  ~MyHeapObject() { (*destroy_ct_)++; }

 private:
  int* destroy_ct_;
};

}  // namespace

TEST(BrowserInfoMapTest, DefaultTraits) {
  CefBrowserInfoMap<int, MyHeapObject*> map;

  int destroy_ct = 0;
  map.Add(1, 1, new MyHeapObject(&destroy_ct));
  map.Add(1, 2, new MyHeapObject(&destroy_ct));
  map.Add(2, 1, new MyHeapObject(&destroy_ct));
  map.Add(2, 2, new MyHeapObject(&destroy_ct));
  map.Add(3, 1, new MyHeapObject(&destroy_ct));
  map.Add(3, 2, new MyHeapObject(&destroy_ct));

  EXPECT_EQ(6U, map.size());

  map.clear(1);
  EXPECT_EQ(4U, map.size());
  EXPECT_EQ(2, destroy_ct);

  map.clear();
  EXPECT_EQ(0U, map.size());
  EXPECT_EQ(6, destroy_ct);
}
