141 lines
4.7 KiB
C++
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);
|
|
}
|