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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 1. ToJsonAsm produce un dump no vacio de un JSON valido |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CTestParser_ToJsonAsmNotEmpty : public ICiTest
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
inline string Name() const override final { return "Parser_ToJsonAsm_NotEmpty"; }
|
|
|
|
|
inline int Importance() const override final { return 85; }
|
|
|
|
|
bool Run(string &msg) override final
|
|
|
|
|
{
|
|
|
|
|
TSN::CJsonParser p;
|
|
|
|
|
p.Assing(g_parser_roundtrip_json);
|
|
|
|
|
p.CorrectPadding();
|
|
|
|
|
if(!p.Parse())
|
|
|
|
|
{
|
|
|
|
|
msg = "Parse() del fixture fallo";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
uchar buf[];
|
|
|
|
|
int written = p.ToJsonAsm(buf);
|
|
|
|
|
if(written <= 0)
|
|
|
|
|
{
|
|
|
|
|
msg = StringFormat("ToJsonAsm devolvio %d bytes", written);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 2. Round-trip completo: Parse -> ToJsonAsm -> ParseAssembly -> |
|
|
|
|
|
//| misma cinta que el parseo original |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CTestParser_JsonAsmRoundTripSameCinta : public ICiTest
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
inline string Name() const override final { return "Parser_JsonAsm_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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Dump a jsonasm
|
|
|
|
|
uchar buf[];
|
|
|
|
|
p1.ToJsonAsm(buf);
|
|
|
|
|
string asm_text = CharArrayToString(buf, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
|
|
|
|
|
|
|
//--- Reparseamos el dump con un parser nuevo
|
|
|
|
|
TSN::CJsonParser p2;
|
|
|
|
|
p2.Assing(asm_text);
|
|
|
|
|
p2.CorrectPadding();
|
|
|
|
|
if(!p2.ParseAssembly())
|
|
|
|
|
{
|
|
|
|
|
msg = "ParseAssembly() del dump fallo";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- La cinta debe coincidir en tipos y estructura
|
|
|
|
|
TSN::CJsonNode r1 = p1.GetRoot();
|
|
|
|
|
TSN::CJsonNode r2 = p2.GetRoot();
|
|
|
|
|
|
|
|
|
|
if(r1.GetType() != r2.GetType())
|
|
|
|
|
{
|
|
|
|
|
msg = "Tipo de root distinto tras round-trip jsonasm";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 3. Round-trip jsonasm preserva valores accesibles via API |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CTestParser_JsonAsmRoundTripPreservesValues : public ICiTest
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
inline string Name() const override final { return "Parser_JsonAsm_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();
|
|
|
|
|
|
|
|
|
|
//---
|
|
|
|
|
uchar buf[];
|
|
|
|
|
p1.ToJsonAsm(buf);
|
|
|
|
|
string asm_text = CharArrayToString(buf, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
|
|
|
|
|
|
|
//---
|
|
|
|
|
TSN::CJsonParser p2;
|
|
|
|
|
p2.Assing(asm_text);
|
|
|
|
|
p2.CorrectPadding();
|
|
|
|
|
if(!p2.ParseAssembly())
|
|
|
|
|
{
|
|
|
|
|
msg = "ParseAssembly() fallo";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//---
|
|
|
|
|
TSN::CJsonNode root2 = p2.GetRoot();
|
|
|
|
|
string symbol = root2["symbol"].ToString("__MISSING__");
|
|
|
|
|
double bid = root2["bid"].ToDouble(-1.0);
|
|
|
|
|
bool active = root2["active"].ToBool(false);
|
|
|
|
|
long neg = root2["neg"].ToInt(0);
|
|
|
|
|
string deep = root2["meta"]["nested"]["deep"].ToString("__MISSING__");
|
|
|
|
|
|
|
|
|
|
//---
|
|
|
|
|
if(symbol != "EURUSD" || MathAbs(bid - 1.2345) > 0.0001 || !active || neg != -42 || deep != "ok")
|
|
|
|
|
{
|
|
|
|
|
msg = StringFormat("valores no preservados: symbol=%s bid=%.4f active=%s neg=%d deep=%s",
|
|
|
|
|
symbol, bid, active ? "true" : "false", neg, deep);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 4. Round-trip jsonasm preserva arrays y objetos vacios |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CTestParser_JsonAsmRoundTripEmptyContainers : public ICiTest
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
inline string Name() const override final { return "Parser_JsonAsm_RoundTrip_EmptyContainers"; }
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
uchar buf[];
|
|
|
|
|
p1.ToJsonAsm(buf);
|
|
|
|
|
string asm_text = CharArrayToString(buf, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
|
|
|
|
|
|
|
TSN::CJsonParser p2;
|
|
|
|
|
p2.Assing(asm_text);
|
|
|
|
|
p2.CorrectPadding();
|
|
|
|
|
p2.ParseAssembly();
|
|
|
|
|
|
|
|
|
|
TSN::CJsonNode root2 = p2.GetRoot();
|
|
|
|
|
string keys[];
|
|
|
|
|
int c1 = root2["empty_obj"].GetKeys(keys);
|
|
|
|
|
|
|
|
|
|
TSN::CJsonNode arr = root2["empty_arr"];
|
|
|
|
|
bool arr_in_range = arr.InRangeSafe(0);
|
|
|
|
|
|
|
|
|
|
if(c1 != 0 || arr_in_range)
|
|
|
|
|
{
|
|
|
|
|
msg = StringFormat("empty_obj keys=%d, empty_arr InRangeSafe(0)=%s", c1, arr_in_range ? "true" : "false");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 5. Round-trip jsonasm preserva arrays con multiples elementos |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CTestParser_JsonAsmRoundTripArray : public ICiTest
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
inline string Name() const override final { return "Parser_JsonAsm_RoundTrip_Array"; }
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
uchar buf[];
|
|
|
|
|
p1.ToJsonAsm(buf);
|
|
|
|
|
string asm_text = CharArrayToString(buf, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
|
|
|
|
|
|
|
TSN::CJsonParser p2;
|
|
|
|
|
p2.Assing(asm_text);
|
|
|
|
|
p2.CorrectPadding();
|
|
|
|
|
p2.ParseAssembly();
|
|
|
|
|
|
|
|
|
|
TSN::CJsonNode tags = p2.GetRoot()["tags"];
|
|
|
|
|
string first = tags.At(0).ToString("__MISSING__");
|
|
|
|
|
string second = tags.At(1).ToString("__MISSING__");
|
|
|
|
|
|
|
|
|
|
if(first != "fx" || second != "major")
|
|
|
|
|
{
|
|
|
|
|
msg = StringFormat("esperado 'fx'/'major', obtenido '%s'/'%s'", first, second);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 6. Round-trip jsonasm con doble vuelta (asm de un asm-reparseado)|
|
|
|
|
|
//| debe seguir siendo estable (idempotencia del formato) |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CTestParser_JsonAsmDoubleRoundTripStable : public ICiTest
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
inline string Name() const override final { return "Parser_JsonAsm_DoubleRoundTrip_Stable"; }
|
|
|
|
|
inline int Importance() const override final { return 75; }
|
|
|
|
|
bool Run(string &msg) override final
|
|
|
|
|
{
|
|
|
|
|
TSN::CJsonParser p1;
|
|
|
|
|
p1.Assing(g_parser_roundtrip_json);
|
|
|
|
|
p1.CorrectPadding();
|
|
|
|
|
p1.Parse();
|
|
|
|
|
|
|
|
|
|
//--- primera vuelta
|
|
|
|
|
uchar buf1[];
|
|
|
|
|
p1.ToJsonAsm(buf1);
|
|
|
|
|
string asm1 = CharArrayToString(buf1, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
|
|
|
|
|
|
|
TSN::CJsonParser p2;
|
|
|
|
|
p2.Assing(asm1);
|
|
|
|
|
p2.CorrectPadding();
|
|
|
|
|
p2.ParseAssembly();
|
|
|
|
|
|
|
|
|
|
//--- segunda vuelta: dump de p2 y reparseo en p3
|
|
|
|
|
uchar buf2[];
|
|
|
|
|
p2.ToJsonAsm(buf2);
|
|
|
|
|
string asm2 = CharArrayToString(buf2, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
|
|
|
|
|
|
|
TSN::CJsonParser p3;
|
|
|
|
|
p3.Assing(asm2);
|
|
|
|
|
p3.CorrectPadding();
|
|
|
|
|
if(!p3.ParseAssembly())
|
|
|
|
|
{
|
|
|
|
|
msg = "ParseAssembly() de la segunda vuelta fallo";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string symbol = p3.GetRoot()["symbol"].ToString("__MISSING__");
|
|
|
|
|
if(symbol != "EURUSD")
|
|
|
|
|
{
|
|
|
|
|
msg = StringFormat("esperado 'EURUSD' tras doble round-trip, obtenido '%s'", symbol);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 7. ParseAssembly detecta null correctamente tras el dump |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CTestParser_JsonAsmRoundTripNull : public ICiTest
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
inline string Name() const override final { return "Parser_JsonAsm_RoundTrip_Null"; }
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
uchar buf[];
|
|
|
|
|
p1.ToJsonAsm(buf);
|
|
|
|
|
string asm_text = CharArrayToString(buf, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
|
|
|
|
|
|
|
TSN::CJsonParser p2;
|
|
|
|
|
p2.Assing(asm_text);
|
|
|
|
|
p2.CorrectPadding();
|
|
|
|
|
p2.ParseAssembly();
|
|
|
|
|
|
|
|
|
|
if(!p2.GetRoot()["nothing"].IsNull())
|
|
|
|
|
{
|
|
|
|
|
msg = "IsNull() sobre 'nothing' tras round-trip devolvio false";
|
|
|
|
|
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"];
|
|
|
|
|
if(!tags2.ExistInArrayStr("major") || tags2.ExistInArrayStr("no_existe"))
|
|
|
|
|
{
|
|
|
|
|
msg = "ExistInArrayStr sobre datos recargados de .jsontc fallo";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 13. Comparativa cruzada: jsonasm vs jsontc deben producir el |
|
|
|
|
|
//| mismo resultado semantico partiendo del mismo JSON original |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CTestParser_CrossFormatConsistency : public ICiTest
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
inline string Name() const override final { return "Parser_CrossFormat_JsonAsmVsJsonTc_Consistency"; }
|
|
|
|
|
inline int Importance() const override final { return 90; }
|
|
|
|
|
bool Run(string &msg) override final
|
|
|
|
|
{
|
|
|
|
|
//--- Parser base
|
|
|
|
|
TSN::CJsonParser base;
|
|
|
|
|
base.Assing(g_parser_roundtrip_json);
|
|
|
|
|
base.CorrectPadding();
|
|
|
|
|
base.Parse();
|
|
|
|
|
|
|
|
|
|
//--- Via jsonasm
|
|
|
|
|
uchar buf[];
|
|
|
|
|
base.ToJsonAsm(buf);
|
|
|
|
|
string asm_text = CharArrayToString(buf, 0, WHOLE_ARRAY, CP_UTF8);
|
|
|
|
|
TSN::CJsonParser via_asm;
|
|
|
|
|
via_asm.Assing(asm_text);
|
|
|
|
|
via_asm.CorrectPadding();
|
|
|
|
|
via_asm.ParseAssembly();
|
|
|
|
|
|
|
|
|
|
//--- Via jsontc
|
|
|
|
|
const string cache_file = "TbpCi_TestCache_CrossFormat.jsontc";
|
|
|
|
|
base.SaveCompiled(cache_file, false);
|
|
|
|
|
TSN::CJsonParser via_tc;
|
|
|
|
|
via_tc.AssingFile(cache_file, false);
|
|
|
|
|
|
|
|
|
|
//--- Ambos deben devolver el mismo valor para varias claves
|
|
|
|
|
string s_asm = via_asm.GetRoot()["symbol"].ToString("__A__");
|
|
|
|
|
string s_tc = via_tc.GetRoot()["symbol"].ToString("__B__");
|
|
|
|
|
double b_asm = via_asm.GetRoot()["bid"].ToDouble(-1.0);
|
|
|
|
|
double b_tc = via_tc.GetRoot()["bid"].ToDouble(-2.0);
|
|
|
|
|
|
|
|
|
|
if(s_asm != s_tc || MathAbs(b_asm - b_tc) > 0.00001)
|
|
|
|
|
{
|
|
|
|
|
msg = StringFormat("inconsistencia entre formatos: symbol asm='%s' tc='%s', bid asm=%.5f tc=%.5f",
|
|
|
|
|
s_asm, s_tc, b_asm, b_tc);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 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
|
|
|
|
|
if(p.GetStep(3) != 2 || p.GetStepPure(3) != 2)
|
|
|
|
|
{
|
|
|
|
|
msg = StringFormat("esperado step=2 para INTEGER, obtenido GetStep=%d GetStepPure=%d", p.GetStep(3), p.GetStepPure(3));
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#endif // JSONPARSERBYLEO_WF_INC_PARSERTEST_MQH
|