File size: 5,695 Bytes
6baed57 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
#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();
} |