libxml / tests /tests_HTMLparser_htmlAutoCloseOnEnd.c
AryaWu's picture
Upload folder using huggingface_hub
6baed57 verified
#include "unity/unity.h"
#include <libxml/HTMLparser.h>
#include <libxml/parser.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
/* Wrapper provided in the module under test for the static function */
void test_htmlAutoCloseOnEnd(htmlParserCtxtPtr ctxt);
/* Recorder for SAX endElement calls */
typedef struct {
int call_count;
char names[64][64]; /* store up to 64 element names, 63 chars each */
} EndRec;
static xmlSAXHandler g_sax; /* must be static/global so the pointer remains valid */
static void on_end_element(void *userData, const xmlChar *name) {
EndRec *rec = (EndRec *)userData;
if (rec == NULL || name == NULL)
return;
if (rec->call_count < 64) {
strncpy(rec->names[rec->call_count], (const char *)name, sizeof(rec->names[0]) - 1);
rec->names[rec->call_count][sizeof(rec->names[0]) - 1] = '\0';
rec->call_count++;
}
}
static int ci_equal(const char *a, const char *b) {
if (a == NULL || b == NULL) return 0;
while (*a && *b) {
unsigned char ca = (unsigned char)*a;
unsigned char cb = (unsigned char)*b;
if (tolower(ca) != tolower(cb)) return 0;
a++; b++;
}
return *a == *b;
}
/* Helper to create a push parser context with our SAX recorder */
static htmlParserCtxtPtr create_push_ctxt(EndRec *rec) {
return htmlCreatePushParserCtxt(&g_sax, rec, NULL, 0, NULL, XML_CHAR_ENCODING_NONE);
}
/* Helper to feed a chunk to the push parser */
static void feed_chunk(htmlParserCtxtPtr ctxt, const char *data) {
htmlParseChunk(ctxt, data, (int)strlen(data), 0);
}
void setUp(void) {
/* initialize global SAX handler and libxml2 */
memset(&g_sax, 0, sizeof(g_sax));
g_sax.endElement = on_end_element;
xmlInitParser();
}
void tearDown(void) {
xmlCleanupParser();
}
/* Test: when there are no open elements (nameNr == 0), function does nothing */
void test_htmlAutoCloseOnEnd_no_open_elements_noop(void) {
EndRec rec = {0};
htmlParserCtxtPtr ctxt = create_push_ctxt(&rec);
TEST_ASSERT_NOT_NULL(ctxt);
/* Ensure no input was fed; nameNr should be 0 */
TEST_ASSERT_EQUAL_INT(0, ctxt->nameNr);
test_htmlAutoCloseOnEnd(ctxt);
TEST_ASSERT_EQUAL_INT(0, ctxt->nameNr);
TEST_ASSERT_EQUAL_INT(0, rec.call_count);
htmlFreeParserCtxt(ctxt);
}
/* Test: closes all open elements and invokes endElement for each in reverse order */
void test_htmlAutoCloseOnEnd_closes_stack_and_calls_endElement_reverse_order(void) {
EndRec rec = {0};
htmlParserCtxtPtr ctxt = create_push_ctxt(&rec);
TEST_ASSERT_NOT_NULL(ctxt);
/* Feed partial HTML leaving elements unclosed */
feed_chunk(ctxt, "<div><span>");
/* The parser likely opened implicit elements like html/body; verify we have some open names */
TEST_ASSERT_TRUE_MESSAGE(ctxt->nameNr > 0, "Expected at least one open element on the stack");
/* Capture the top-of-stack element name before we call the function */
char top_before[64] = {0};
if (ctxt->name != NULL) {
strncpy(top_before, (const char *)ctxt->name, sizeof(top_before) - 1);
}
/* Reset recorder to only capture calls from htmlAutoCloseOnEnd */
rec.call_count = 0;
int initial_nameNr = ctxt->nameNr;
test_htmlAutoCloseOnEnd(ctxt);
/* All names should be popped */
TEST_ASSERT_EQUAL_INT(0, ctxt->nameNr);
/* endElement should have been called once per previously open element */
TEST_ASSERT_EQUAL_INT(initial_nameNr, rec.call_count);
/* First callback should correspond to the previous top element (reverse order) */
if (top_before[0] != '\0') {
TEST_ASSERT_TRUE_MESSAGE(ci_equal(rec.names[0], top_before),
"First endElement should be for the previous top-of-stack element");
}
htmlFreeParserCtxt(ctxt);
}
/* Test: returns early when HTML_PARSE_HTML5 option is set */
void test_htmlAutoCloseOnEnd_returns_early_with_HTML5_option(void) {
EndRec rec = {0};
htmlParserCtxtPtr ctxt = create_push_ctxt(&rec);
TEST_ASSERT_NOT_NULL(ctxt);
feed_chunk(ctxt, "<div><span>");
TEST_ASSERT_TRUE_MESSAGE(ctxt->nameNr > 0, "Expected open elements before testing HTML5 early return");
int before_nameNr = ctxt->nameNr;
ctxt->options |= HTML_PARSE_HTML5;
test_htmlAutoCloseOnEnd(ctxt);
/* Should be unchanged and no callbacks should be invoked */
TEST_ASSERT_EQUAL_INT(before_nameNr, ctxt->nameNr);
TEST_ASSERT_EQUAL_INT(0, rec.call_count);
htmlFreeParserCtxt(ctxt);
}
/* Test: with NULL SAX handler, the function still empties the stack but doesn't call endElement */
void test_htmlAutoCloseOnEnd_handles_null_sax_handler(void) {
EndRec rec = {0};
htmlParserCtxtPtr ctxt = create_push_ctxt(&rec);
TEST_ASSERT_NOT_NULL(ctxt);
feed_chunk(ctxt, "<div>");
TEST_ASSERT_TRUE_MESSAGE(ctxt->nameNr > 0, "Expected open elements before null SAX test");
int initial_nameNr = ctxt->nameNr;
/* Remove SAX handler to simulate NULL sax/endElement */
ctxt->sax = NULL;
test_htmlAutoCloseOnEnd(ctxt);
TEST_ASSERT_EQUAL_INT(0, ctxt->nameNr);
TEST_ASSERT_EQUAL_INT(0, rec.call_count); /* no callbacks since sax is NULL */
(void)initial_nameNr; /* suppress unused warning if not used in some configs */
htmlFreeParserCtxt(ctxt);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_htmlAutoCloseOnEnd_no_open_elements_noop);
RUN_TEST(test_htmlAutoCloseOnEnd_closes_stack_and_calls_endElement_reverse_order);
RUN_TEST(test_htmlAutoCloseOnEnd_returns_early_with_HTML5_option);
RUN_TEST(test_htmlAutoCloseOnEnd_handles_null_sax_handler);
return UNITY_END();
}