|
#include "UserScript/parser.h"
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <ranges>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <random>
|
|
#include <algorithm>
|
|
|
|
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<scripting::script_error> 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<std::unique_ptr<scripting::ast::command_expression>>(block.contents.front().contents));
|
|
auto& cmd1 = std::get<std::unique_ptr<scripting::ast::command_expression>>(block.contents.front().contents);
|
|
REQUIRE(cmd1->name.value == "salad");
|
|
REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::command_expression>>(std::span(block.contents).subspan(1).front().contents));
|
|
auto& cmd2 = std::get<std::unique_ptr<scripting::ast::command_expression>>(std::span(block.contents).subspan(1).front().contents);
|
|
REQUIRE(cmd2->name.value == "salad");
|
|
REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::conditional>>(std::span(block.contents).subspan(2).front().contents));
|
|
auto& conditional = std::get<std::unique_ptr<scripting::ast::conditional>>(std::span(block.contents).subspan(2).front().contents);
|
|
REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::paren_expression>>(conditional->condition->contents));
|
|
auto& paren = std::get<std::unique_ptr<scripting::ast::paren_expression>>(conditional->condition->contents)->content;
|
|
REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::command_expression>>(paren));
|
|
auto& condition = std::get<std::unique_ptr<scripting::ast::command_expression>>(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<scripting::script_error> 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<std::unique_ptr<scripting::ast::command_expression>>(block.contents.front().contents));
|
|
auto& cmd1 = std::get<std::unique_ptr<scripting::ast::command_expression>>(block.contents.front().contents);
|
|
REQUIRE(cmd1->name.value == "salad");
|
|
REQUIRE(cmd1->arguments.size() == 4);
|
|
REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::command_expression>>(std::span(block.contents).subspan(1).front().contents));
|
|
auto& cmd2 = std::get<std::unique_ptr<scripting::ast::command_expression>>(std::span(block.contents).subspan(1).front().contents);
|
|
REQUIRE(cmd2->name.value == "salad");
|
|
REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::conditional>>(std::span(block.contents).subspan(2).front().contents));
|
|
auto& conditional = std::get<std::unique_ptr<scripting::ast::conditional>>(std::span(block.contents).subspan(2).front().contents);
|
|
REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::unary_algebraic_expression>>(conditional->condition->contents));
|
|
}
|
|
|
|
template<auto seed_template = -1>
|
|
constexpr auto runner = [](){
|
|
std::vector<std::string> sources = {
|
|
"../tests/scripts/001.script",
|
|
"../tests/scripts/002.script",
|
|
"../tests/scripts/003.script",
|
|
"../tests/scripts/004.script",
|
|
"../tests/scripts/005.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<std::string> 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 = 5000000;
|
|
|
|
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<scripting::script_error> 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<size_t>(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<std::chrono::microseconds>(end-begin).count() << "µs\n"
|
|
<< "Time per iteration: " << (std::chrono::duration_cast<std::chrono::nanoseconds>(end-begin)/(error_cnt+success_cnt)).count() << "ns\n";
|
|
};
|
|
|
|
TEST_CASE("Try to crash the parser (known seeds)") {
|
|
runner<1547293717>();
|
|
runner<1759257947>();
|
|
runner<2909912711>();
|
|
runner<1236548620>();
|
|
}
|
|
|
|
TEST_CASE("Try to crash the parser (new seeds)") {
|
|
runner<>();
|
|
}
|