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