#include "UserScript/parser.h" #include #include #include #include #include #include #include TEST_CASE("Can parse") { std::string code = "/salad (/potato) 12 13 \"hello\" ident\n" "/salad 12 13 \"hello\" ident\n" "if(/test)\n" " /nice\n" "endif"; std::vector errors; auto lexed = scripting::ast::lex(code, errors); auto parsed = scripting::ast::parse(lexed, errors); if(not errors.empty()) { for(auto& line : errors) { std::cout << line.message << "\n at line " << line.location->line_number << ":" << line.location->column_number << "\n"; std::cout << " " << *line.location->line_contents << "\n"; std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n"; } } auto& block = parsed; REQUIRE(block.contents.size() == 3); REQUIRE(std::holds_alternative>(block.contents.front().contents)); auto& cmd1 = std::get>(block.contents.front().contents); REQUIRE(cmd1->name.value == "salad"); REQUIRE(std::holds_alternative>(std::span(block.contents).subspan(1).front().contents)); auto& cmd2 = std::get>(std::span(block.contents).subspan(1).front().contents); REQUIRE(cmd2->name.value == "salad"); REQUIRE(std::holds_alternative>(std::span(block.contents).subspan(2).front().contents)); auto& conditional = std::get>(std::span(block.contents).subspan(2).front().contents); REQUIRE(std::holds_alternative>(conditional->condition->contents)); auto& paren = std::get>(conditional->condition->contents)->content; REQUIRE(std::holds_alternative>(paren)); auto& condition = std::get>(paren); REQUIRE(condition->name.value == "test"); } TEST_CASE("Can parse 2") { std::string code = "/salad (/potato) 12+13*16/(/potato)+myvar \"hello\" ident\n" "/salad 12 13 \"hello\" ident\n" "if !(/test)\n" " /nice\n" "endif"; std::vector errors; auto lexed = scripting::ast::lex(code, errors); auto parsed = scripting::ast::parse(lexed, errors); if(not errors.empty()) { for(auto& line : errors) { std::cout << line.message << "\n at line " << line.location->line_number << ":" << line.location->column_number << "\n"; std::cout << " " << *line.location->line_contents << "\n"; std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n"; } } auto& block = parsed; REQUIRE(block.contents.size() == 3); REQUIRE(std::holds_alternative>(block.contents.front().contents)); auto& cmd1 = std::get>(block.contents.front().contents); REQUIRE(cmd1->name.value == "salad"); REQUIRE(cmd1->arguments.size() == 4); REQUIRE(std::holds_alternative>(std::span(block.contents).subspan(1).front().contents)); auto& cmd2 = std::get>(std::span(block.contents).subspan(1).front().contents); REQUIRE(cmd2->name.value == "salad"); REQUIRE(std::holds_alternative>(std::span(block.contents).subspan(2).front().contents)); auto& conditional = std::get>(std::span(block.contents).subspan(2).front().contents); REQUIRE(std::holds_alternative>(conditional->condition->contents)); } template constexpr auto runner = [](){ std::vector sources = { "../tests/scripts/001.script", "../tests/scripts/002.script", "../tests/scripts/003.script", "../tests/scripts/004.script", "../tests/scripts/005.script", "../tests/scripts/006.script", "../tests/scripts/007.script", "../tests/scripts/008.script", }; auto seed = seed_template == -1 ? std::random_device{}() : seed_template; std::cout << "TEST \"Try to crash the parser 1\" with seed " << seed << std::endl; std::mt19937_64 rand(seed); auto mod = [&](std::string tmp) -> std::string { if(tmp.empty()) return tmp; auto alter_idx = rand()%tmp.size(); switch(rand()%3) { case 0:{ tmp.erase(alter_idx); }break; case 1:{ tmp[alter_idx] = rand() % 256; }break; case 2:{ tmp.insert(alter_idx, 1, char(rand() % 256)); }break; } return tmp; }; auto codes = sources | std::ranges::views::transform([](std::string file){ std::ifstream file_str{file}; std::stringstream read; read << file_str.rdbuf(); return read.str(); }); std::vector vec; std::copy(codes.begin(), codes.end(), std::back_inserter(vec)); size_t count = 0; size_t error_cnt = 0; size_t success_cnt = 0; constexpr size_t max_count = 8000000; auto begin = std::chrono::high_resolution_clock::now(); while(count < max_count) { std::cout << 100.0*double(count)/max_count <<"%"<< std::endl; for(auto& code : vec) { std::vector errors; auto lexed = scripting::ast::lex(code, errors); auto parsed = scripting::ast::parse(lexed, errors); if(errors.empty()) success_cnt++; else error_cnt++; count++; } auto limit = std::min(vec.size(), 5000) ; for(size_t idx = 0; idx < limit; ++idx) { vec.push_back(mod(vec[idx])); } std::transform(vec.begin(), vec.end(), vec.begin(), mod); std::shuffle(vec.begin(), vec.end(), rand); if(vec.size()>30000) vec.resize(30000); } auto end = std::chrono::high_resolution_clock::now(); std::cout << "Successes: " << success_cnt << "\n" << "Failures: " << error_cnt << "\n" << "Ratio: " << double(success_cnt)/double(success_cnt+error_cnt) << "\n" << "Total time: " << std::chrono::duration_cast(end-begin).count() << "µs\n" << "Time per iteration: " << (std::chrono::duration_cast(end-begin)/(error_cnt+success_cnt)).count() << "ns\n"; }; TEST_CASE("Try to crash the parser (known seeds)") { runner<4138740281>(); runner<1547293717>(); runner<1759257947>(); runner<2909912711>(); runner<1236548620>(); } TEST_CASE("Try to crash the parser (new seeds)") { runner<>(); }