From 1a694a8b26a441b12547057d6e0be61a111cced3 Mon Sep 17 00:00:00 2001 From: Spartan322 Date: Sat, 15 Jun 2024 09:40:31 -0400 Subject: Add unit tests Make github action tests run explicit Fix dropping annotation list for Errors Fix potential empty get_errors crashes Fix incorrect csv error behavior Add use_sep for `LineObject` and `std::vector` Remove constexpr of load_from_buffer and load_from_string for parsers Add snitch-org/snitch@d6632123cc8d13bdbc5cd60fd6741b9e0f635e82 Make versioned submodules ignore dirty Add tests/bin/* to gitignore --- tests/src/v2script/AbstractSyntaxTree.cpp | 172 ++++++++++++ tests/src/v2script/Parser.cpp | 437 ++++++++++++++++++++++++++++++ 2 files changed, 609 insertions(+) create mode 100644 tests/src/v2script/AbstractSyntaxTree.cpp create mode 100644 tests/src/v2script/Parser.cpp (limited to 'tests/src/v2script') diff --git a/tests/src/v2script/AbstractSyntaxTree.cpp b/tests/src/v2script/AbstractSyntaxTree.cpp new file mode 100644 index 0000000..c06da08 --- /dev/null +++ b/tests/src/v2script/AbstractSyntaxTree.cpp @@ -0,0 +1,172 @@ +#include +#include + +#include +#include +#include + +#include +#include + +#include "Helper.hpp" +#include +#include +#include +#include + +using namespace ovdl; +using namespace ovdl::v2script::ast; +using namespace std::string_view_literals; + +struct Ast : SymbolIntern { + dryad::tree tree; + dryad::node_map map; + symbol_interner_type symbol_interner; + + template + T* create(Args&&... args) { + auto node = tree.template create(DRYAD_FWD(args)...); + return node; + } + + template + T* create_with_intern(std::string_view view, Args&&... args) { + auto intern = symbol_interner.intern(view.data(), view.size()); + auto node = tree.template create(intern, DRYAD_FWD(args)...); + return node; + } + + template + T* create_with_loc(NodeLocation loc, Args&&... args) { + auto node = tree.template create(DRYAD_FWD(args)...); + set_location(node, loc); + return node; + } + + template + T* create_with_loc_and_intern(NodeLocation loc, std::string_view view, Args&&... args) { + auto intern = symbol_interner.intern(view.data(), view.size()); + auto node = tree.template create(intern, DRYAD_FWD(args)...); + set_location(node, loc); + return node; + } + + void set_location(const Node* n, NodeLocation loc) { + map.insert(n, loc); + } + + NodeLocation location_of(const Node* n) const { + auto result = map.lookup(n); + if (result == nullptr) + return {}; + return *result; + } +}; + +TEST_CASE("V2Script Nodes", "[v2script-nodes]") { + Ast ast; + + auto* id = ast.create_with_intern("id"); + CHECK_IF(id) { + CHECK(id->kind() == NodeKind::IdentifierValue); + CHECK(id->value(ast.symbol_interner) == "id"sv); + } + + auto* str = ast.create_with_intern("str"); + CHECK_IF(str) { + CHECK(str->kind() == NodeKind::StringValue); + CHECK(str->value(ast.symbol_interner) == "str"sv); + } + + auto* list = ast.create(); + CHECK_IF(list) { + CHECK(list->kind() == NodeKind::ListValue); + } + + auto* null = ast.create(); + CHECK_IF(null) { + CHECK(null->kind() == NodeKind::NullValue); + } + + auto* event = ast.create(false, list); + CHECK_IF(event) { + CHECK(event->kind() == NodeKind::EventStatement); + CHECK(!event->is_province_event()); + CHECK(event->right() == list); + } + + auto* assign = ast.create(id, str); + CHECK_IF(assign) { + CHECK(assign->kind() == NodeKind::AssignStatement); + CHECK(assign->left() == id); + CHECK(assign->right() == str); + } + + auto* value_statement = ast.create(null); + CHECK_IF(value_statement) { + CHECK(value_statement->kind() == NodeKind::ValueStatement); + CHECK(value_statement->value() == null); + } + + if (!assign || !value_statement || !event || !null || !list || !str || !id) { + return; + } + + StatementList statement_list {}; + statement_list.push_back(assign); + statement_list.push_back(value_statement); + statement_list.push_back(event); + CHECK_FALSE(statement_list.empty()); + CHECK(ranges::distance(statement_list) == 3); + + for (const auto [statement_list_index, statement] : statement_list | ranges::views::enumerate) { + CAPTURE(statement_list_index); + CHECK_OR_CONTINUE(statement); + switch (statement_list_index) { + case 0: CHECK_OR_CONTINUE(statement == assign); break; + case 1: CHECK_OR_CONTINUE(statement == value_statement); break; + case 2: CHECK_OR_CONTINUE(statement == event); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + auto* file_tree = ast.create(statement_list); + CHECK_IF(file_tree) { + CHECK(file_tree->kind() == NodeKind::FileTree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 3); + + for (const auto [statement, list_statement] : ranges::views::zip(statements, statement_list)) { + CHECK_OR_CONTINUE(list_statement); + CHECK_OR_CONTINUE(statement); + CHECK_OR_CONTINUE(statement == list_statement); + } + } + + ast.tree.set_root(file_tree); + CHECK(ast.tree.has_root()); + CHECK(ast.tree.root() == file_tree); + + ast.tree.clear(); + CHECK_FALSE(ast.tree.has_root()); + CHECK(ast.tree.root() != file_tree); +} + +TEST_CASE("V2Script Nodes Location", "[v2script-nodes-location]") { + Ast ast; + + constexpr auto fake_buffer = "id"sv; + + auto* id = ast.create_with_loc_and_intern(NodeLocation::make_from(&fake_buffer[0], &fake_buffer[1]), "id"); + + CHECK_IF(id) { + CHECK(id->value(ast.symbol_interner) == "id"sv); + + auto location = ast.location_of(id); + CHECK_FALSE(location.is_synthesized()); + CHECK(location.begin() == &fake_buffer[0]); + CHECK(location.end() - 1 == &fake_buffer[1]); + } +} \ No newline at end of file diff --git a/tests/src/v2script/Parser.cpp b/tests/src/v2script/Parser.cpp new file mode 100644 index 0000000..5ddc49d --- /dev/null +++ b/tests/src/v2script/Parser.cpp @@ -0,0 +1,437 @@ +#include +#include +#include + +#include +#include + +#include + +#include "Helper.hpp" +#include +#include +#include +#include + +using namespace ovdl; +using namespace v2script; +using namespace std::string_view_literals; + +static constexpr auto simple_buffer = "a = b"sv; +static constexpr auto simple_path = "file.txt"sv; + +static void SetupFile(std::string_view path) { + std::ofstream stream(path.data()); + stream << simple_buffer << std::flush; +} + +#define CHECK_PARSE(...) \ + CHECK_OR_RETURN(parser.get_errors().empty()); \ + CHECK_OR_RETURN(parser.simple_parse(__VA_ARGS__)); \ + CHECK_OR_RETURN(parser.get_errors().empty()) + +TEST_CASE("V2Script Memory Buffer (data, size) Simple Parse", "[v2script-memory-simple-parse][buffer][data-size]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_buffer(simple_buffer.data(), simple_buffer.size()); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script Memory Buffer (begin, end) Simple Parse", "[v2script-memory-simple-parse][buffer][begin-end]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_buffer(simple_buffer.data(), simple_buffer.data() + simple_buffer.size()); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script Memory Buffer String Simple Parse", "[v2script-memory-simple-parse][buffer][string]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_string(simple_buffer); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script File (const char*) Simple Parse", "[v2script-file-simple-parse][char-ptr]") { + SetupFile(simple_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(simple_path.data()); + + std::filesystem::remove(simple_path); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script File (filesystem::path) Simple Parse", "[v2script-file-simple-parse][filesystem-path]") { + SetupFile(simple_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(std::filesystem::path(simple_path)); + + std::filesystem::remove(simple_path); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script File (HasCstr) Simple Parse", "[v2script-file-simple-parse][has-cstr]") { + SetupFile(simple_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(std::string { simple_path }); + + std::filesystem::remove(simple_path); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script Identifier Simple Parse", "[v2script-id-simple-parse]") { + Parser parser(ovdl::detail::cnull); + + SECTION("a = b") { + static constexpr auto buffer = "a = b"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + CHECK(parser.value(right) == "b"sv); + } + } + + SECTION("a b c d") { + static constexpr auto buffer = "a b c d"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 4); + + for (const auto [statement_index, statement] : statements | ranges::views::enumerate) { + CHECK_OR_CONTINUE(statement); + + const auto* value_statement = dryad::node_try_cast(statement); + CHECK_OR_CONTINUE(value_statement); + + const auto* value = dryad::node_try_cast(value_statement->value()); + CHECK_OR_CONTINUE(value); + switch (statement_index) { + case 0: CHECK_OR_CONTINUE(parser.value(value) == "a"sv); break; + case 1: CHECK_OR_CONTINUE(parser.value(value) == "b"sv); break; + case 2: CHECK_OR_CONTINUE(parser.value(value) == "c"sv); break; + case 3: CHECK_OR_CONTINUE(parser.value(value) == "d"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION("a = { a = b }") { + static constexpr auto buffer = "a = { a = b }"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + const auto inner_statements = right->statements(); + CHECK_FALSE(inner_statements.empty()); + CHECK(ranges::distance(inner_statements) == 1); + + const ast::Statement* inner_statement = inner_statements.front(); + CHECK(inner_statement); + + const auto* inner_assign = dryad::node_try_cast(inner_statement); + CHECK(inner_assign); + CHECK(inner_assign->left()); + CHECK(inner_assign->right()); + + const auto* inner_left = dryad::node_try_cast(inner_assign->left()); + CHECK_IF(inner_left) { + CHECK(parser.value(inner_left) == "a"sv); + } + + const auto* inner_right = dryad::node_try_cast(inner_assign->right()); + CHECK_IF(inner_right) { + CHECK(parser.value(inner_right) == "b"sv); + } + } + } + + SECTION("a = { { a } }") { + static constexpr auto buffer = "a = { { a } }"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + const auto inner_statements = right->statements(); + CHECK_FALSE(inner_statements.empty()); + CHECK(ranges::distance(inner_statements) == 1); + + const ast::Statement* inner_statement = inner_statements.front(); + CHECK(inner_statement); + + const auto* value_statement = dryad::node_try_cast(inner_statement); + CHECK(value_statement); + + const auto* list_value = dryad::node_try_cast(value_statement->value()); + CHECK(list_value); + + const auto list_statements = list_value->statements(); + CHECK_FALSE(list_statements.empty()); + CHECK(ranges::distance(list_statements) == 1); + + const auto* inner_value_statement = dryad::node_try_cast(list_statements.front()); + CHECK(inner_value_statement); + + const auto* id_value = dryad::node_try_cast(inner_value_statement->value()); + CHECK(id_value); + CHECK(parser.value(id_value) == "a"sv); + } + } +} + +TEST_CASE("V2Script String Simple Parse", "[v2script-id-simple-parse]") { + Parser parser(ovdl::detail::cnull); + + SECTION("a = \"b\"") { + static constexpr auto buffer = "a = \"b\""sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + CHECK(parser.value(right) == "b"sv); + } + } + + SECTION("\"a\" \"b\" \"c\" \"d\"") { + static constexpr auto buffer = "\"a\" \"b\" \"c\" \"d\""sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 4); + + for (const auto [statement_index, statement] : statements | ranges::views::enumerate) { + CHECK_OR_CONTINUE(statement); + + const auto* value_statement = dryad::node_try_cast(statement); + CHECK_OR_CONTINUE(value_statement); + + const auto* value = dryad::node_try_cast(value_statement->value()); + CHECK_OR_CONTINUE(value); + switch (statement_index) { + case 0: CHECK_OR_CONTINUE(parser.value(value) == "a"sv); break; + case 1: CHECK_OR_CONTINUE(parser.value(value) == "b"sv); break; + case 2: CHECK_OR_CONTINUE(parser.value(value) == "c"sv); break; + case 3: CHECK_OR_CONTINUE(parser.value(value) == "d"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION("a = { a = \"b\" }") { + static constexpr auto buffer = "a = { a = \"b\" }"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = file_tree->statements().front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + const auto inner_statements = right->statements(); + CHECK_FALSE(inner_statements.empty()); + CHECK(ranges::distance(inner_statements) == 1); + + const ast::Statement* inner_statement = inner_statements.front(); + CHECK(inner_statement); + + const auto* inner_assign = dryad::node_try_cast(inner_statement); + CHECK(inner_assign); + CHECK(inner_assign->left()); + CHECK(inner_assign->right()); + + const auto* inner_left = dryad::node_try_cast(inner_assign->left()); + CHECK_IF(inner_left) { + CHECK(parser.value(inner_left) == "a"sv); + } + + const auto* inner_right = dryad::node_try_cast(inner_assign->right()); + CHECK_IF(inner_right) { + CHECK(parser.value(inner_right) == "b"sv); + } + } + } + + SECTION("a = { { \"a\" } }") { + static constexpr auto buffer = "a = { { \"a\" } }"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + const auto inner_statements = right->statements(); + CHECK_FALSE(inner_statements.empty()); + CHECK(ranges::distance(inner_statements) == 1); + + const ast::Statement* inner_statement = inner_statements.front(); + CHECK(inner_statement); + + const auto* value_statement = dryad::node_try_cast(inner_statement); + CHECK(value_statement); + + const auto* list_value = dryad::node_try_cast(value_statement->value()); + CHECK(list_value); + + const auto list_statements = list_value->statements(); + CHECK_FALSE(list_statements.empty()); + CHECK(ranges::distance(list_statements) == 1); + + const auto* inner_value_statement = dryad::node_try_cast(list_statements.front()); + CHECK_OR_RETURN(inner_value_statement); + + const auto* str_value = dryad::node_try_cast(inner_value_statement->value()); + CHECK_OR_RETURN(str_value); + CHECK(parser.value(str_value) == "a"sv); + } + } +} \ No newline at end of file -- cgit v1.2.3-56-ga3b1