1 1.1 nia /* Tests related to the hash tables used inside Expat 2 1.1 nia __ __ _ 3 1.1 nia ___\ \/ /_ __ __ _| |_ 4 1.1 nia / _ \\ /| '_ \ / _` | __| 5 1.1 nia | __// \| |_) | (_| | |_ 6 1.1 nia \___/_/\_\ .__/ \__,_|\__| 7 1.1 nia |_| XML parser 8 1.1 nia 9 1.1 nia Copyright (c) 2026 Sebastian Pipping <sebastian (at) pipping.org> 10 1.1 nia Licensed under the MIT license: 11 1.1 nia 12 1.1 nia Permission is hereby granted, free of charge, to any person obtaining 13 1.1 nia a copy of this software and associated documentation files (the 14 1.1 nia "Software"), to deal in the Software without restriction, including 15 1.1 nia without limitation the rights to use, copy, modify, merge, publish, 16 1.1 nia distribute, sublicense, and/or sell copies of the Software, and to permit 17 1.1 nia persons to whom the Software is furnished to do so, subject to the 18 1.1 nia following conditions: 19 1.1 nia 20 1.1 nia The above copyright notice and this permission notice shall be included 21 1.1 nia in all copies or substantial portions of the Software. 22 1.1 nia 23 1.1 nia THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 1.1 nia EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 1.1 nia MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 26 1.1 nia NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 27 1.1 nia DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 28 1.1 nia OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 29 1.1 nia USE OR OTHER DEALINGS IN THE SOFTWARE. 30 1.1 nia 31 1.1 nia SPDX-License-Identifier: MIT 32 1.1 nia */ 33 1.1 nia 34 1.1 nia #include "hash_tests.h" 35 1.1 nia 36 1.1 nia #include "common.h" // for XCS 37 1.1 nia #include "expat.h" 38 1.1 nia #include "hash_table.h" 39 1.1 nia #include "minicheck.h" 40 1.1 nia 41 1.1 nia #include <stdbool.h> 42 1.1 nia #include <string.h> // for memcmp 43 1.1 nia 44 1.1 nia START_TEST(test_hash_table) { 45 1.1 nia // The test is not doing any parsing, so a single run 46 1.1 nia // (with `g_chunkSize == 0`) is enough 47 1.1 nia if (g_chunkSize != 0) 48 1.1 nia return; 49 1.1 nia 50 1.1 nia typedef struct { 51 1.1 nia const XML_Char *name; 52 1.1 nia bool initialized; 53 1.1 nia } NAME_AND_FLAG; 54 1.1 nia 55 1.1 nia HASH_TABLE table; 56 1.1 nia XML_Parser parser = XML_ParserCreate(NULL); 57 1.1 nia hashTableInit(&table, parser); 58 1.1 nia 59 1.1 nia const XML_Char *const key1 = XCS("key1"); 60 1.1 nia const XML_Char *const key2 = XCS("key2"); 61 1.1 nia const XML_Char *const key3 = XCS("key1overlap"); 62 1.1 nia 63 1.1 nia // Self-test: `key3` starts with `key1` but is different from it 64 1.1 nia assert_true(memcmp(key1, key3, keylen(key1)) == 0); 65 1.1 nia assert_true(keyeq(key1, keylen(key1), key3) == XML_FALSE); 66 1.1 nia 67 1.1 nia // Test: Look up false for all keys because the table is empty 68 1.1 nia assert_true(lookup(parser, &table, key1, 0) == NULL); 69 1.1 nia assert_true(lookup(parser, &table, key2, 0) == NULL); 70 1.1 nia assert_true(lookup(parser, &table, key3, 0) == NULL); 71 1.1 nia 72 1.1 nia // Test: Iteration yields 0 items initially 73 1.1 nia { 74 1.1 nia HASH_TABLE_ITER iter1; 75 1.1 nia hashTableIterInit(&iter1, &table); 76 1.1 nia assert_true(hashTableIterNext(&iter1) == NULL); 77 1.1 nia } 78 1.1 nia 79 1.1 nia // Test: Insertion works (including initialization to zero) 80 1.1 nia NAME_AND_FLAG *const inserted1 81 1.1 nia = (NAME_AND_FLAG *)lookup(parser, &table, key1, sizeof(NAME_AND_FLAG)); 82 1.1 nia assert_true(inserted1 != NULL); 83 1.1 nia assert_true(inserted1->name == key1); 84 1.1 nia assert_true(! inserted1->initialized); 85 1.1 nia 86 1.1 nia // Make it possible to tell the struct apart from a freshly inserted version 87 1.1 nia inserted1->initialized = true; 88 1.1 nia 89 1.1 nia // Test: Only present keys can be looked up 90 1.1 nia assert_true(lookup(parser, &table, key1, 0) != NULL); 91 1.1 nia assert_true(lookup(parser, &table, key2, 0) == NULL); 92 1.1 nia assert_true(lookup(parser, &table, key3, 0) == NULL); 93 1.1 nia 94 1.1 nia // Test: Key length works without false positives 95 1.1 nia assert_true(lookupWithLength(parser, &table, key3, /*nameLen=*/3, 0) == NULL); 96 1.1 nia assert_true(lookupWithLength(parser, &table, key3, /*nameLen=*/4, 0) != NULL); 97 1.1 nia assert_true(lookupWithLength(parser, &table, key3, /*nameLen=*/5, 0) == NULL); 98 1.1 nia 99 1.1 nia // TEST: Lookup does not reset existing entries to zeros 100 1.1 nia NAME_AND_FLAG *const found 101 1.1 nia = (NAME_AND_FLAG *)lookup(parser, &table, key1, sizeof(NAME_AND_FLAG)); 102 1.1 nia assert_true(found != NULL); 103 1.1 nia assert_true(found->name == key1); 104 1.1 nia assert_true(found->initialized); // this is key 105 1.1 nia 106 1.1 nia // Test: Insertion of a second item works 107 1.1 nia assert_true(lookup(parser, &table, key2, 0) == NULL); 108 1.1 nia assert_true(lookup(parser, &table, key2, sizeof(NAME_AND_FLAG)) != NULL); 109 1.1 nia assert_true(lookup(parser, &table, key2, 0) != NULL); 110 1.1 nia 111 1.1 nia // Test: Iteration yields nothing but the two expected items 112 1.1 nia { 113 1.1 nia HASH_TABLE_ITER iter2; 114 1.1 nia hashTableIterInit(&iter2, &table); 115 1.1 nia size_t itemCount = 0; 116 1.1 nia while (true) { 117 1.1 nia const NAME_AND_FLAG *const item 118 1.1 nia = (const NAME_AND_FLAG *)hashTableIterNext(&iter2); 119 1.1 nia if (item == NULL) 120 1.1 nia break; 121 1.1 nia 122 1.1 nia itemCount++; 123 1.1 nia 124 1.1 nia if (keyeq(key1, keylen(key1), item->name) == XML_TRUE) 125 1.1 nia assert_true(item->initialized); 126 1.1 nia else if (keyeq(key2, keylen(key2), item->name) == XML_TRUE) 127 1.1 nia assert_true(! item->initialized); 128 1.1 nia else 129 1.1 nia fail("unexpected item .name"); 130 1.1 nia } 131 1.1 nia assert_true(itemCount == 2); 132 1.1 nia } 133 1.1 nia 134 1.1 nia // Test: After clearing all lookups fail 135 1.1 nia hashTableClear(&table); 136 1.1 nia assert_true(lookup(parser, &table, key1, 0) == NULL); 137 1.1 nia assert_true(lookup(parser, &table, key2, 0) == NULL); 138 1.1 nia assert_true(lookup(parser, &table, key3, 0) == NULL); 139 1.1 nia 140 1.1 nia // Test: After clearing iteration yields 0 items again 141 1.1 nia { 142 1.1 nia HASH_TABLE_ITER iter3; 143 1.1 nia hashTableIterInit(&iter3, &table); 144 1.1 nia assert_true(hashTableIterNext(&iter3) == NULL); 145 1.1 nia } 146 1.1 nia 147 1.1 nia hashTableDestroy(&table); 148 1.1 nia XML_ParserFree(parser); 149 1.1 nia } 150 1.1 nia END_TEST 151 1.1 nia 152 1.1 nia void 153 1.1 nia make_hash_test_case(Suite *s) { 154 1.1 nia TCase *const tc_hash = tcase_create("hash tests"); 155 1.1 nia suite_add_tcase(s, tc_hash); 156 1.1 nia tcase_add_test(tc_hash, test_hash_table); 157 1.1 nia } 158