File size: 6,302 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 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
#include "unity/unity.h"
#include <libxml/HTMLparser.h>
#include <libxml/parserInternals.h>
#include <libxml/xmlstring.h>
#include <libxml/encoding.h>
#include <string.h>
#include <stdlib.h>
/* The function under test is exposed via a test wrapper */
void test_htmlAutoCloseOnClose(htmlParserCtxtPtr ctxt, const xmlChar * newtag);
/* Helper to collect endElement events */
typedef struct {
xmlChar **names;
int count;
int cap;
} EndEvents;
static void EndEvents_init(EndEvents *ev) {
ev->names = NULL;
ev->count = 0;
ev->cap = 0;
}
static void EndEvents_push(EndEvents *ev, const xmlChar *name) {
if (ev->count == ev->cap) {
int newCap = (ev->cap == 0) ? 8 : ev->cap * 2;
xmlChar **newArr = (xmlChar **)realloc(ev->names, (size_t)newCap * sizeof(xmlChar *));
TEST_ASSERT_NOT_NULL_MESSAGE(newArr, "Failed to allocate event array");
ev->names = newArr;
ev->cap = newCap;
}
ev->names[ev->count++] = xmlStrdup(name);
}
static void EndEvents_free(EndEvents *ev) {
if (ev->names) {
for (int i = 0; i < ev->count; i++) {
if (ev->names[i]) xmlFree(ev->names[i]);
}
free(ev->names);
}
ev->names = NULL;
ev->count = 0;
ev->cap = 0;
}
/* SAX handler that records endElement calls */
static void recordingEndElement(void *userData, const xmlChar *name) {
EndEvents *ev = (EndEvents *)userData;
if (ev && name) {
EndEvents_push(ev, name);
}
}
/* We use a static SAX handler to ensure its lifetime outlives the parser context */
static xmlSAXHandler gSAX;
/* Build a parser context, attach SAX handler to capture endElement events, and feed HTML chunk */
static htmlParserCtxtPtr build_ctx_with_stack(const char *htmlChunk, EndEvents *events) {
memset(&gSAX, 0, sizeof(gSAX));
gSAX.endElement = recordingEndElement; /* SAX1 endElement will be called by the target function */
htmlParserCtxtPtr ctxt = htmlCreatePushParserCtxt(&gSAX, events, NULL, 0, NULL, XML_CHAR_ENCODING_NONE);
TEST_ASSERT_NOT_NULL_MESSAGE(ctxt, "Failed to create HTML push parser context");
/* Feed only opening tags to build a stack of open elements */
int len = (int)strlen(htmlChunk);
int rc = htmlParseChunk(ctxt, htmlChunk, len, 0);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "htmlParseChunk returned error building initial stack");
return ctxt;
}
void setUp(void) {
/* Setup code here, or leave empty */
}
void tearDown(void) {
/* Cleanup code here, or leave empty */
}
/* Test: closes intermediate elements until the matching tag is on top; endElement called for each */
void test_htmlAutoCloseOnClose_closes_intermediate_elements_until_match(void) {
EndEvents events;
EndEvents_init(&events);
/* Build a stack: ... <xdiv><xspan><xem> (xem is on top) */
htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events);
TEST_ASSERT_NOT_NULL(ctxt->name);
int beforeNr = ctxt->nameNr;
/* Trigger autoclose with closing tag for xdiv */
test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xdiv");
/* Should have closed xem then xspan (LIFO), leaving xdiv on top */
TEST_ASSERT_EQUAL_INT(2, events.count);
TEST_ASSERT_TRUE(xmlStrEqual(events.names[0], BAD_CAST "xem"));
TEST_ASSERT_TRUE(xmlStrEqual(events.names[1], BAD_CAST "xspan"));
TEST_ASSERT_NOT_NULL(ctxt->name);
TEST_ASSERT_TRUE(xmlStrEqual(ctxt->name, BAD_CAST "xdiv"));
TEST_ASSERT_EQUAL_INT(beforeNr - 2, ctxt->nameNr);
htmlFreeParserCtxt(ctxt);
EndEvents_free(&events);
}
/* Test: no action when target tag is not found in the open element stack */
void test_htmlAutoCloseOnClose_no_action_if_target_not_found(void) {
EndEvents events;
EndEvents_init(&events);
/* Build a stack: ... <xdiv><xspan><xem> */
htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events);
int beforeNr = ctxt->nameNr;
const xmlChar *beforeTop = ctxt->name;
/* Target "xfoo" doesn't exist in the stack: function should do nothing */
test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xfoo");
TEST_ASSERT_EQUAL_INT(0, events.count);
TEST_ASSERT_EQUAL_INT(beforeNr, ctxt->nameNr);
TEST_ASSERT_TRUE(xmlStrEqual(ctxt->name, beforeTop));
htmlFreeParserCtxt(ctxt);
EndEvents_free(&events);
}
/* Test: no action when the target tag already matches the top of the stack */
void test_htmlAutoCloseOnClose_no_action_if_match_at_top(void) {
EndEvents events;
EndEvents_init(&events);
/* Build a stack: ... <xdiv><xspan> (xspan is on top) */
htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan>", &events);
int beforeNr = ctxt->nameNr;
const xmlChar *beforeTop = ctxt->name;
TEST_ASSERT_TRUE(xmlStrEqual(beforeTop, BAD_CAST "xspan"));
/* Target is already the top element; no intermediate pops expected */
test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xspan");
TEST_ASSERT_EQUAL_INT(0, events.count);
TEST_ASSERT_EQUAL_INT(beforeNr, ctxt->nameNr);
TEST_ASSERT_TRUE(xmlStrEqual(ctxt->name, BAD_CAST "xspan"));
htmlFreeParserCtxt(ctxt);
EndEvents_free(&events);
}
/* Test: in HTML5 mode (HTML_PARSE_HTML5), the function must return immediately and do nothing */
void test_htmlAutoCloseOnClose_early_return_in_html5_mode(void) {
EndEvents events;
EndEvents_init(&events);
/* Build a stack: ... <xdiv><xspan><xem> */
htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events);
int beforeNr = ctxt->nameNr;
const xmlChar *beforeTop = ctxt->name;
/* Enable HTML5 mode */
ctxt->options |= HTML_PARSE_HTML5;
test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xdiv");
/* No changes expected */
TEST_ASSERT_EQUAL_INT(0, events.count);
TEST_ASSERT_EQUAL_INT(beforeNr, ctxt->nameNr);
TEST_ASSERT_TRUE(xmlStrEqual(ctxt->name, beforeTop));
htmlFreeParserCtxt(ctxt);
EndEvents_free(&events);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_htmlAutoCloseOnClose_closes_intermediate_elements_until_match);
RUN_TEST(test_htmlAutoCloseOnClose_no_action_if_target_not_found);
RUN_TEST(test_htmlAutoCloseOnClose_no_action_if_match_at_top);
RUN_TEST(test_htmlAutoCloseOnClose_early_return_in_html5_mode);
return UNITY_END();
} |