#include "unity/unity.h" #include #include #include /* Wrapper for the static function provided in the module */ extern void test_htmlCheckImplied(htmlParserCtxtPtr ctxt, const xmlChar *newtag); typedef struct { char **names; int count; int capacity; } Capture; static char *xstrdup(const char *s) { if (s == NULL) return NULL; size_t len = strlen(s) + 1; char *d = (char *)malloc(len); if (d) memcpy(d, s, len); return d; } static void cap_init(Capture *cap) { cap->names = NULL; cap->count = 0; cap->capacity = 0; } static void cap_add(Capture *cap, const char *name) { if (cap->count == cap->capacity) { int newcap = (cap->capacity == 0) ? 4 : cap->capacity * 2; char **nn = (char **)realloc(cap->names, (size_t)newcap * sizeof(char *)); if (!nn) return; /* keep silent; tests will fail if memory is out */ cap->names = nn; cap->capacity = newcap; } cap->names[cap->count++] = xstrdup(name); } static void cap_free(Capture *cap) { if (cap->names) { for (int i = 0; i < cap->count; i++) { free(cap->names[i]); } free(cap->names); } cap->names = NULL; cap->count = 0; cap->capacity = 0; } static void testStartElement(void *userData, const xmlChar *name, const xmlChar **atts) { (void)atts; Capture *cap = (Capture *)userData; cap_add(cap, (const char *)name); } static htmlParserCtxtPtr new_ctxt(Capture *cap, xmlSAXHandlerPtr *out_sax) { xmlSAXHandler *sax = (xmlSAXHandler *)calloc(1, sizeof(xmlSAXHandler)); if (sax == NULL) return NULL; memset(sax, 0, sizeof(xmlSAXHandler)); sax->startElement = testStartElement; htmlParserCtxtPtr ctxt = htmlCreatePushParserCtxt(sax, cap, "", 0, NULL, XML_CHAR_ENCODING_NONE); if (out_sax) *out_sax = sax; return ctxt; } static void free_ctxt(htmlParserCtxtPtr ctxt, xmlSAXHandlerPtr sax, Capture *cap) { if (ctxt) htmlFreeParserCtxt(ctxt); if (sax) free(sax); if (cap) cap_free(cap); } /* Unity hooks */ void setUp(void) { /* No global setup needed */ } void tearDown(void) { /* No global teardown needed */ } /* Tests */ void test_htmlCheckImplied_with_p_generates_html_and_body(void) { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); test_htmlCheckImplied(ctxt, (const xmlChar *)"p"); TEST_ASSERT_EQUAL_INT(2, cap.count); TEST_ASSERT_NOT_NULL(cap.names[0]); TEST_ASSERT_NOT_NULL(cap.names[1]); TEST_ASSERT_EQUAL_STRING("html", cap.names[0]); TEST_ASSERT_EQUAL_STRING("body", cap.names[1]); free_ctxt(ctxt, sax, &cap); } void test_htmlCheckImplied_with_head_generates_only_html(void) { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); test_htmlCheckImplied(ctxt, (const xmlChar *)"head"); TEST_ASSERT_EQUAL_INT(1, cap.count); TEST_ASSERT_EQUAL_STRING("html", cap.names[0]); free_ctxt(ctxt, sax, &cap); } void test_htmlCheckImplied_with_html_generates_nothing(void) { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); test_htmlCheckImplied(ctxt, (const xmlChar *)"html"); TEST_ASSERT_EQUAL_INT(0, cap.count); free_ctxt(ctxt, sax, &cap); } void test_htmlCheckImplied_with_script_generates_html_and_head(void) { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); test_htmlCheckImplied(ctxt, (const xmlChar *)"script"); TEST_ASSERT_TRUE(cap.count >= 1); TEST_ASSERT_EQUAL_STRING("html", cap.names[0]); /* Expect head generation in initial state */ TEST_ASSERT_EQUAL_INT(2, cap.count); TEST_ASSERT_EQUAL_STRING("head", cap.names[1]); free_ctxt(ctxt, sax, &cap); } void test_htmlCheckImplied_subsequent_p_does_not_generate_body_if_head_present(void) { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); /* First call with script: should push html + head */ test_htmlCheckImplied(ctxt, (const xmlChar *)"script"); TEST_ASSERT_EQUAL_INT(2, cap.count); TEST_ASSERT_EQUAL_STRING("html", cap.names[0]); TEST_ASSERT_EQUAL_STRING("head", cap.names[1]); /* Second call with p: should not generate body since head is on the stack */ test_htmlCheckImplied(ctxt, (const xmlChar *)"p"); TEST_ASSERT_EQUAL_INT(2, cap.count); /* no new events */ free_ctxt(ctxt, sax, &cap); } void test_htmlCheckImplied_no_duplicate_body_on_subsequent_calls(void) { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); /* First call with p: expect html + body */ test_htmlCheckImplied(ctxt, (const xmlChar *)"p"); TEST_ASSERT_EQUAL_INT(2, cap.count); TEST_ASSERT_EQUAL_STRING("html", cap.names[0]); TEST_ASSERT_EQUAL_STRING("body", cap.names[1]); /* Second call with span: body already on stack, so nothing new */ test_htmlCheckImplied(ctxt, (const xmlChar *)"span"); TEST_ASSERT_EQUAL_INT(2, cap.count); free_ctxt(ctxt, sax, &cap); } void test_htmlCheckImplied_frame_like_tags_do_not_generate_body(void) { /* frame */ { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); test_htmlCheckImplied(ctxt, (const xmlChar *)"frame"); TEST_ASSERT_EQUAL_INT(1, cap.count); TEST_ASSERT_EQUAL_STRING("html", cap.names[0]); free_ctxt(ctxt, sax, &cap); } /* frameset */ { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); test_htmlCheckImplied(ctxt, (const xmlChar *)"frameset"); TEST_ASSERT_EQUAL_INT(1, cap.count); TEST_ASSERT_EQUAL_STRING("html", cap.names[0]); free_ctxt(ctxt, sax, &cap); } /* noframes */ { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); test_htmlCheckImplied(ctxt, (const xmlChar *)"noframes"); TEST_ASSERT_EQUAL_INT(1, cap.count); TEST_ASSERT_EQUAL_STRING("html", cap.names[0]); free_ctxt(ctxt, sax, &cap); } } void test_htmlCheckImplied_noimplied_option_suppresses_generation(void) { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); /* Set NOIMPLIED option */ int rc = htmlCtxtUseOptions(ctxt, HTML_PARSE_NOIMPLIED); (void)rc; test_htmlCheckImplied(ctxt, (const xmlChar *)"p"); /* No implied insertions should occur */ TEST_ASSERT_EQUAL_INT(0, cap.count); free_ctxt(ctxt, sax, &cap); } void test_htmlCheckImplied_html5_option_suppresses_generation(void) { Capture cap; cap_init(&cap); xmlSAXHandlerPtr sax = NULL; htmlParserCtxtPtr ctxt = new_ctxt(&cap, &sax); TEST_ASSERT_NOT_NULL(ctxt); /* Set HTML5 option */ int rc = htmlCtxtUseOptions(ctxt, HTML_PARSE_HTML5); (void)rc; test_htmlCheckImplied(ctxt, (const xmlChar *)"p"); /* No implied insertions should occur */ TEST_ASSERT_EQUAL_INT(0, cap.count); free_ctxt(ctxt, sax, &cap); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_htmlCheckImplied_with_p_generates_html_and_body); RUN_TEST(test_htmlCheckImplied_with_head_generates_only_html); RUN_TEST(test_htmlCheckImplied_with_html_generates_nothing); RUN_TEST(test_htmlCheckImplied_with_script_generates_html_and_head); RUN_TEST(test_htmlCheckImplied_subsequent_p_does_not_generate_body_if_head_present); RUN_TEST(test_htmlCheckImplied_no_duplicate_body_on_subsequent_calls); RUN_TEST(test_htmlCheckImplied_frame_like_tags_do_not_generate_body); RUN_TEST(test_htmlCheckImplied_noimplied_option_suppresses_generation); RUN_TEST(test_htmlCheckImplied_html5_option_suppresses_generation); return UNITY_END(); }