mirror of
https://github.com/alexhopeoconnor/DFTE.git
synced 2026-10-04 04:28:10 +10:00
Add regression coverage and docs updates so these edge cases stay stable across board test runs.
318 lines
14 KiB
C++
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(®istry);
|
|
|
|
// 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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
|
|
// 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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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(®istry);
|
|
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");
|
|
}
|
|
|