Files
DFTE/test/test_template_engine/tests/test_edge_cases.cpp
T
alex 73fdd28f74 Harden DFTE rendering and placeholder registration to fail safely under stalled and invalid inputs.
Add regression coverage and docs updates so these edge cases stay stable across board test runs.
2026-04-16 13:48:25 +10:00

318 lines
14 KiB
C++

#include <unity.h>
#include <Arduino.h>
#include <TemplateEngine.h>
#include "../templates/simple_templates.h"
#include "../templates/placeholder_templates.h"
#include "../templates/edge_case_templates.h"
#include "../templates/nested_templates.h"
#include "../utils/test_utils.h"
// Test RAM data getter that returns null
static const char* getNullRamData() {
return nullptr;
}
// Test RAM data getter that returns empty string
static const char* getEmptyRamData() {
static String empty = "";
return empty.c_str();
}
// Test error handling
void test_edge_cases_error_handling() {
Serial.println("[TEST] Testing error handling...");
PlaceholderRegistry registry(10);
TemplateContext ctx;
ctx.setRegistry(&registry);
// Test render with null template pointer (should enter error cleanly)
TemplateRenderer::initializeContext(ctx, nullptr, false);
TEST_ASSERT_TRUE_MESSAGE(ctx.hasError(),
"Null template pointer should set context to error");
uint8_t nullBuffer[8];
size_t nullWritten = TemplateRenderer::renderNextChunk(ctx, nullBuffer, sizeof(nullBuffer));
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0u, static_cast<uint32_t>(nullWritten),
"Null template render should produce no output");
// Test render with empty template
ctx.reset();
ctx.setRegistry(&registry);
TemplateRenderer::initializeContext(ctx, empty_template);
String result1 = captureRenderedOutput(ctx);
TEST_ASSERT_EQUAL_STRING_MESSAGE("", result1.c_str(),
"Should handle empty template");
// Test render with null registry
ctx.reset();
ctx.setRegistry(nullptr);
TemplateRenderer::initializeContext(ctx, single_placeholder_template);
String result2 = captureRenderedOutput(ctx);
// Should skip placeholder and render text
TEST_ASSERT_EQUAL_STRING_MESSAGE("Hello, !", result2.c_str(),
"Should handle null registry");
// Test RAM getter returning null
registry.registerRamData("%NULL%", getNullRamData);
ctx.reset();
TemplateRenderer::initializeContext(ctx, single_placeholder_template);
ctx.setRegistry(&registry);
String result3 = captureRenderedOutput(ctx);
// Should handle null return gracefully
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete even with null RAM getter");
// Test RAM getter returning empty string
registry.registerRamData("%EMPTY%", getEmptyRamData);
ctx.reset();
TemplateRenderer::initializeContext(ctx, single_placeholder_template);
ctx.setRegistry(&registry);
String result4 = captureRenderedOutput(ctx);
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with empty RAM data");
// Test incomplete placeholder (no closing %)
ctx.reset();
TemplateRenderer::initializeContext(ctx, incomplete_placeholder_template);
ctx.setRegistry(&registry);
String result5 = captureRenderedOutput(ctx);
// Should handle incomplete placeholder gracefully
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete even with incomplete placeholder");
Serial.println("[TEST] Error handling tests completed successfully");
}
// Test boundary conditions
void test_edge_cases_boundary_conditions() {
Serial.println("[TEST] Testing boundary conditions...");
PlaceholderRegistry registry(10);
registry.registerProgmemData("%PLACEHOLDER%", test_css_data);
TemplateContext ctx;
ctx.setRegistry(&registry);
// Test placeholder at start
// PLACEHOLDER is already registered above, so we can use it
Serial.print("[DEBUG] Edge case - %PLACEHOLDER% registered: ");
Serial.println(registry.getPlaceholder("%PLACEHOLDER%") != nullptr ? "YES" : "NO");
ctx.reset();
ctx.setRegistry(&registry);
TemplateRenderer::initializeContext(ctx, placeholder_start_template);
Serial.print("[DEBUG] Edge case template: [");
Serial.print(placeholder_start_template);
Serial.println("]");
String result1 = captureRenderedOutput(ctx);
Serial.print("[DEBUG] Edge case result length: ");
Serial.println(result1.length());
Serial.print("[DEBUG] Edge case result: [");
Serial.print(result1);
Serial.println("]");
Serial.print("[DEBUG] Edge case - Contains ' text': ");
Serial.println(result1.indexOf(" text") >= 0 ? "YES" : "NO");
Serial.print("[DEBUG] Edge case - Contains 'body': ");
Serial.println(result1.indexOf("body") >= 0 ? "YES" : "NO");
Serial.print("[DEBUG] Edge case - Final state: ");
Serial.println(ctx.getStateString());
RenderingContext* currentCtx = ctx.getCurrentContext();
if (currentCtx && currentCtx->type == RenderingContextType::TEMPLATE) {
Serial.print("[DEBUG] Edge case - Template length: ");
Serial.println(currentCtx->context.templateCtx.templateLen);
}
// Should render placeholder and text
TEST_ASSERT_TRUE_MESSAGE(result1.length() > 0, "Should handle placeholder at start");
// Check for either the placeholder content or the text part
TEST_ASSERT_TRUE_MESSAGE(result1.indexOf(" text") >= 0 || result1.indexOf("body") >= 0,
"Should contain placeholder or text");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with placeholder at start");
// Test placeholder at end
ctx.reset();
ctx.setRegistry(&registry);
TemplateRenderer::initializeContext(ctx, placeholder_end_template);
String result2 = captureRenderedOutput(ctx);
TEST_ASSERT_TRUE_MESSAGE(result2.length() > 0, "Should handle placeholder at end");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with placeholder at end");
// Test only placeholders (no text)
registry.registerProgmemData("%A%", test_css_data);
registry.registerProgmemData("%B%", test_js_data);
registry.registerProgmemData("%C%", test_favicon_data);
ctx.reset();
TemplateRenderer::initializeContext(ctx, only_placeholders_template);
ctx.setRegistry(&registry);
String result3 = captureRenderedOutput(ctx);
TEST_ASSERT_TRUE_MESSAGE(result3.length() > 0, "Should handle only placeholders");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with only placeholders");
// Test only text (no placeholders)
ctx.reset();
TemplateRenderer::initializeContext(ctx, only_text_template);
ctx.setRegistry(&registry);
String result4 = captureRenderedOutput(ctx);
TEST_ASSERT_TRUE_MESSAGE(result4.length() > 0, "Should handle only text");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with only text");
// Test special characters
ctx.reset();
TemplateRenderer::initializeContext(ctx, special_chars_template);
ctx.setRegistry(&registry);
String result5 = captureRenderedOutput(ctx);
TEST_ASSERT_TRUE_MESSAGE(result5.length() > 0, "Should handle special characters");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with special characters");
// Test newlines and placeholders
ctx.reset();
TemplateRenderer::initializeContext(ctx, newlines_template);
ctx.setRegistry(&registry);
String result6 = captureRenderedOutput(ctx);
TEST_ASSERT_TRUE_MESSAGE(result6.length() > 0, "Should handle newlines");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with newlines");
// Test empty placeholder (%%)
ctx.reset();
TemplateRenderer::initializeContext(ctx, empty_placeholder_template);
ctx.setRegistry(&registry);
String result7 = captureRenderedOutput(ctx);
// Should handle empty placeholder gracefully
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with empty placeholder");
Serial.println("[TEST] Boundary condition tests completed successfully");
}
// Test stress conditions
void test_edge_cases_stress() {
Serial.println("[TEST] Testing stress conditions...");
PlaceholderRegistry registry(50);
// Register many placeholders
for (int i = 0; i < 20; i++) {
char name[10];
sprintf(name, "%%P%d%%", i);
registry.registerProgmemData(name, test_css_data);
}
TEST_ASSERT_EQUAL_MESSAGE(20, registry.getCount(),
"Should register many placeholders");
// Test template with many placeholders
// Note: PROGMEM templates must be defined at file scope
const char many_placeholders_template[] =
"%P0%%P1%%P2%%P3%%P4%%P5%%P6%%P7%%P8%%P9%"
"%P10%%P11%%P12%%P13%%P14%%P15%%P16%%P17%%P18%%P19%";
// All placeholders are already registered above
Serial.print("[DEBUG] Stress test - Registry count: ");
Serial.println(registry.getCount());
Serial.print("[DEBUG] Stress test - %P0% registered: ");
Serial.println(registry.getPlaceholder("%P0%") != nullptr ? "YES" : "NO");
Serial.print("[DEBUG] Stress test - %P19% registered: ");
Serial.println(registry.getPlaceholder("%P19%") != nullptr ? "YES" : "NO");
TemplateContext ctx;
ctx.setRegistry(&registry);
TemplateRenderer::initializeContext(ctx, many_placeholders_template);
Serial.print("[DEBUG] Stress test template length: ");
RenderingContext* currentCtx = ctx.getCurrentContext();
if (currentCtx && currentCtx->type == RenderingContextType::TEMPLATE) {
Serial.println(currentCtx->context.templateCtx.templateLen);
} else {
Serial.println(0);
}
String result = captureRenderedOutput(ctx);
Serial.print("[DEBUG] Stress test result length: ");
Serial.println(result.length());
Serial.print("[DEBUG] Stress test result (first 200 chars): [");
if (result.length() > 200) {
Serial.print(result.substring(0, 200));
Serial.println("...]");
} else {
Serial.print(result);
Serial.println("]");
}
Serial.print("[DEBUG] Stress test - Contains 'body': ");
Serial.println(result.indexOf("body") >= 0 ? "YES" : "NO");
Serial.print("[DEBUG] Stress test - Final state: ");
Serial.println(ctx.getStateString());
Serial.print("[DEBUG] Stress test - Is complete: ");
Serial.println(TemplateRenderer::isComplete(ctx) ? "YES" : "NO");
// All placeholders are registered with test_css_data, so result should contain "body" multiple times
TEST_ASSERT_TRUE_MESSAGE(result.length() > 0, "Should render template with many placeholders");
// Verify we got some content (all placeholders render the same CSS data)
TEST_ASSERT_TRUE_MESSAGE(result.indexOf("body") >= 0 || result.length() > 20,
"Should contain rendered placeholder data");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete with many placeholders");
// Test very long template
// Register placeholder for long_text_template (if not already registered)
if (registry.getPlaceholder("%PLACEHOLDER%") == nullptr) {
registry.registerProgmemData("%PLACEHOLDER%", test_css_data);
}
ctx.reset();
ctx.setRegistry(&registry);
TemplateRenderer::initializeContext(ctx, long_text_template);
String result2 = captureRenderedOutput(ctx);
TEST_ASSERT_TRUE_MESSAGE(result2.length() > 0, "Should render very long template");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete very long template");
// Test rendering with very small buffer repeatedly
ctx.reset();
ctx.setRegistry(&registry);
TemplateRenderer::initializeContext(ctx, long_text_template);
uint8_t tinyBuffer[1];
int iterations = 0;
while (!TemplateRenderer::isComplete(ctx) && iterations < 10000) {
TemplateRenderer::renderNextChunk(ctx, tinyBuffer, sizeof(tinyBuffer));
iterations++;
}
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete even with tiny buffer");
TEST_ASSERT_LESS_THAN_MESSAGE(10000, iterations,
"Should complete in reasonable iterations");
// Test stack depth near limit
registry.clear();
registry.registerProgmemTemplate("%L2%", deep_nest_level2);
registry.registerProgmemTemplate("%L3%", deep_nest_level3);
registry.registerProgmemTemplate("%L4%", deep_nest_level4);
registry.registerProgmemTemplate("%L5%", deep_nest_level5);
registry.registerProgmemTemplate("%L6%", deep_nest_level6);
registry.registerProgmemTemplate("%L7%", deep_nest_level7);
registry.registerProgmemTemplate("%L8%", deep_nest_level8);
registry.registerProgmemTemplate("%L9%", deep_nest_level9);
registry.registerProgmemTemplate("%L10%", deep_nest_level10);
registry.registerProgmemTemplate("%L11%", deep_nest_level11);
registry.registerProgmemTemplate("%L12%", deep_nest_level12);
registry.registerProgmemTemplate("%L13%", deep_nest_level13);
registry.registerProgmemTemplate("%L14%", deep_nest_level14);
registry.registerProgmemTemplate("%L15%", deep_nest_level15);
registry.registerProgmemTemplate("%L16%", deep_nest_level16);
ctx.reset();
ctx.setRegistry(&registry);
TemplateRenderer::initializeContext(ctx, deep_nest_level1);
String result3 = captureRenderedOutput(ctx);
TEST_ASSERT_TRUE_MESSAGE(result3.length() > 0, "Should render deep nested template");
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
"Should complete deep nested template");
TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(TemplateContext::MAX_RENDERING_DEPTH, ctx.renderingDepth,
"Rendering depth should not exceed MAX_RENDERING_DEPTH");
Serial.println("[TEST] Stress condition tests completed successfully");
}