2026-07-21 09:42:30 -05:00 | | | //+------------------------------------------------------------------+
|
| | | //| ParserTest.mqh |
|
| | | //| Copyright 2026, MetaQuotes Ltd. |
|
| | | //| https://www.mql5.com |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "Copyright 2026, MetaQuotes Ltd."
|
| | | #property link "https://www.mql5.com"
|
| | | #property strict
|
| | |
|
| | | #ifndef JSONPARSERBYLEO_WF_INC_PARSERTEST_MQH
|
| | | #define JSONPARSERBYLEO_WF_INC_PARSERTEST_MQH
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | #include "Def.mqh"
|
| | | #resource "parser_roundtrip.json" as const string g_parser_roundtrip_json
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | namespace TSN
|
| | | {
|
| | | //+------------------------------------------------------------------+
|
| | | //| Helper compartido: compara dos parsers slot a slot en m_cinta. |
|
| | | //| No es un ICiTest, es una funcion de apoyo usada por varios tests.|
|
| | | //+------------------------------------------------------------------+
|
| | | bool JPBL_CintaIguales(const TSN::CJsonParser& a, const TSN::CJsonParser& b, string& diff)
|
| | | {
|
| | | if(a.m_cinta_pos != b.m_cinta_pos)
|
| | | {
|
| | | diff = StringFormat("m_cinta_pos distinto: a=%d b=%d", a.m_cinta_pos, b.m_cinta_pos);
|
| | | return false;
|
| | | }
|
| | | for(int i = 0; i < a.m_cinta_pos; i++)
|
| | | {
|
| | | if(a.m_cinta[i] != b.m_cinta[i])
|
| | | {
|
| | | diff = StringFormat("slot %d distinto: a=%d b=%d", i, a.m_cinta[i], b.m_cinta[i]);
|
| | | return false;
|
| | | }
|
| | | }
|
| | | return true;
|
| | | }
|
| | |
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 8. SaveCompiled + AssingFile -> misma cinta exacta (comparacion |
|
| | | //| binaria completa via helper JPBL_CintaIguales) |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_JsonTcRoundTripSameCinta : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_JsonTc_RoundTrip_SameCinta"; }
|
| | | inline int Importance() const override final { return 95; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p1;
|
| | | p1.Assing(g_parser_roundtrip_json);
|
| | | p1.CorrectPadding();
|
| | | if(!p1.Parse())
|
| | | {
|
| | | msg = "Parse() original fallo";
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Guardamos comprimido (embebido: cinta + raw + tablas)
|
| | | const string cache_file = "TbpCi_TestCache_Basic.jsontc";
|
| | | if(!p1.SaveCompiled(cache_file, false))
|
| | | {
|
| | | msg = "SaveCompiled() fallo";
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Recargamos en un parser nuevo, sin reparsear
|
| | | TSN::CJsonParser p2;
|
| | | const int result = p2.AssingFile(cache_file, false);
|
| | | if(result <= 0)
|
| | | {
|
| | | msg = StringFormat("AssingFile() del .jsontc fallo, result=%d", result);
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- La cinta debe ser IDENTICA slot a slot (mismo m_raw embebido)
|
| | | string diff;
|
| | | if(!JPBL_CintaIguales(p1, p2, diff))
|
| | | {
|
| | | msg = diff;
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 9. jsontc round-trip preserva valores via API (sin reparseo) |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_JsonTcRoundTripPreservesValues : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_JsonTc_RoundTrip_PreservesValues"; }
|
| | | inline int Importance() const override final { return 95; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p1;
|
| | | p1.Assing(g_parser_roundtrip_json);
|
| | | p1.CorrectPadding();
|
| | | p1.Parse();
|
| | |
|
| | | const string cache_file = "TbpCi_TestCache_Values.jsontc";
|
| | | if(!p1.SaveCompiled(cache_file, false))
|
| | | {
|
| | | msg = "SaveCompiled() fallo";
|
| | | return false;
|
| | | }
|
| | |
|
| | | TSN::CJsonParser p2;
|
| | | if(p2.AssingFile(cache_file, false) <= 0)
|
| | | {
|
| | | msg = "AssingFile() fallo";
|
| | | return false;
|
| | | }
|
| | |
|
| | | TSN::CJsonNode root2 = p2.GetRoot();
|
| | | string symbol = root2["symbol"].ToString("__MISSING__");
|
| | | double bid = root2["bid"].ToDouble(-1.0);
|
| | | string deep = root2["meta"]["nested"]["deep"].ToString("__MISSING__");
|
| | | if(symbol != "EURUSD" || MathAbs(bid - 1.2345) > 0.0001 || deep != "ok")
|
| | | {
|
| | | msg = StringFormat("valores no preservados tras .jsontc: symbol=%s bid=%.4f deep=%s", symbol, bid, deep);
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 10. jsontc preserva las tablas de perfect-hash ya calentadas |
|
| | | //| (warm-up antes de guardar, luego el acceso debe seguir O(1) |
|
| | | //| sin recalcular, y el resultado debe ser correcto) |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_JsonTcPreservesHashTables : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_JsonTc_PreservesHashTables"; }
|
| | | inline int Importance() const override final { return 85; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p1;
|
| | | p1.Assing(g_parser_roundtrip_json);
|
| | | p1.CorrectPadding();
|
| | | p1.Parse();
|
| | |
|
| | | //--- Warm-up: forzamos que 'meta' se promueva a perfect-hash antes de guardar
|
| | | TSN::CJsonNode meta = p1.GetRoot()["meta"];
|
| | | for(int i = 0; i < 10; i++)
|
2026-07-21 10:38:19 -05:00 | | | meta["id"];
|
2026-07-21 09:42:30 -05:00 | | |
|
| | | const string cache_file = "TbpCi_TestCache_Hash.jsontc";
|
| | | if(!p1.SaveCompiled(cache_file, false))
|
| | | {
|
| | | msg = "SaveCompiled() fallo";
|
| | | return false;
|
| | | }
|
| | |
|
| | | TSN::CJsonParser p2;
|
| | | if(p2.AssingFile(cache_file, false) <= 0)
|
| | | {
|
| | | msg = "AssingFile() fallo";
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- m_tables_size debe haberse restaurado (al menos 1 tabla: 'meta')
|
| | | if(p2.m_tables_size < 1)
|
| | | {
|
| | | msg = StringFormat("esperado m_tables_size >= 1, obtenido %d", p2.m_tables_size);
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Y el acceso debe seguir siendo correcto
|
| | | long id = p2.GetRoot()["meta"]["id"].ToInt(-1);
|
| | | if(id != 7)
|
| | | {
|
| | | msg = StringFormat("esperado id=7, obtenido %d", id);
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 11. SaveCompiled con referencia externa (no embebido) + reload |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_JsonTcExternalRefRoundTrip : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_JsonTc_ExternalRef_RoundTrip"; }
|
| | | inline int Importance() const override final { return 75; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | //--- Primero escribimos el .json fuente en Files\ (relativo al terminal)
|
| | | const string json_file = "TbpCi_TestSource_ExternalRef.json";
|
| | | int fh = FileOpen(json_file, FILE_WRITE | FILE_TXT | FILE_ANSI);
|
| | | if(fh == INVALID_HANDLE)
|
| | | {
|
| | | msg = "No se pudo crear el .json fuente de prueba";
|
| | | return false;
|
| | | }
|
| | | FileWriteString(fh, g_parser_roundtrip_json);
|
| | | FileClose(fh);
|
| | |
|
| | | //--- Parseamos normalmente
|
| | | TSN::CJsonParser p1;
|
2026-07-21 10:38:19 -05:00 | | | if(p1.AssingFile(json_file, false) != 0)
|
2026-07-21 09:42:30 -05:00 | | | {
|
2026-07-21 10:38:19 -05:00 | | | msg = "AssingFile() del .json fuente fallo, valor diferente a 0";
|
2026-07-21 09:42:30 -05:00 | | | return false;
|
| | | }
|
| | | p1.CorrectPadding();
|
| | | if(!p1.Parse())
|
| | | {
|
| | | msg = "Parse() del .json fuente fallo";
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Guardamos con referencia externa (no embebe el raw, guarda la ruta)
|
| | | const string cache_file = "TbpCi_TestCache_ExternalRef.jsontc";
|
| | | if(!p1.SaveCompiled(cache_file, false, json_file, false))
|
| | | {
|
| | | msg = "SaveCompiled() con referencia externa fallo";
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Recargamos: debe volver a abrir el .json referenciado + aplicar la cinta cacheada
|
| | | TSN::CJsonParser p2;
|
2026-07-21 10:38:19 -05:00 | | | if(p2.AssingFile(cache_file, false) != 1)
|
2026-07-21 09:42:30 -05:00 | | | {
|
2026-07-21 10:38:19 -05:00 | | | msg = "AssingFile() del .jsontc con ref externa fallo, esperado valor 1";
|
2026-07-21 09:42:30 -05:00 | | | return false;
|
| | | }
|
| | |
|
| | | string symbol = p2.GetRoot()["symbol"].ToString("__MISSING__");
|
| | | if(symbol != "EURUSD")
|
| | | {
|
| | | msg = StringFormat("esperado 'EURUSD', obtenido '%s'", symbol);
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 12. jsontc round-trip preserva arrays anidados |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_JsonTcRoundTripNestedArray : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_JsonTc_RoundTrip_NestedAccess"; }
|
| | | inline int Importance() const override final { return 80; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p1;
|
| | | p1.Assing(g_parser_roundtrip_json);
|
| | | p1.CorrectPadding();
|
| | | p1.Parse();
|
| | |
|
| | | const string cache_file = "TbpCi_TestCache_Nested.jsontc";
|
| | | p1.SaveCompiled(cache_file, false);
|
| | |
|
| | | TSN::CJsonParser p2;
|
| | | p2.AssingFile(cache_file, false);
|
| | |
|
| | | TSN::CJsonNode tags2 = p2.GetRoot()["tags"];
|
2026-10-03 22:25:05 -05:00 | | | if(!tags2.ExistInArrayHomogeneo("major") || tags2.ExistInArrayHomogeneo("no_existe"))
|
2026-07-21 09:42:30 -05:00 | | | {
|
2026-10-03 22:25:05 -05:00 | | | msg = "ExistInArrayHomogeneo sobre datos recargados de .jsontc fallo";
|
2026-07-21 09:42:30 -05:00 | | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
2026-10-03 22:25:05 -05:00 | | |
|
2026-07-21 09:42:30 -05:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 14. GetStep / GetStepPure consistentes para un entero (2 slots) |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_GetStepInteger : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_GetStep_Integer_TwoSlots"; }
|
| | | inline int Importance() const override final { return 70; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p;
|
| | | p.Assing("{\"n\":42}");
|
| | | p.CorrectPadding();
|
| | | p.Parse();
|
| | |
|
| | | // root(OBJ) en 0,1 ; KEY "n" en 2 ; INTEGER en 3,4
|
2026-10-08 21:07:14 -05:00 | | | if(p.GetStep(4) != 2 || p.GetStepPure(4) != 2)
|
2026-07-21 09:42:30 -05:00 | | | {
|
2026-10-08 21:07:14 -05:00 | | | msg = StringFormat("esperado step=2 para INTEGER, obtenido GetStep=%d GetStepPure=%d", p.GetStep(4), p.GetStepPure(4));
|
2026-07-21 09:42:30 -05:00 | | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 15. Unescape (string) resuelve secuencias basicas |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_UnescapeBasic : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Unescape_BasicSequences"; }
|
| | | inline int Importance() const override final { return 80; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p;
|
| | | p.Assing("{\"k\":\"a\\nb\\tc\"}");
|
| | | p.CorrectPadding();
|
| | | p.Parse();
|
| | |
|
| | | string val = p.GetRoot()["k"].ToString("__MISSING__");
|
| | | if(StringFind(val, "\n") < 0 || StringFind(val, "\t") < 0)
|
| | | {
|
| | | msg = StringFormat("Unescape no resolvio correctamente, obtenido '%s'", val);
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 16. Parse() detecta JSON malformado (clave sin cerrar) y setea |
|
| | | //| LastErr() en consecuencia |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_MalformedKeyNotClosed : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Parse_MalformedKeyNotClosed"; }
|
| | | inline int Importance() const override final { return 85; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p;
|
| | | p.Assing("{\"unclosed_key : 1}");
|
| | | p.CorrectPadding();
|
| | |
|
| | | bool ok = p.Parse();
|
| | | if(ok)
|
| | | {
|
| | | msg = "Parse() de clave sin cerrar devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | | if(p.LastErr() == TSN_JSON_NOT_ERR)
|
| | | {
|
| | | msg = "LastErr() sigue en TSN_JSON_NOT_ERR tras fallo de parseo";
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 17. Parse() detecta JSON incompleto (sin cerrar el objeto raiz) |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_MalformedUnfinishedJson : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Parse_UnfinishedJson"; }
|
| | | inline int Importance() const override final { return 85; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p;
|
| | | p.Assing("{\"a\":1,\"b\":2");
|
| | | p.CorrectPadding();
|
| | |
|
| | | bool ok = p.Parse();
|
| | | if(ok)
|
| | | {
|
| | | msg = "Parse() de JSON sin cerrar devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 18. Parse() sobre JSON valido simple retorna true y accesible |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_ValidSimpleParseOk : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Parse_ValidSimple_Ok"; }
|
| | | inline int Importance() const override final { return 90; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p;
|
| | | p.Assing("{\"a\":1}");
|
| | | p.CorrectPadding();
|
| | | if(!p.Parse())
|
| | | {
|
| | | msg = "Parse() de JSON simple valido devolvio false";
|
| | | return false;
|
| | | }
|
| | | if(p.LastErr() != TSN_JSON_NOT_ERR)
|
| | | {
|
| | | msg = "LastErr() indica error tras un parseo exitoso";
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
2026-10-03 22:25:05 -05:00 | | | //+------------------------------------------------------------------+
|
| | | //| 19. Parse() rechaza comas colgantes en objeto y en array |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_TrailingCommaRejected : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Parse_TrailingComma_Rejected"; }
|
| | | inline int Importance() const override final { return 85; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p_obj;
|
| | | p_obj.Assing("{\"a\":1,}");
|
| | | p_obj.CorrectPadding();
|
| | | if(p_obj.Parse())
|
| | | {
|
| | | msg = "Parse() de objeto con coma colgante devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | |
|
| | | TSN::CJsonParser p_arr;
|
| | | p_arr.Assing("[1,2,]");
|
| | | p_arr.CorrectPadding();
|
| | | if(p_arr.Parse())
|
| | | {
|
| | | msg = "Parse() de array con coma colgante devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 20. Resolucion on-demand de \u: Parse() no valida el escape, |
|
| | | //| solo falla al materializar el string (ToString) si esta |
|
| | | //| incompleto; si es valido, se decodifica correctamente |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_UnescapeUnicodeOnDemand : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Unescape_Unicode_OnDemand"; }
|
| | | inline int Importance() const override final { return 80; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | //--- Secuencia valida: debe decodificar a 'A'
|
| | | TSN::CJsonParser p_ok;
|
| | | p_ok.Assing("{\"k\":\"\\u0041\"}");
|
| | | p_ok.CorrectPadding();
|
| | | if(!p_ok.Parse())
|
| | | {
|
| | | msg = "Parse() con \\u valido devolvio false (esperado true)";
|
| | | return false;
|
| | | }
|
| | | const string val_ok = p_ok.GetRoot()["k"].ToString("__MISSING__");
|
| | | if(val_ok != "A")
|
| | | {
|
| | | msg = StringFormat("secuencia \\u0041 no decodifico a 'A', obtenido '%s'", val_ok);
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Secuencia incompleta: Parse() debe seguir devolviendo true (no valida en parseo)...
|
| | | TSN::CJsonParser p_bad;
|
| | | p_bad.Assing("{\"k\":\"\\u12\"}");
|
| | | p_bad.CorrectPadding();
|
| | | if(!p_bad.Parse())
|
| | | {
|
| | | msg = "Parse() con \\u incompleto devolvio false (esperado true, la validacion es on-demand)";
|
| | | return false;
|
| | | }
|
| | | //--- ...pero al materializar via ToString() debe fallar y caer al valor por defecto
|
| | | const string val_bad = p_bad.GetRoot()["k"].ToString("__MISSING__");
|
| | | if(val_bad != "__MISSING__")
|
| | | {
|
| | | msg = StringFormat("ToString() con \\u incompleto no cayo al default, obtenido '%s'", val_bad);
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 21. Claves duplicadas: el lookup lineal se queda con la primera |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_DuplicateKeysFirstWins : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_DuplicateKeys_FirstWins"; }
|
| | | inline int Importance() const override final { return 70; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p;
|
| | | p.Assing("{\"a\":1,\"a\":2}");
|
| | | p.CorrectPadding();
|
| | | if(!p.Parse())
|
| | | {
|
| | | msg = "Parse() de objeto con claves duplicadas devolvio false (esperado true, es JSON sintacticamente valido)";
|
| | | return false;
|
| | | }
|
| | |
|
| | | const long val = p.GetRoot()["a"].ToInt(-1);
|
| | | if(val != 1)
|
| | | {
|
| | | msg = StringFormat("esperado que gane la primera clave 'a'=1, obtenido %d", val);
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 22. Parse() rechaza cierre de bracket que no corresponde al tipo |
|
| | | //| de apertura (objeto cerrado con ']') |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_BracketMismatchRejected : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Parse_BracketMismatch_Rejected"; }
|
| | | inline int Importance() const override final { return 85; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p;
|
| | | p.Assing("{\"a\":1]");
|
| | | p.CorrectPadding();
|
| | |
|
| | | if(p.Parse())
|
| | | {
|
| | | msg = "Parse() de objeto cerrado con ']' devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | | if(p.LastErr() == TSN_JSON_NOT_ERR)
|
| | | {
|
| | | msg = "LastErr() sigue en TSN_JSON_NOT_ERR tras bracket mismatch";
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 23. Leading zeros: "0" solo es valido, pero "01"/"-01" setean |
|
| | | //| TSN_JSON_ERR_LEADING_ZEROS (el check solo dispara si hay un |
|
| | | //| segundo digito tras el primer '0') |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_LeadingZerosRejected : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Parse_LeadingZeros_Rejected"; }
|
| | | inline int Importance() const override final { return 85; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | //--- Sanity: "0" solo es valido
|
| | | TSN::CJsonParser p_zero;
|
| | | p_zero.Assing("{\"a\":0}");
|
| | | p_zero.CorrectPadding();
|
| | | if(!p_zero.Parse())
|
| | | {
|
| | | msg = "Parse() de '0' solo devolvio false (esperado true)";
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- "01" invalido
|
| | | TSN::CJsonParser p_pos;
|
| | | p_pos.Assing("{\"a\":01}");
|
| | | p_pos.CorrectPadding();
|
| | | if(p_pos.Parse())
|
| | | {
|
| | | msg = "Parse() de '01' devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | | if(p_pos.LastErr() != TSN_JSON_ERR_LEADING_ZEROS)
|
| | | {
|
| | | msg = StringFormat("esperado TSN_JSON_ERR_LEADING_ZEROS para '01', obtenido %d", p_pos.LastErr());
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- "-01" invalido (rama negativa)
|
| | | TSN::CJsonParser p_neg;
|
| | | p_neg.Assing("{\"a\":-01}");
|
| | | p_neg.CorrectPadding();
|
| | | if(p_neg.Parse())
|
| | | {
|
| | | msg = "Parse() de '-01' devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | | if(p_neg.LastErr() != TSN_JSON_ERR_LEADING_ZEROS)
|
| | | {
|
| | | msg = StringFormat("esperado TSN_JSON_ERR_LEADING_ZEROS para '-01', obtenido %d", p_neg.LastErr());
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 24. Decimal y exponente malformados: "1.x" y "1ex" deben fallar |
|
| | | //| con sus codigos de error especificos |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_MalformedDecimalAndExponent : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Parse_MalformedDecimalAndExponent_Rejected"; }
|
| | | inline int Importance() const override final { return 85; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | //--- "1.x" -> punto decimal sin digito valido despues
|
| | | TSN::CJsonParser p_dec;
|
| | | p_dec.Assing("{\"a\":1.x}");
|
| | | p_dec.CorrectPadding();
|
| | | if(p_dec.Parse())
|
| | | {
|
| | | msg = "Parse() de '1.x' devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | | if(p_dec.LastErr() != TSN_JSON_ERR_EXPECTED_NUMBER_AFECTER_DECIMALP)
|
| | | {
|
| | | msg = StringFormat("esperado TSN_JSON_ERR_EXPECTED_NUMBER_AFECTER_DECIMALP para '1.x', obtenido %d", p_dec.LastErr());
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- "1ex" -> exponente sin digito valido despues
|
| | | TSN::CJsonParser p_exp;
|
| | | p_exp.Assing("{\"a\":1ex}");
|
| | | p_exp.CorrectPadding();
|
| | | if(p_exp.Parse())
|
| | | {
|
| | | msg = "Parse() de '1ex' devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | | if(p_exp.LastErr() != TSN_JSON_ERR_EXPECTED_NUMBER_AFECTER_EXP)
|
| | | {
|
| | | msg = StringFormat("esperado TSN_JSON_ERR_EXPECTED_NUMBER_AFECTER_EXP para '1ex', obtenido %d", p_exp.LastErr());
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 25. true/false/null malformados: Parse() falla, pero LastErr() |
|
| | | //| NO cambia (el return false de estas ramas no setea |
|
| | | //| m_last_err; queda documentado como quirk conocido) |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTestParser_MalformedLiteralsRejected : public ICiTest
|
| | | {
|
| | | public:
|
| | | inline string Name() const override final { return "Parser_Parse_MalformedLiterals_Rejected"; }
|
| | | inline int Importance() const override final { return 80; }
|
| | | bool Run(string &msg) override final
|
| | | {
|
| | | TSN::CJsonParser p_true;
|
| | | p_true.Assing("{\"a\":trux}");
|
| | | p_true.CorrectPadding();
|
| | | if(p_true.Parse())
|
| | | {
|
| | | msg = "Parse() de 'trux' devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | |
|
| | | TSN::CJsonParser p_false;
|
| | | p_false.Assing("{\"a\":falze}");
|
| | | p_false.CorrectPadding();
|
| | | if(p_false.Parse())
|
| | | {
|
| | | msg = "Parse() de 'falze' devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | |
|
| | | TSN::CJsonParser p_null;
|
| | | p_null.Assing("{\"a\":nulx}");
|
| | | p_null.CorrectPadding();
|
| | | if(p_null.Parse())
|
| | | {
|
| | | msg = "Parse() de 'nulx' devolvio true (esperado false)";
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Quirk documentado: estas ramas no setean m_last_err al fallar
|
| | | if(p_true.LastErr() != TSN_JSON_NOT_ERR)
|
| | | {
|
| | | msg = StringFormat("quirk roto: se esperaba LastErr()==TSN_JSON_NOT_ERR tras 'trux' malformado, obtenido %d", p_true.LastErr());
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
| | | };
|
| | |
|
2026-07-21 09:42:30 -05:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | #endif // JSONPARSERBYLEO_WF_INC_PARSERTEST_MQH
|