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