blob: 687f19ee0d4897652ab28e6fe52cb64e44d915f0 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 "Lucy/Util/ToolSet.h"
#include <stdlib.h>
#include <time.h>
#include "Lucy/Test.h"
#include "Lucy/Test/TestUtils.h"
#include "Lucy/Test/Object/TestHash.h"
#include "Lucy/Object/Hash.h"
static CharBuf*
S_random_string() {
uint32_t len = rand() % 1200;
CharBuf *string = CB_new(len * 3);
while (len--) {
uint8_t bytes = (rand() % 3) + 1;
uint32_t code_point = 0;
switch (bytes & 0x3) {
case 1:
code_point = rand() % 0x80;
break;
case 2:
code_point = (rand() % (0x0800 - 0x0080)) + 0x0080;
break;
case 3:
code_point = (rand() % (0x10000 - 0x0800)) + 0x0800;
break;
}
CB_Cat_Char(string, code_point);
}
return string;
}
static void
test_Equals(TestBatch *batch) {
Hash *hash = Hash_new(0);
Hash *other = Hash_new(0);
ZombieCharBuf *stuff = ZCB_WRAP_STR("stuff", 5);
TEST_TRUE(batch, Hash_Equals(hash, (Obj*)other),
"Empty hashes are equal");
Hash_Store_Str(hash, "foo", 3, INCREF(&EMPTY));
TEST_FALSE(batch, Hash_Equals(hash, (Obj*)other),
"Add one pair and Equals returns false");
Hash_Store_Str(other, "foo", 3, INCREF(&EMPTY));
TEST_TRUE(batch, Hash_Equals(hash, (Obj*)other),
"Add a matching pair and Equals returns true");
Hash_Store_Str(other, "foo", 3, INCREF(stuff));
TEST_FALSE(batch, Hash_Equals(hash, (Obj*)other),
"Non-matching value spoils Equals");
DECREF(hash);
DECREF(other);
}
static void
test_Store_and_Fetch(TestBatch *batch) {
Hash *hash = Hash_new(100);
Hash *dupe = Hash_new(100);
const uint32_t starting_cap = Hash_Get_Capacity(hash);
VArray *expected = VA_new(100);
VArray *got = VA_new(100);
ZombieCharBuf *twenty = ZCB_WRAP_STR("20", 2);
ZombieCharBuf *forty = ZCB_WRAP_STR("40", 2);
ZombieCharBuf *foo = ZCB_WRAP_STR("foo", 3);
int32_t i;
for (i = 0; i < 100; i++) {
CharBuf *cb = CB_newf("%i32", i);
Hash_Store(hash, (Obj*)cb, (Obj*)cb);
Hash_Store(dupe, (Obj*)cb, INCREF(cb));
VA_Push(expected, INCREF(cb));
}
TEST_TRUE(batch, Hash_Equals(hash, (Obj*)dupe), "Equals");
TEST_INT_EQ(batch, Hash_Get_Capacity(hash), starting_cap,
"Initial capacity sufficient (no rebuilds)");
for (i = 0; i < 100; i++) {
Obj *key = VA_Fetch(expected, i);
Obj *elem = Hash_Fetch(hash, key);
VA_Push(got, (Obj*)INCREF(elem));
}
TEST_TRUE(batch, VA_Equals(got, (Obj*)expected),
"basic Store and Fetch");
TEST_INT_EQ(batch, Hash_Get_Size(hash), 100,
"size incremented properly by Hash_Store");
TEST_TRUE(batch, Hash_Fetch(hash, (Obj*)foo) == NULL,
"Fetch against non-existent key returns NULL");
Hash_Store(hash, (Obj*)forty, INCREF(foo));
TEST_TRUE(batch, ZCB_Equals(foo, Hash_Fetch(hash, (Obj*)forty)),
"Hash_Store replaces existing value");
TEST_FALSE(batch, Hash_Equals(hash, (Obj*)dupe),
"replacement value spoils equals");
TEST_INT_EQ(batch, Hash_Get_Size(hash), 100,
"size unaffected after value replaced");
TEST_TRUE(batch, Hash_Delete(hash, (Obj*)forty) == (Obj*)foo,
"Delete returns value");
DECREF(foo);
TEST_INT_EQ(batch, Hash_Get_Size(hash), 99,
"size decremented by successful Delete");
TEST_TRUE(batch, Hash_Delete(hash, (Obj*)forty) == NULL,
"Delete returns NULL when key not found");
TEST_INT_EQ(batch, Hash_Get_Size(hash), 99,
"size not decremented by unsuccessful Delete");
DECREF(Hash_Delete(dupe, (Obj*)forty));
TEST_TRUE(batch, VA_Equals(got, (Obj*)expected), "Equals after Delete");
Hash_Clear(hash);
TEST_TRUE(batch, Hash_Fetch(hash, (Obj*)twenty) == NULL, "Clear");
TEST_TRUE(batch, Hash_Get_Size(hash) == 0, "size is 0 after Clear");
DECREF(hash);
DECREF(dupe);
DECREF(got);
DECREF(expected);
}
static void
test_Keys_Values_Iter(TestBatch *batch) {
uint32_t i;
Hash *hash = Hash_new(0); // trigger multiple rebuilds.
VArray *expected = VA_new(100);
VArray *keys;
VArray *values;
for (i = 0; i < 500; i++) {
CharBuf *cb = CB_newf("%u32", i);
Hash_Store(hash, (Obj*)cb, (Obj*)cb);
VA_Push(expected, INCREF(cb));
}
VA_Sort(expected, NULL, NULL);
keys = Hash_Keys(hash);
values = Hash_Values(hash);
VA_Sort(keys, NULL, NULL);
VA_Sort(values, NULL, NULL);
TEST_TRUE(batch, VA_Equals(keys, (Obj*)expected), "Keys");
TEST_TRUE(batch, VA_Equals(values, (Obj*)expected), "Values");
VA_Clear(keys);
VA_Clear(values);
{
Obj *key;
Obj *value;
Hash_Iterate(hash);
while (Hash_Next(hash, &key, &value)) {
VA_Push(keys, INCREF(key));
VA_Push(values, INCREF(value));
}
}
VA_Sort(keys, NULL, NULL);
VA_Sort(values, NULL, NULL);
TEST_TRUE(batch, VA_Equals(keys, (Obj*)expected), "Keys from Iter");
TEST_TRUE(batch, VA_Equals(values, (Obj*)expected), "Values from Iter");
{
ZombieCharBuf *forty = ZCB_WRAP_STR("40", 2);
ZombieCharBuf *nope = ZCB_WRAP_STR("nope", 4);
Obj *key = Hash_Find_Key(hash, (Obj*)forty, ZCB_Hash_Sum(forty));
TEST_TRUE(batch, Obj_Equals(key, (Obj*)forty), "Find_Key");
key = Hash_Find_Key(hash, (Obj*)nope, ZCB_Hash_Sum(nope)),
TEST_TRUE(batch, key == NULL,
"Find_Key returns NULL for non-existent key");
}
DECREF(hash);
DECREF(expected);
DECREF(keys);
DECREF(values);
}
static void
test_Dump_and_Load(TestBatch *batch) {
Hash *hash = Hash_new(0);
Obj *dump;
Hash *loaded;
Hash_Store_Str(hash, "foo", 3,
(Obj*)CB_new_from_trusted_utf8("foo", 3));
dump = (Obj*)Hash_Dump(hash);
loaded = (Hash*)Obj_Load(dump, dump);
TEST_TRUE(batch, Hash_Equals(hash, (Obj*)loaded),
"Dump => Load round trip");
DECREF(dump);
DECREF(loaded);
/* TODO: Fix Hash_Load().
Hash_Store_Str(hash, "_class", 6,
(Obj*)CB_new_from_trusted_utf8("not_a_class", 11));
dump = (Obj*)Hash_Dump(hash);
loaded = (Hash*)Obj_Load(dump, dump);
TEST_TRUE(batch, Hash_Equals(hash, (Obj*)loaded),
"Load still works with _class if it's not a real class");
DECREF(dump);
DECREF(loaded);
*/
DECREF(hash);
}
static void
test_serialization(TestBatch *batch) {
Hash *wanted = Hash_new(0);
Hash *got;
uint32_t i;
for (i = 0; i < 10; i++) {
CharBuf *cb = S_random_string();
Integer32 *num = Int32_new(i);
Hash_Store(wanted, (Obj*)cb, (Obj*)num);
Hash_Store(wanted, (Obj*)num, (Obj*)cb);
}
got = (Hash*)TestUtils_freeze_thaw((Obj*)wanted);
TEST_TRUE(batch, got && Hash_Equals(wanted, (Obj*)got),
"Round trip through serialization.");
DECREF(got);
DECREF(wanted);
}
static void
test_stress(TestBatch *batch) {
uint32_t i;
Hash *hash = Hash_new(0); // trigger multiple rebuilds.
VArray *expected = VA_new(1000);
VArray *keys;
VArray *values;
for (i = 0; i < 1000; i++) {
CharBuf *cb = S_random_string();
while (Hash_Fetch(hash, (Obj*)cb)) {
DECREF(cb);
cb = S_random_string();
}
Hash_Store(hash, (Obj*)cb, (Obj*)cb);
VA_Push(expected, INCREF(cb));
}
VA_Sort(expected, NULL, NULL);
// Overwrite for good measure.
for (i = 0; i < 1000; i++) {
CharBuf *cb = (CharBuf*)VA_Fetch(expected, i);
Hash_Store(hash, (Obj*)cb, INCREF(cb));
}
keys = Hash_Keys(hash);
values = Hash_Values(hash);
VA_Sort(keys, NULL, NULL);
VA_Sort(values, NULL, NULL);
TEST_TRUE(batch, VA_Equals(keys, (Obj*)expected), "stress Keys");
TEST_TRUE(batch, VA_Equals(values, (Obj*)expected), "stress Values");
DECREF(keys);
DECREF(values);
DECREF(expected);
DECREF(hash);
}
void
TestHash_run_tests() {
TestBatch *batch = TestBatch_new(29);
srand((unsigned int)time((time_t*)NULL));
TestBatch_Plan(batch);
test_Equals(batch);
test_Store_and_Fetch(batch);
test_Keys_Values_Iter(batch);
test_Dump_and_Load(batch);
test_serialization(batch);
test_stress(batch);
DECREF(batch);
}