sots-engine/tests/mars_parse/test_effect.cpp

141 lines
4.7 KiB
C++

// Unit tests for the .effect reader. Every sample is hand-written.
#include <string>
#include "canon.h"
#include "mars/parse/effect.h"
#include "test_main.h"
using namespace mars::parse;
namespace {
const char* kSample =
"TXT\n"
"EFFECTLENGTH 3.000000\n"
"EFFECTLOOPS -1\n"
"NAME \"New Emitter\"\n"
"RANGEUP\n"
"\tBEGIN\n"
"\t\tNUMKEYS 1\n"
"\t\tTYPE 1\n"
"\tEND\n"
"PARTICLEDATATYPE 0\n"
"MODIFIER\n"
"\tBEGIN\n"
"\t\tCREATION\n"
"\t\t\tBEGIN\n"
"\t\t\t\tVALUE 0.500000\n"
"\t\t\tEND\n"
"\tEND\n"
"PARTICLEDATATYPE 1\n"
"MODIFIER\n"
"\tBEGIN\n"
"\t\tVISIBLE FALSE\n"
"\tEND\n";
EffectFile must_parse(std::string_view text) {
auto r = parse_effect(text);
if (!r.ok()) {
std::printf(" unexpected parse error line %d: %s\n", r.error().line, r.error().message.c_str());
CHECK(r.ok());
return EffectFile{};
}
return std::move(r).value();
}
} // namespace
TEST(effect_basic_shape_and_order) {
EffectFile f = must_parse(kSample);
const EffectGroup& root = f.root;
CHECK_EQ(root.entries.size(), std::size_t{8});
CHECK_EQ(root.entries[0].key, "EFFECTLENGTH");
CHECK_EQ(std::string(root.entries[0].value()), "3.000000");
CHECK_EQ(std::string(root.entries[1].value()), "-1");
CHECK_EQ(std::string(root.entries[2].value()), "New Emitter");
CHECK(root.entries[2].values[0].quoted);
CHECK(root.entries[3].is_group());
CHECK_EQ(root.entries[3].key, "RANGEUP");
CHECK_EQ(root.entries[3].group->entries.size(), std::size_t{2});
// order: PARTICLEDATATYPE 0, MODIFIER{...}, PARTICLEDATATYPE 1, MODIFIER{...}
CHECK_EQ(root.entries[4].key, "PARTICLEDATATYPE");
CHECK_EQ(root.entries[5].key, "MODIFIER");
CHECK_EQ(root.entries[6].key, "PARTICLEDATATYPE");
CHECK_EQ(std::string(root.entries[6].value()), "1");
CHECK_EQ(root.entries[7].key, "MODIFIER");
// nested group inside a group
const EffectEntry* creation = root.entries[5].group->first("creation");
CHECK(creation != nullptr && creation->is_group());
CHECK_EQ(std::string(creation->group->first("VALUE")->value()), "0.500000");
CHECK_EQ(root.all("modifier").size(), std::size_t{2});
CHECK_EQ(root.entries[3].line, 5);
CHECK_EQ(root.entries[4].line, 10);
}
TEST(effect_canonical_form) {
EffectFile f = must_parse("TXT\nA 1\nB \"x y\"\nG\n BEGIN\n C TRUE\n END\nA 2\n");
CHECK_EQ(canon::effect_json(f.root),
R"([["A",1],["B","x y"],["G",[["C",true]]],["A",2]])");
}
TEST(effect_crlf) {
EffectFile f = must_parse("TXT\r\nA 1\r\nG\r\n\tBEGIN\r\n\t\tB 2\r\n\tEND\r\n");
CHECK_EQ(f.root.entries.size(), std::size_t{2});
CHECK_EQ(std::string(f.root.entries[0].value()), "1");
CHECK_EQ(std::string(f.root.entries[1].group->entries[0].value()), "2");
}
TEST(effect_magic_required) {
CHECK(!parse_effect("").ok());
CHECK(!parse_effect("A 1\n").ok());
CHECK(!parse_effect("TXTX\n").ok());
CHECK(parse_effect("TXT").ok());
CHECK(parse_effect(" TXT \r\n").ok()); // whitespace around the magic is fine
CHECK(parse_effect("TXT\n").ok());
CHECK_EQ(parse_effect("nope").error().line, 1);
}
TEST(effect_structural_errors) {
auto r1 = parse_effect("TXT\nBEGIN\nEND\n");
CHECK(!r1.ok());
CHECK_EQ(r1.error().line, 2);
auto r2 = parse_effect("TXT\nA 1\nEND\n");
CHECK(!r2.ok());
CHECK_EQ(r2.error().line, 3);
auto r3 = parse_effect("TXT\nG\nA 1\n"); // key not followed by BEGIN
CHECK(!r3.ok());
CHECK_EQ(r3.error().line, 3);
auto r4 = parse_effect("TXT\nG\n BEGIN\n A 1\n"); // unclosed
CHECK(!r4.ok());
auto r5 = parse_effect("TXT\nA 1\nG\n"); // trailing key
CHECK(!r5.ok());
CHECK_EQ(r5.error().line, 3);
}
TEST(effect_comments_and_blank_lines) {
EffectFile f = must_parse("TXT\n// c\nA 1 // trailing\n\n \nB \"a//b\"\n");
CHECK_EQ(f.root.entries.size(), std::size_t{2});
CHECK_EQ(std::string(f.root.entries[0].value()), "1");
CHECK_EQ(std::string(f.root.entries[1].value()), "a//b");
}
TEST(effect_multi_value_line) {
EffectFile f = must_parse("TXT\nCOLOR 1 0.5 \"z\"\n");
CHECK_EQ(f.root.entries[0].values.size(), std::size_t{3});
CHECK_EQ(f.root.entries[0].values[1].text, "0.5");
CHECK(f.root.entries[0].values[2].quoted);
CHECK_EQ(canon::effect_json(f.root), "[[\"COLOR\",[1,\"\\u00010.5\",\"z\"]]]");
}
TEST(effect_unterminated_quote_is_a_bare_token) {
EffectFile f = must_parse("TXT\nA \"open\n");
CHECK_EQ(f.root.entries[0].values[0].text, "\"open");
CHECK(!f.root.entries[0].values[0].quoted);
}
TEST(effect_lookup_misses) {
EffectFile f = must_parse("TXT\nA 1\n");
CHECK(f.root.first("B") == nullptr);
CHECK(f.root.all("B").empty());
CHECK(f.root.first("a") != nullptr);
}