From 00863566ec4601c65c435b74e575d49546a1c707 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Sat, 7 Dec 2019 03:36:21 +0300 Subject: split server into multiple components In a vague attempt to make header files more readable, split server/ into a number of components. Also, refactor the unit tests to use the "Data-driven test cases" of Boost.Test. --- test/unit_tests/CMakeLists.txt | 6 +- test/unit_tests/lexer.cpp | 209 ++++++++++++++++++++++++++--------------- test/unit_tests/parser.cpp | 125 +++++++++++++++--------- 3 files changed, 221 insertions(+), 119 deletions(-) (limited to 'test/unit_tests') diff --git a/test/unit_tests/CMakeLists.txt b/test/unit_tests/CMakeLists.txt index d320974..ebdfbf1 100644 --- a/test/unit_tests/CMakeLists.txt +++ b/test/unit_tests/CMakeLists.txt @@ -1,6 +1,8 @@ find_package(Boost REQUIRED) add_executable(unit_tests main.cpp lexer.cpp parser.cpp) -target_include_directories(unit_tests SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) - +target_link_libraries(unit_tests PRIVATE lexer parser) target_include_directories(unit_tests PRIVATE ../..) + +target_include_directories(unit_tests SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(unit_tests PRIVATE ${Boost_LIBRARIES}) diff --git a/test/unit_tests/lexer.cpp b/test/unit_tests/lexer.cpp index 7b513e8..fdc93e1 100644 --- a/test/unit_tests/lexer.cpp +++ b/test/unit_tests/lexer.cpp @@ -1,12 +1,27 @@ -#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -BOOST_AUTO_TEST_CASE(test_lexer_parse_number) { - using namespace math::server::lexer; +BOOST_AUTO_TEST_SUITE(lexer_tests) +namespace bdata = boost::unit_test::data; +using math::server::Lexer; +using math::server::LexerError; +using math::server::lexer::Token; +using math::server::lexer::token::Type; +namespace details = math::server::lexer::details; + +BOOST_AUTO_TEST_CASE(test_parse_number) { // These are valid numbers: BOOST_TEST(details::parse_number("0").value() == 0); BOOST_TEST(details::parse_number("1.").value() == 1.); @@ -25,85 +40,129 @@ BOOST_AUTO_TEST_CASE(test_lexer_parse_number) { BOOST_TEST(!details::parse_number("e12").has_value()); // This is definitely a number, but a malformed one (an exponent must be // followed by some digits). - BOOST_CHECK_THROW(details::parse_number("12e"), Error); + BOOST_CHECK_THROW(details::parse_number("12e"), LexerError); } -BOOST_AUTO_TEST_CASE(test_lexer_parse_const_token) { - using namespace math::server::lexer; - - // TODO: No time to implement the required string conversions, hence the - // extra parentheses. - BOOST_TEST((details::parse_const_token("+").value() == Token::Type::PLUS)); +BOOST_AUTO_TEST_CASE(test_parse_const_token) { + BOOST_TEST(details::parse_const_token("+").value() == Type::PLUS); // parse_* functions only consume a single token: - BOOST_TEST((details::parse_const_token("+++").value() == Token::Type::PLUS)); - BOOST_TEST((details::parse_const_token("-").value() == Token::Type::MINUS)); + BOOST_TEST(details::parse_const_token("+/*").value() == Type::PLUS); + BOOST_TEST(details::parse_const_token("-").value() == Type::MINUS); BOOST_TEST(!details::parse_const_token("&+").has_value()); } -BOOST_AUTO_TEST_CASE(test_lexer_get_tokens) { - using namespace math::server; - using namespace math::server::lexer; +namespace { +namespace get_tokens::valid { - // TODO: No time to implement the required string conversions, hence the - // extra parentheses. - { - Lexer lexer{""}; - BOOST_TEST((lexer.get_tokens() == std::vector{})); - } - { - Lexer lexer{" + - "}; - BOOST_TEST((lexer.get_tokens() == std::vector{ - Token{Token::Type::PLUS}, - Token{Token::Type::MINUS}, - })); - } - { - Lexer lexer{"&"}; - BOOST_CHECK_THROW((lexer.get_tokens()), lexer::Error); - } - { - Lexer lexer{" 1 + 123 & 456"}; - BOOST_CHECK_THROW((lexer.get_tokens()), lexer::Error); - } - { - Lexer lexer{"1+2"}; - BOOST_TEST((lexer.get_tokens() == std::vector{ - Token{1}, - Token{Token::Type::PLUS}, - Token{2}, - })); - } - { - Lexer lexer{"1+2 * (3- 4e-2)"}; - BOOST_TEST((lexer.get_tokens() == std::vector{ - Token{1}, - Token{Token::Type::PLUS}, - Token{2}, - Token{Token::Type::ASTERISK}, - Token{Token::Type::LEFT_PAREN}, - Token{3}, - Token{Token::Type::MINUS}, - Token{4e-2}, - Token{Token::Type::RIGHT_PAREN}, - })); +const std::vector input{ + "", + " + - ", + "1+2", + "1+2 * (3- 4e-2)", + " 2 * (1 + 3 * (1 - -3)) ", +}; + +// Some black magic-fuckery to resolve operator<< for std::vector. +// See https://stackoverflow.com/a/18817428/514684. + +struct Expected { + std::vector m_tokens; +}; + +std::ostream& operator<<(std::ostream& os, const Expected& expected) { + for (const auto& token : expected.m_tokens) { + os << token; } - { - Lexer lexer{" 2 * (1 + 3 * (1 - -3)) "}; - BOOST_TEST((lexer.get_tokens() == std::vector{ - Token{2}, - Token{Token::Type::ASTERISK}, - Token{Token::Type::LEFT_PAREN}, - Token{1}, - Token{Token::Type::PLUS}, - Token{3}, - Token{Token::Type::ASTERISK}, - Token{Token::Type::LEFT_PAREN}, - Token{1}, - Token{Token::Type::MINUS}, - Token{Token::Type::MINUS}, - Token{3}, - Token{Token::Type::RIGHT_PAREN}, - Token{Token::Type::RIGHT_PAREN}, - })); + return os; +} + +const std::vector expected{ + {{}}, + {{ + Token{Type::PLUS}, + Token{Type::MINUS}, + }}, + {{ + Token{1}, + Token{Type::PLUS}, + Token{2}, + }}, + {{ + Token{1}, + Token{Type::PLUS}, + Token{2}, + Token{Type::ASTERISK}, + Token{Type::LEFT_PAREN}, + Token{3}, + Token{Type::MINUS}, + Token{4e-2}, + Token{Type::RIGHT_PAREN}, + }}, + {{ + Token{2}, + Token{Type::ASTERISK}, + Token{Type::LEFT_PAREN}, + Token{1}, + Token{Type::PLUS}, + Token{3}, + Token{Type::ASTERISK}, + Token{Type::LEFT_PAREN}, + Token{1}, + Token{Type::MINUS}, + Token{Type::MINUS}, + Token{3}, + Token{Type::RIGHT_PAREN}, + Token{Type::RIGHT_PAREN}, + }}, +}; + +} + +namespace get_tokens::invalid { + +const std::vector input{ + "&", + " 1 + 123 & 456", +}; + +const std::vector error_msg{ + "server error: lexer error: invalid input at: &", + "server error: lexer error: invalid input at: & 456", +}; + +} +} + +BOOST_DATA_TEST_CASE( + test_get_tokens_valid, + bdata::make(get_tokens::valid::input) ^ get_tokens::valid::expected, + input, + expected) { + + Lexer lexer{input}; + const auto actual = lexer.get_tokens(); + BOOST_CHECK_EQUAL_COLLECTIONS(actual.cbegin(), actual.cend(), + expected.m_tokens.cbegin(), + expected.m_tokens.cend()); +} + +BOOST_DATA_TEST_CASE( + test_get_tokens_invalid, + bdata::make(get_tokens::invalid::input) ^ get_tokens::invalid::error_msg, + input, + error_msg) { + + BOOST_REQUIRE_THROW(do { + Lexer lexer{input}; + lexer.get_tokens(); + } while (0), LexerError); + + try { + Lexer lexer{input}; + lexer.get_tokens(); + } catch (const LexerError& e) { + BOOST_TEST(error_msg == e.what()); } } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit_tests/parser.cpp b/test/unit_tests/parser.cpp index 11f48d3..bf31223 100644 --- a/test/unit_tests/parser.cpp +++ b/test/unit_tests/parser.cpp @@ -1,48 +1,89 @@ -#include +#include +#include +#include +#include #include -BOOST_AUTO_TEST_CASE(test_parser_exec) { - using namespace math::server; +#include +#include +#include - { - Parser parser{""}; - BOOST_CHECK_THROW(parser.exec(), parser::Error); - } - { - Parser parser{"1"}; - BOOST_TEST(parser.exec() == 1); - } - { - Parser parser{" 1 + "}; - BOOST_CHECK_THROW(parser.exec(), parser::Error); - } - { - Parser parser{" 1 + 2 "}; - BOOST_TEST(parser.exec() == 3); - } - { - Parser parser{" 2 * 1 + 3 "}; - BOOST_TEST(parser.exec() == 5); - } - { - Parser parser{" 2 * (1 + 3) "}; - BOOST_TEST(parser.exec() == 8); - } - { - Parser parser{" 2 * (1 + 3 "}; - BOOST_CHECK_THROW(parser.exec(), parser::Error); - } - { - Parser parser{" 2 * (1 + 3) )"}; - BOOST_CHECK_THROW(parser.exec(), parser::Error); - } - { - Parser parser{" 2 * (1 + 3 * (1 - -3)) "}; - BOOST_TEST(parser.exec() == 26); - } - { - Parser parser{" -2 * ---- (3 + -100e-1) "}; // Looks weird, but also works in e.g. Python - BOOST_TEST(parser.exec() == 14); +BOOST_AUTO_TEST_SUITE(parser_tests) + +namespace bdata = boost::unit_test::data; +using math::server::Parser; +using math::server::ParserError; + +namespace { +namespace exec::valid { + +const std::vector input{ + "1", + " 1 + 2 ", + " 2 * 1 + 3 ", + " 2 * (1 + 3) ", + " 2 * (1 + 3 * (1 - -3)) ", + " -2 * ---- (3 + -100e-1) ", // Looks weird, but also works in e.g. Python +}; + +const std::vector expected{ + 1, + 3, + 5, + 8, + 26, + 14, +}; + +} + +namespace exec::invalid { + +const std::vector input{ + "", + " 1 + ", + " 2 * (1 + 3 ", + " 2 * (1 + 3) )", +}; + +const std::vector error_msg{ + "server error: parser error: expected '-', '(' or a number", + "server error: parser error: expected '-', '(' or a number", + "server error: parser error: missing closing ')'", + "server error: parser error: expected a binary operator", +}; + +} +} + +BOOST_DATA_TEST_CASE( + test_exec_valid, + bdata::make(exec::valid::input) ^ exec::valid::expected, + input, + expected) { + + Parser parser{input}; + BOOST_TEST(parser.exec() == expected); +} + +BOOST_DATA_TEST_CASE( + test_exec_invalid, + bdata::make(exec::invalid::input) ^ exec::invalid::error_msg, + input, + error_msg) { + + BOOST_REQUIRE_THROW(do { + Parser parser{input}; + parser.exec(); + } while (0), ParserError); + + try { + Parser parser{input}; + parser.exec(); + } catch (const ParserError& e) { + BOOST_TEST(error_msg == e.what()); } } + +BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3