blob: c6e0071a98e53dd41e33f834395ed2946263fe0b [file] [log] [blame]
/* 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 "js/HashTable.h"
#include "jsapi-tests/tests.h"
//#define FUZZ
typedef js::HashMap<uint32_t, uint32_t, js::DefaultHasher<uint32_t>, js::SystemAllocPolicy> IntMap;
typedef js::HashSet<uint32_t, js::DefaultHasher<uint32_t>, js::SystemAllocPolicy> IntSet;
/*
* The rekeying test as conducted by adding only keys masked with 0x0000FFFF
* that are unique. We rekey by shifting left 16 bits.
*/
#ifdef FUZZ
const size_t TestSize = 0x0000FFFF / 2;
const size_t TestIterations = SIZE_MAX;
#else
const size_t TestSize = 10000;
const size_t TestIterations = 10;
#endif
JS_STATIC_ASSERT(TestSize <= 0x0000FFFF / 2);
struct LowToHigh
{
static uint32_t rekey(uint32_t initial) {
if (initial > uint32_t(0x0000FFFF))
return initial;
return initial << 16;
}
static bool shouldBeRemoved(uint32_t initial) {
return false;
}
};
struct LowToHighWithRemoval
{
static uint32_t rekey(uint32_t initial) {
if (initial > uint32_t(0x0000FFFF))
return initial;
return initial << 16;
}
static bool shouldBeRemoved(uint32_t initial) {
if (initial >= 0x00010000)
return (initial >> 16) % 2 == 0;
return initial % 2 == 0;
}
};
static bool
MapsAreEqual(IntMap& am, IntMap& bm)
{
bool equal = true;
if (am.count() != bm.count()) {
equal = false;
fprintf(stderr, "A.count() == %u and B.count() == %u\n", am.count(), bm.count());
}
for (IntMap::Range r = am.all(); !r.empty(); r.popFront()) {
if (!bm.has(r.front().key())) {
equal = false;
fprintf(stderr, "B does not have %x which is in A\n", r.front().key());
}
}
for (IntMap::Range r = bm.all(); !r.empty(); r.popFront()) {
if (!am.has(r.front().key())) {
equal = false;
fprintf(stderr, "A does not have %x which is in B\n", r.front().key());
}
}
return equal;
}
static bool
SetsAreEqual(IntSet& am, IntSet& bm)
{
bool equal = true;
if (am.count() != bm.count()) {
equal = false;
fprintf(stderr, "A.count() == %u and B.count() == %u\n", am.count(), bm.count());
}
for (IntSet::Range r = am.all(); !r.empty(); r.popFront()) {
if (!bm.has(r.front())) {
equal = false;
fprintf(stderr, "B does not have %x which is in A\n", r.front());
}
}
for (IntSet::Range r = bm.all(); !r.empty(); r.popFront()) {
if (!am.has(r.front())) {
equal = false;
fprintf(stderr, "A does not have %x which is in B\n", r.front());
}
}
return equal;
}
static bool
AddLowKeys(IntMap* am, IntMap* bm, int seed)
{
size_t i = 0;
srand(seed);
while (i < TestSize) {
uint32_t n = rand() & 0x0000FFFF;
if (!am->has(n)) {
if (bm->has(n))
return false;
am->putNew(n, n);
bm->putNew(n, n);
i++;
}
}
return true;
}
static bool
AddLowKeys(IntSet* as, IntSet* bs, int seed)
{
size_t i = 0;
srand(seed);
while (i < TestSize) {
uint32_t n = rand() & 0x0000FFFF;
if (!as->has(n)) {
if (bs->has(n))
return false;
as->putNew(n);
bs->putNew(n);
i++;
}
}
return true;
}
template <class NewKeyFunction>
static bool
SlowRekey(IntMap* m) {
IntMap tmp;
tmp.init();
for (IntMap::Range r = m->all(); !r.empty(); r.popFront()) {
if (NewKeyFunction::shouldBeRemoved(r.front().key()))
continue;
uint32_t hi = NewKeyFunction::rekey(r.front().key());
if (tmp.has(hi))
return false;
tmp.putNew(hi, r.front().value());
}
m->clear();
for (IntMap::Range r = tmp.all(); !r.empty(); r.popFront()) {
m->putNew(r.front().key(), r.front().value());
}
return true;
}
template <class NewKeyFunction>
static bool
SlowRekey(IntSet* s) {
IntSet tmp;
tmp.init();
for (IntSet::Range r = s->all(); !r.empty(); r.popFront()) {
if (NewKeyFunction::shouldBeRemoved(r.front()))
continue;
uint32_t hi = NewKeyFunction::rekey(r.front());
if (tmp.has(hi))
return false;
tmp.putNew(hi);
}
s->clear();
for (IntSet::Range r = tmp.all(); !r.empty(); r.popFront()) {
s->putNew(r.front());
}
return true;
}
BEGIN_TEST(testHashRekeyManual)
{
IntMap am, bm;
am.init();
bm.init();
for (size_t i = 0; i < TestIterations; ++i) {
#ifdef FUZZ
fprintf(stderr, "map1: %lu\n", i);
#endif
CHECK(AddLowKeys(&am, &bm, i));
CHECK(MapsAreEqual(am, bm));
for (IntMap::Enum e(am); !e.empty(); e.popFront()) {
uint32_t tmp = LowToHigh::rekey(e.front().key());
if (tmp != e.front().key())
e.rekeyFront(tmp);
}
CHECK(SlowRekey<LowToHigh>(&bm));
CHECK(MapsAreEqual(am, bm));
am.clear();
bm.clear();
}
IntSet as, bs;
as.init();
bs.init();
for (size_t i = 0; i < TestIterations; ++i) {
#ifdef FUZZ
fprintf(stderr, "set1: %lu\n", i);
#endif
CHECK(AddLowKeys(&as, &bs, i));
CHECK(SetsAreEqual(as, bs));
for (IntSet::Enum e(as); !e.empty(); e.popFront()) {
uint32_t tmp = LowToHigh::rekey(e.front());
if (tmp != e.front())
e.rekeyFront(tmp);
}
CHECK(SlowRekey<LowToHigh>(&bs));
CHECK(SetsAreEqual(as, bs));
as.clear();
bs.clear();
}
return true;
}
END_TEST(testHashRekeyManual)
BEGIN_TEST(testHashRekeyManualRemoval)
{
IntMap am, bm;
am.init();
bm.init();
for (size_t i = 0; i < TestIterations; ++i) {
#ifdef FUZZ
fprintf(stderr, "map2: %lu\n", i);
#endif
CHECK(AddLowKeys(&am, &bm, i));
CHECK(MapsAreEqual(am, bm));
for (IntMap::Enum e(am); !e.empty(); e.popFront()) {
if (LowToHighWithRemoval::shouldBeRemoved(e.front().key())) {
e.removeFront();
} else {
uint32_t tmp = LowToHighWithRemoval::rekey(e.front().key());
if (tmp != e.front().key())
e.rekeyFront(tmp);
}
}
CHECK(SlowRekey<LowToHighWithRemoval>(&bm));
CHECK(MapsAreEqual(am, bm));
am.clear();
bm.clear();
}
IntSet as, bs;
as.init();
bs.init();
for (size_t i = 0; i < TestIterations; ++i) {
#ifdef FUZZ
fprintf(stderr, "set1: %lu\n", i);
#endif
CHECK(AddLowKeys(&as, &bs, i));
CHECK(SetsAreEqual(as, bs));
for (IntSet::Enum e(as); !e.empty(); e.popFront()) {
if (LowToHighWithRemoval::shouldBeRemoved(e.front())) {
e.removeFront();
} else {
uint32_t tmp = LowToHighWithRemoval::rekey(e.front());
if (tmp != e.front())
e.rekeyFront(tmp);
}
}
CHECK(SlowRekey<LowToHighWithRemoval>(&bs));
CHECK(SetsAreEqual(as, bs));
as.clear();
bs.clear();
}
return true;
}
END_TEST(testHashRekeyManualRemoval)
// A type that is not copyable, only movable.
struct MoveOnlyType {
uint32_t val;
explicit MoveOnlyType(uint32_t val) : val(val) { }
MoveOnlyType(MoveOnlyType&& rhs) {
val = rhs.val;
}
MoveOnlyType& operator=(MoveOnlyType&& rhs) {
MOZ_ASSERT(&rhs != this);
this->~MoveOnlyType();
new(this) MoveOnlyType(mozilla::Move(rhs));
return *this;
}
struct HashPolicy {
typedef MoveOnlyType Lookup;
static js::HashNumber hash(const Lookup& lookup) {
return lookup.val;
}
static bool match(const MoveOnlyType& existing, const Lookup& lookup) {
return existing.val == lookup.val;
}
};
private:
MoveOnlyType(const MoveOnlyType&) = delete;
MoveOnlyType& operator=(const MoveOnlyType&) = delete;
};
BEGIN_TEST(testHashSetOfMoveOnlyType)
{
typedef js::HashSet<MoveOnlyType, MoveOnlyType::HashPolicy, js::SystemAllocPolicy> Set;
Set set;
set.init();
MoveOnlyType a(1);
set.put(mozilla::Move(a)); // This shouldn't generate a compiler error.
return true;
}
END_TEST(testHashSetOfMoveOnlyType)