#include "unity/unity.h" #include #include #include #include #include #include /* Wrapper for the static function under test provided in the module */ extern void test_htmlParserFinishElementParsing(htmlParserCtxtPtr ctxt); static htmlParserCtxtPtr create_ctxt_with_input(size_t base_len, size_t cur_offset, unsigned long consumed, unsigned long line, xmlParserInputPtr *outInput, xmlChar **outBase) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL_MESSAGE(ctxt, "Failed to create HTML parser context"); xmlParserInputPtr in = xmlNewInputStream(ctxt); TEST_ASSERT_NOT_NULL_MESSAGE(in, "Failed to create new input stream"); xmlChar *base = (xmlChar *)xmlMalloc(base_len); TEST_ASSERT_NOT_NULL_MESSAGE(base, "Failed to allocate base buffer"); memset(base, 'A', base_len); /* Ensure cur_offset within base_len */ TEST_ASSERT_MESSAGE(cur_offset <= base_len, "cur_offset exceeds base buffer length"); in->base = base; in->cur = base + cur_offset; in->end = base + base_len; in->consumed = consumed; in->line = line; ctxt->input = in; if (outInput) *outInput = in; if (outBase) *outBase = base; return ctxt; } static void destroy_ctxt_and_input(htmlParserCtxtPtr ctxt, xmlParserInputPtr in, xmlChar *base) { if (ctxt != NULL) { /* Detach input from context so we can free it explicitly to avoid double-free */ ctxt->input = NULL; } if (in != NULL) { /* Avoid freeing base twice if libxml would attempt it; we own base */ in->base = NULL; in->cur = NULL; in->end = NULL; xmlFreeInputStream(in); } if (base != NULL) { xmlFree(base); } if (ctxt != NULL) { htmlFreeParserCtxt(ctxt); } } void setUp(void) { xmlInitParser(); } void tearDown(void) { xmlCleanupParser(); } void test_htmlParserFinishElementParsing_records_end_info_basic(void) { xmlParserInputPtr in = NULL; xmlChar *base = NULL; /* base length 32, cur_offset 5, consumed 2, line 7 */ htmlParserCtxtPtr ctxt = create_ctxt_with_input(32, 5, 2, 7, &in, &base); /* Prepare node and nodeInfo */ xmlNodePtr node = xmlNewNode(NULL, (const xmlChar *)"test"); TEST_ASSERT_NOT_NULL(node); ctxt->node = node; ctxt->record_info = 1; xmlParserNodeInfo *info = (xmlParserNodeInfo *)xmlMalloc(sizeof(xmlParserNodeInfo)); TEST_ASSERT_NOT_NULL(info); memset(info, 0, sizeof(*info)); ctxt->nodeInfo = info; /* Call the wrapper */ test_htmlParserFinishElementParsing(ctxt); /* Verify info was added to the context's node info sequence */ const xmlParserNodeInfo *found = xmlParserFindNodeInfo(ctxt, node); TEST_ASSERT_NOT_NULL_MESSAGE(found, "xmlParserNodeInfo not found for node"); TEST_ASSERT_EQUAL_PTR(node, found->node); /* end_pos = consumed + (cur - base) = 2 + (5) = 7 */ TEST_ASSERT_EQUAL_UINT(7u, (unsigned int)found->end_pos); TEST_ASSERT_EQUAL_UINT(7u, (unsigned int)found->end_line); /* Cleanup */ xmlFreeNode(node); /* info was likely freed by htmlNodeInfoPop; freeing only if still referenced would be unsafe. We won't free 'info' here. */ destroy_ctxt_and_input(ctxt, in, base); } void test_htmlParserFinishElementParsing_calculates_end_pos_with_consumed_offset(void) { xmlParserInputPtr in = NULL; xmlChar *base = NULL; /* base length 64, cur_offset 13, consumed 21, line 3 */ htmlParserCtxtPtr ctxt = create_ctxt_with_input(64, 13, 21, 3, &in, &base); xmlNodePtr node = xmlNewNode(NULL, (const xmlChar *)"elem"); TEST_ASSERT_NOT_NULL(node); ctxt->node = node; ctxt->record_info = 1; xmlParserNodeInfo *info = (xmlParserNodeInfo *)xmlMalloc(sizeof(xmlParserNodeInfo)); TEST_ASSERT_NOT_NULL(info); memset(info, 0, sizeof(*info)); ctxt->nodeInfo = info; test_htmlParserFinishElementParsing(ctxt); const xmlParserNodeInfo *found = xmlParserFindNodeInfo(ctxt, node); TEST_ASSERT_NOT_NULL_MESSAGE(found, "xmlParserNodeInfo not found for node"); TEST_ASSERT_EQUAL_PTR(node, found->node); /* end_pos = 21 + 13 = 34; end_line = 3 */ TEST_ASSERT_EQUAL_UINT(34u, (unsigned int)found->end_pos); TEST_ASSERT_EQUAL_UINT(3u, (unsigned int)found->end_line); xmlFreeNode(node); destroy_ctxt_and_input(ctxt, in, base); } void test_htmlParserFinishElementParsing_noop_when_node_null(void) { xmlParserInputPtr in = NULL; xmlChar *base = NULL; htmlParserCtxtPtr ctxt = create_ctxt_with_input(16, 4, 9, 11, &in, &base); /* node is NULL; enable record_info to test the node NULL guard */ ctxt->node = NULL; ctxt->record_info = 1; /* Provide a nodeInfo struct; function must not use it since node is NULL */ xmlParserNodeInfo *info = (xmlParserNodeInfo *)xmlMalloc(sizeof(xmlParserNodeInfo)); TEST_ASSERT_NOT_NULL(info); memset(info, 0, sizeof(*info)); ctxt->nodeInfo = info; test_htmlParserFinishElementParsing(ctxt); /* Create a node to search for; should not find any info since node was NULL */ xmlNodePtr node = xmlNewNode(NULL, (const xmlChar *)"unused"); TEST_ASSERT_NOT_NULL(node); const xmlParserNodeInfo *found = xmlParserFindNodeInfo(ctxt, node); TEST_ASSERT_NULL(found); xmlFreeNode(node); /* As above, assume htmlNodeInfoPop may have freed info; do not free directly. */ destroy_ctxt_and_input(ctxt, in, base); } void test_htmlParserFinishElementParsing_noop_when_record_info_false_even_with_node(void) { /* Intentionally no input to ensure function doesn't touch input when record_info == 0 */ htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); xmlNodePtr node = xmlNewNode(NULL, (const xmlChar *)"n"); TEST_ASSERT_NOT_NULL(node); ctxt->node = node; ctxt->record_info = 0; /* disabled */ ctxt->input = NULL; /* would crash if accessed */ /* Provide a nodeInfo pointer; must not be used since record_info == 0 */ xmlParserNodeInfo *info = (xmlParserNodeInfo *)xmlMalloc(sizeof(xmlParserNodeInfo)); TEST_ASSERT_NOT_NULL(info); memset(info, 0, sizeof(*info)); ctxt->nodeInfo = info; test_htmlParserFinishElementParsing(ctxt); /* Since recording is disabled, no node info should be present */ const xmlParserNodeInfo *found = xmlParserFindNodeInfo(ctxt, node); TEST_ASSERT_NULL(found); xmlFreeNode(node); /* Do not free 'info' as it may be managed by internal pop logic in other cases. */ htmlFreeParserCtxt(ctxt); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_htmlParserFinishElementParsing_records_end_info_basic); RUN_TEST(test_htmlParserFinishElementParsing_calculates_end_pos_with_consumed_offset); RUN_TEST(test_htmlParserFinishElementParsing_noop_when_node_null); RUN_TEST(test_htmlParserFinishElementParsing_noop_when_record_info_false_even_with_node); return UNITY_END(); }