From 210a7b82c57803a824c61943f95ea13ed507e6bf Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Mon, 23 Jan 2017 21:47:40 -0500 Subject: [PATCH] Fix up CommandParser operation and add unit tests --- src/core/CommandParser.cc | 18 +++---- src/core/CommandParser.h | 8 ++- src/core/EncodedString.h | 14 ++++++ test/src/test_CommandParser.cc | 90 ++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 test/src/test_CommandParser.cc diff --git a/src/core/CommandParser.cc b/src/core/CommandParser.cc index 095e1ef..618b9c3 100644 --- a/src/core/CommandParser.cc +++ b/src/core/CommandParser.cc @@ -18,7 +18,7 @@ bool CommandParser::parse(const EncodedString & command) auto collect_arg = [this, ¤t_arg, &arg_content] { if (arg_content) { - m_args.push_back(std::make_shared(¤t_arg[0], current_arg.size())); + m_args.push_back(EncodedString(¤t_arg[0], current_arg.size())); current_arg.clear(); arg_content = false; } @@ -30,16 +30,13 @@ bool CommandParser::parse(const EncodedString & command) if ((quote != 0u) && (cp == quote)) { quote = 0u; - ++it; - continue; } - if ((quote == 0u) && ((cp == '\'') || (cp == '"'))) + else if ((quote == 0u) && ((cp == '\'') || (cp == '"'))) { quote = cp; - ++it; - continue; + arg_content = true; } - if (cp == '\\') + else if (cp == '\\') { if (++it == end) { @@ -47,19 +44,16 @@ bool CommandParser::parse(const EncodedString & command) return false; } append_char(); - ++it; - continue; } - else if (cp == ' ') + else if ((cp == ' ') && (quote == 0u)) { collect_arg(); } else { append_char(); - ++it; - continue; } + ++it; } if (quote != 0u) diff --git a/src/core/CommandParser.h b/src/core/CommandParser.h index 8ec455d..99a99b6 100644 --- a/src/core/CommandParser.h +++ b/src/core/CommandParser.h @@ -9,7 +9,7 @@ class CommandParser { public: bool parse(const EncodedString & command); - std::shared_ptr operator[](size_t index) const + const EncodedString & operator[](size_t index) const { return m_args[index]; } @@ -21,9 +21,13 @@ public: { return m_parse_error; } + const std::vector args() const + { + return m_args; + } protected: - std::vector> m_args; + std::vector m_args; std::string m_parse_error; }; diff --git a/src/core/EncodedString.h b/src/core/EncodedString.h index 1a2da10..91b7d4d 100644 --- a/src/core/EncodedString.h +++ b/src/core/EncodedString.h @@ -46,6 +46,10 @@ public: }; EncodedString(const uint8_t * data, size_t size, Encoding::Type encoding = Encoding::UTF_8); + EncodedString(const std::string & s) + : EncodedString((const uint8_t *)&s[0], s.size()) + { + } iterator begin() const; iterator end() const; @@ -61,6 +65,16 @@ public: { return (*m_data)[index]; } + bool operator==(const EncodedString & other) const + { + return (m_encoding == other.m_encoding) && + (m_size == other.m_size) && + (memcmp(&(*m_data)[0], &(*other.m_data)[0], m_size) == 0); + } + bool operator!=(const EncodedString & other) const + { + return !(*this == other); + } bool operator==(const std::string & s) const { return (m_size == s.size()) && (memcmp(&(*m_data)[0], &s[0], m_size) == 0); diff --git a/test/src/test_CommandParser.cc b/test/src/test_CommandParser.cc new file mode 100644 index 0000000..b059a18 --- /dev/null +++ b/test/src/test_CommandParser.cc @@ -0,0 +1,90 @@ +#include "gtest/gtest.h" +#include "CommandParser.h" +#include + +typedef std::string ss; + +void dump(const std::vector & v) +{ + for (auto s : v) + { + std::cerr << " \"" << ss((const char *)&s[0], s.size()) << "\"" << std::endl; + } +} + +#define compare_parsed(expected, results) \ + compare_parsed_(expected, results, __LINE__) + +void compare_parsed_(const std::vector & expected, + const std::vector & results, + int line) +{ + if (expected != results) + { + std::cerr << "Line " << line << " unexpected parsed result:" << std::endl; + std::cerr << "Expected:" << std::endl; + dump(expected); + std::cerr << "Actual:" << std::endl; + dump(results); + } + EXPECT_EQ(expected, results); +} + +TEST(CommandParser_parse, parses_a_basic_command) +{ + EncodedString command("w /home/josh/file"); + CommandParser cp; + EXPECT_TRUE(cp.parse(command)); + compare_parsed(std::vector({ + ss("w"), + ss("/home/josh/file"), + }), cp.args()); +} + +TEST(CommandParser_parse, parses_a_command_with_quoted_arguments) +{ + EncodedString command("echo 'Hello There'\" World!\" a'b'c d\"e\"f ' ' \"\" 1 2 3"); + CommandParser cp; + EXPECT_TRUE(cp.parse(command)); + compare_parsed(std::vector({ + ss("echo"), + ss("Hello There World!"), + ss("abc"), + ss("def"), + ss(" "), + ss(""), + ss("1"), + ss("2"), + ss("3"), + }), cp.args()); +} + +TEST(CommandParser_parse, parses_a_command_with_escaped_characters) +{ + EncodedString command(" echo \\ ' \\' a ' \"b \\\" c\" \\\\too"); + CommandParser cp; + EXPECT_TRUE(cp.parse(command)); + compare_parsed(std::vector({ + ss("echo"), + ss(" "), + ss(" ' a "), + ss("b \" c"), + ss("\\too"), + }), cp.args()); +} + +TEST(CommandParser_parse, fails_to_parse_with_an_unterminated_escape_sequence) +{ + EncodedString command("echo hi\\"); + CommandParser cp; + EXPECT_FALSE(cp.parse(command)); + EXPECT_EQ("Unterminated escape sequence", cp.parse_error()); +} + +TEST(CommandParser_parse, fails_to_parse_with_an_unterminated_quoted_region) +{ + EncodedString command("echo Bob's Golf Bag"); + CommandParser cp; + EXPECT_FALSE(cp.parse(command)); + EXPECT_EQ("Unterminated quoted region", cp.parse_error()); +}