test(cpp): tests para sql_parse, process_state_machine, file_poll_diff
This commit is contained in:
@@ -31,6 +31,14 @@ add_fn_test(test_pie_chart_math test_pie_chart_math.cpp)
|
||||
add_fn_test(test_kpi_card_math test_kpi_card_math.cpp)
|
||||
add_fn_test(test_bar_chart_math test_bar_chart_math.cpp)
|
||||
|
||||
# Issue 0045 — tests de la logica pura extraida.
|
||||
add_fn_test(test_sql_parse test_sql_parse.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/sql_parse.cpp)
|
||||
add_fn_test(test_process_state_machine test_process_state_machine.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/process_state_machine.cpp)
|
||||
add_fn_test(test_file_poll_diff test_file_poll_diff.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/file_poll_diff.cpp)
|
||||
|
||||
# --- Placeholders para primitivos UI (logica visual cubierta en 0048) ------
|
||||
add_fn_test(test_tokens test_tokens.cpp)
|
||||
add_fn_test(test_button test_button.cpp)
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// Tests for fn_ui::file_poll_diff (cpp/functions/core/file_poll_diff).
|
||||
// Pura: compara dos snapshots de FS y devuelve added/modified/removed.
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch_amalgamated.hpp"
|
||||
|
||||
#include "core/file_poll_diff.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
using fn_ui::file_poll_diff;
|
||||
using fn_ui::FileEntry;
|
||||
|
||||
namespace {
|
||||
// Helper: ordena los vectores para comparaciones estables (el orden interno
|
||||
// depende del orden de iteracion de unordered_map, no es estable).
|
||||
void sort_diff(fn_ui::FileDiff& d) {
|
||||
std::sort(d.added.begin(), d.added.end());
|
||||
std::sort(d.modified.begin(), d.modified.end());
|
||||
std::sort(d.removed.begin(), d.removed.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("file_poll_diff detects added/modified/removed") {
|
||||
std::vector<FileEntry> before = {{"a", 10, 1}, {"b", 20, 2}, {"c", 30, 3}};
|
||||
std::vector<FileEntry> after = {{"a", 10, 1}, {"b", 25, 2}, {"d", 40, 4}};
|
||||
auto d = file_poll_diff(before, after);
|
||||
sort_diff(d);
|
||||
REQUIRE(d.added == std::vector<std::string>{"d"});
|
||||
REQUIRE(d.modified == std::vector<std::string>{"b"});
|
||||
REQUIRE(d.removed == std::vector<std::string>{"c"});
|
||||
}
|
||||
|
||||
TEST_CASE("file_poll_diff: empty inputs") {
|
||||
auto d = file_poll_diff({}, {});
|
||||
REQUIRE(d.added.empty());
|
||||
REQUIRE(d.modified.empty());
|
||||
REQUIRE(d.removed.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("file_poll_diff: identical snapshots") {
|
||||
std::vector<FileEntry> snap = {{"a", 1, 100}, {"b", 2, 200}};
|
||||
auto d = file_poll_diff(snap, snap);
|
||||
REQUIRE(d.added.empty());
|
||||
REQUIRE(d.modified.empty());
|
||||
REQUIRE(d.removed.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("file_poll_diff: all added (before vacio)") {
|
||||
std::vector<FileEntry> after = {{"a", 1, 1}, {"b", 2, 2}};
|
||||
auto d = file_poll_diff({}, after);
|
||||
sort_diff(d);
|
||||
REQUIRE(d.added == std::vector<std::string>{"a", "b"});
|
||||
REQUIRE(d.modified.empty());
|
||||
REQUIRE(d.removed.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("file_poll_diff: all removed (after vacio)") {
|
||||
std::vector<FileEntry> before = {{"a", 1, 1}, {"b", 2, 2}};
|
||||
auto d = file_poll_diff(before, {});
|
||||
sort_diff(d);
|
||||
REQUIRE(d.removed == std::vector<std::string>{"a", "b"});
|
||||
REQUIRE(d.modified.empty());
|
||||
REQUIRE(d.added.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("file_poll_diff: solo size cambia -> modified") {
|
||||
std::vector<FileEntry> before = {{"a", 100, 5}};
|
||||
std::vector<FileEntry> after = {{"a", 200, 5}};
|
||||
auto d = file_poll_diff(before, after);
|
||||
REQUIRE(d.modified == std::vector<std::string>{"a"});
|
||||
REQUIRE(d.added.empty());
|
||||
REQUIRE(d.removed.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("file_poll_diff: solo mtime cambia -> modified") {
|
||||
std::vector<FileEntry> before = {{"a", 100, 5}};
|
||||
std::vector<FileEntry> after = {{"a", 100, 9}};
|
||||
auto d = file_poll_diff(before, after);
|
||||
REQUIRE(d.modified == std::vector<std::string>{"a"});
|
||||
REQUIRE(d.added.empty());
|
||||
REQUIRE(d.removed.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("file_poll_diff: combinacion compleja") {
|
||||
std::vector<FileEntry> before = {
|
||||
{"keep", 10, 1},
|
||||
{"mod_size", 20, 2},
|
||||
{"mod_mtime", 30, 3},
|
||||
{"removed", 40, 4},
|
||||
};
|
||||
std::vector<FileEntry> after = {
|
||||
{"keep", 10, 1},
|
||||
{"mod_size", 21, 2},
|
||||
{"mod_mtime", 30, 9},
|
||||
{"new", 50, 5},
|
||||
};
|
||||
auto d = file_poll_diff(before, after);
|
||||
sort_diff(d);
|
||||
REQUIRE(d.added == std::vector<std::string>{"new"});
|
||||
REQUIRE(d.modified == std::vector<std::string>{"mod_mtime", "mod_size"});
|
||||
REQUIRE(d.removed == std::vector<std::string>{"removed"});
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Tests for fn_ui::process_transition (cpp/functions/core/process_state_machine).
|
||||
// Tabla de transiciones definida en .md.
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch_amalgamated.hpp"
|
||||
|
||||
#include "core/process_state_machine.h"
|
||||
#include <cstring>
|
||||
|
||||
using fn_ui::process_transition;
|
||||
using fn_ui::process_state_name;
|
||||
using fn_ui::process_event_name;
|
||||
using fn_ui::ProcessState;
|
||||
using fn_ui::ProcessEvent;
|
||||
|
||||
TEST_CASE("process_transition: idle -> running on Spawned") {
|
||||
REQUIRE(process_transition(ProcessState::Idle, ProcessEvent::Spawned) == ProcessState::Running);
|
||||
}
|
||||
|
||||
TEST_CASE("process_transition: idle + Trigger sigue Idle (la solicitud no muta el estado)") {
|
||||
REQUIRE(process_transition(ProcessState::Idle, ProcessEvent::Trigger) == ProcessState::Idle);
|
||||
}
|
||||
|
||||
TEST_CASE("process_transition: running -> success on Finished") {
|
||||
REQUIRE(process_transition(ProcessState::Running, ProcessEvent::Finished) == ProcessState::Success);
|
||||
}
|
||||
|
||||
TEST_CASE("process_transition: running -> error on Failed/Timeout") {
|
||||
REQUIRE(process_transition(ProcessState::Running, ProcessEvent::Failed) == ProcessState::Error);
|
||||
REQUIRE(process_transition(ProcessState::Running, ProcessEvent::Timeout) == ProcessState::Error);
|
||||
}
|
||||
|
||||
TEST_CASE("process_transition: success/error -> idle on Reset") {
|
||||
REQUIRE(process_transition(ProcessState::Success, ProcessEvent::Reset) == ProcessState::Idle);
|
||||
REQUIRE(process_transition(ProcessState::Error, ProcessEvent::Reset) == ProcessState::Idle);
|
||||
}
|
||||
|
||||
TEST_CASE("process_transition: invalid events keep state") {
|
||||
// Idle no acepta Finished/Failed/Timeout.
|
||||
REQUIRE(process_transition(ProcessState::Idle, ProcessEvent::Finished) == ProcessState::Idle);
|
||||
REQUIRE(process_transition(ProcessState::Idle, ProcessEvent::Failed) == ProcessState::Idle);
|
||||
REQUIRE(process_transition(ProcessState::Idle, ProcessEvent::Timeout) == ProcessState::Idle);
|
||||
REQUIRE(process_transition(ProcessState::Idle, ProcessEvent::Reset) == ProcessState::Idle);
|
||||
|
||||
// Running ignora Trigger/Spawned/Reset.
|
||||
REQUIRE(process_transition(ProcessState::Running, ProcessEvent::Trigger) == ProcessState::Running);
|
||||
REQUIRE(process_transition(ProcessState::Running, ProcessEvent::Spawned) == ProcessState::Running);
|
||||
REQUIRE(process_transition(ProcessState::Running, ProcessEvent::Reset) == ProcessState::Running);
|
||||
|
||||
// Success ignora todo menos Reset.
|
||||
REQUIRE(process_transition(ProcessState::Success, ProcessEvent::Trigger) == ProcessState::Success);
|
||||
REQUIRE(process_transition(ProcessState::Success, ProcessEvent::Spawned) == ProcessState::Success);
|
||||
REQUIRE(process_transition(ProcessState::Success, ProcessEvent::Finished) == ProcessState::Success);
|
||||
REQUIRE(process_transition(ProcessState::Success, ProcessEvent::Failed) == ProcessState::Success);
|
||||
|
||||
// Error idem.
|
||||
REQUIRE(process_transition(ProcessState::Error, ProcessEvent::Finished) == ProcessState::Error);
|
||||
REQUIRE(process_transition(ProcessState::Error, ProcessEvent::Spawned) == ProcessState::Error);
|
||||
}
|
||||
|
||||
TEST_CASE("process_state_name and process_event_name") {
|
||||
REQUIRE(std::strcmp(process_state_name(ProcessState::Idle), "Idle") == 0);
|
||||
REQUIRE(std::strcmp(process_state_name(ProcessState::Running), "Running") == 0);
|
||||
REQUIRE(std::strcmp(process_state_name(ProcessState::Success), "Success") == 0);
|
||||
REQUIRE(std::strcmp(process_state_name(ProcessState::Error), "Error") == 0);
|
||||
|
||||
REQUIRE(std::strcmp(process_event_name(ProcessEvent::Trigger), "Trigger") == 0);
|
||||
REQUIRE(std::strcmp(process_event_name(ProcessEvent::Spawned), "Spawned") == 0);
|
||||
REQUIRE(std::strcmp(process_event_name(ProcessEvent::Finished), "Finished") == 0);
|
||||
REQUIRE(std::strcmp(process_event_name(ProcessEvent::Failed), "Failed") == 0);
|
||||
REQUIRE(std::strcmp(process_event_name(ProcessEvent::Timeout), "Timeout") == 0);
|
||||
REQUIRE(std::strcmp(process_event_name(ProcessEvent::Reset), "Reset") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("process_transition: ciclo completo idle -> running -> success -> idle") {
|
||||
ProcessState s = ProcessState::Idle;
|
||||
s = process_transition(s, ProcessEvent::Spawned);
|
||||
REQUIRE(s == ProcessState::Running);
|
||||
s = process_transition(s, ProcessEvent::Finished);
|
||||
REQUIRE(s == ProcessState::Success);
|
||||
s = process_transition(s, ProcessEvent::Reset);
|
||||
REQUIRE(s == ProcessState::Idle);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Tests for fn_ui::sql_parse / sql_classify (cpp/functions/core/sql_parse).
|
||||
// Pura: tokeniza SQL multi-statement saltando strings y comentarios, y
|
||||
// clasifica por keyword inicial.
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch_amalgamated.hpp"
|
||||
|
||||
#include "core/sql_parse.h"
|
||||
|
||||
using fn_ui::sql_parse;
|
||||
using fn_ui::sql_classify;
|
||||
using fn_ui::SqlStmtKind;
|
||||
|
||||
TEST_CASE("sql_parse classifies common statements") {
|
||||
auto stmts = sql_parse("SELECT * FROM t; INSERT INTO t VALUES (1);");
|
||||
REQUIRE(stmts.size() == 2);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Select);
|
||||
REQUIRE(stmts[1].kind == SqlStmtKind::Insert);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse handles strings and comments") {
|
||||
auto stmts = sql_parse("-- comment\nSELECT 'a;b' FROM t; /* x */ DELETE FROM t;");
|
||||
REQUIRE(stmts.size() == 2);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Select);
|
||||
REQUIRE(stmts[1].kind == SqlStmtKind::Delete);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse trims and ignores empty") {
|
||||
auto stmts = sql_parse("; ; SELECT 1;;");
|
||||
REQUIRE(stmts.size() == 1);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Select);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse: ddl and dcl keywords") {
|
||||
auto stmts = sql_parse("CREATE TABLE t(a); DROP TABLE t; ALTER TABLE t ADD b; UPDATE t SET a=1;");
|
||||
REQUIRE(stmts.size() == 4);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Create);
|
||||
REQUIRE(stmts[1].kind == SqlStmtKind::Drop);
|
||||
REQUIRE(stmts[2].kind == SqlStmtKind::Alter);
|
||||
REQUIRE(stmts[3].kind == SqlStmtKind::Update);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse: pragma and explain") {
|
||||
auto stmts = sql_parse("PRAGMA foreign_keys = ON; EXPLAIN SELECT 1;");
|
||||
REQUIRE(stmts.size() == 2);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Pragma);
|
||||
REQUIRE(stmts[1].kind == SqlStmtKind::Explain);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse: WITH clasifica como Select") {
|
||||
auto stmts = sql_parse("WITH x AS (SELECT 1) SELECT * FROM x;");
|
||||
REQUIRE(stmts.size() == 1);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Select);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse: case-insensitive y trim") {
|
||||
auto stmts = sql_parse(" select 1;\n INSERT INTO t VALUES (2);");
|
||||
REQUIRE(stmts.size() == 2);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Select);
|
||||
REQUIRE(stmts[1].kind == SqlStmtKind::Insert);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse: line tracking") {
|
||||
auto stmts = sql_parse("\n\nSELECT 1;\n-- skip\nDELETE FROM t;\n");
|
||||
REQUIRE(stmts.size() == 2);
|
||||
REQUIRE(stmts[0].line == 3);
|
||||
REQUIRE(stmts[1].line == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse: ultimo statement sin ;") {
|
||||
auto stmts = sql_parse("SELECT 1");
|
||||
REQUIRE(stmts.size() == 1);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Select);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_classify: standalone") {
|
||||
REQUIRE(sql_classify("SELECT 1") == SqlStmtKind::Select);
|
||||
REQUIRE(sql_classify("garbage") == SqlStmtKind::Unknown);
|
||||
REQUIRE(sql_classify("") == SqlStmtKind::Unknown);
|
||||
REQUIRE(sql_classify("/* c */ DELETE FROM t") == SqlStmtKind::Delete);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse: ; dentro de string no separa") {
|
||||
auto stmts = sql_parse("SELECT 'a;b;c' FROM t;");
|
||||
REQUIRE(stmts.size() == 1);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Select);
|
||||
}
|
||||
|
||||
TEST_CASE("sql_parse: backtick identifier") {
|
||||
auto stmts = sql_parse("SELECT * FROM `weird;name`;");
|
||||
REQUIRE(stmts.size() == 1);
|
||||
REQUIRE(stmts[0].kind == SqlStmtKind::Select);
|
||||
}
|
||||
Reference in New Issue
Block a user