Fix up CommandParser operation and add unit tests

This commit is contained in:
Josh Holtrop 2017-01-23 21:47:40 -05:00
parent 0da4108c64
commit 210a7b82c5
4 changed files with 116 additions and 14 deletions

View File

@ -18,7 +18,7 @@ bool CommandParser::parse(const EncodedString & command)
auto collect_arg = [this, &current_arg, &arg_content] { auto collect_arg = [this, &current_arg, &arg_content] {
if (arg_content) if (arg_content)
{ {
m_args.push_back(std::make_shared<EncodedString>(&current_arg[0], current_arg.size())); m_args.push_back(EncodedString(&current_arg[0], current_arg.size()));
current_arg.clear(); current_arg.clear();
arg_content = false; arg_content = false;
} }
@ -30,16 +30,13 @@ bool CommandParser::parse(const EncodedString & command)
if ((quote != 0u) && (cp == quote)) if ((quote != 0u) && (cp == quote))
{ {
quote = 0u; quote = 0u;
++it;
continue;
} }
if ((quote == 0u) && ((cp == '\'') || (cp == '"'))) else if ((quote == 0u) && ((cp == '\'') || (cp == '"')))
{ {
quote = cp; quote = cp;
++it; arg_content = true;
continue;
} }
if (cp == '\\') else if (cp == '\\')
{ {
if (++it == end) if (++it == end)
{ {
@ -47,19 +44,16 @@ bool CommandParser::parse(const EncodedString & command)
return false; return false;
} }
append_char(); append_char();
++it;
continue;
} }
else if (cp == ' ') else if ((cp == ' ') && (quote == 0u))
{ {
collect_arg(); collect_arg();
} }
else else
{ {
append_char(); append_char();
++it;
continue;
} }
++it;
} }
if (quote != 0u) if (quote != 0u)

View File

@ -9,7 +9,7 @@ class CommandParser
{ {
public: public:
bool parse(const EncodedString & command); bool parse(const EncodedString & command);
std::shared_ptr<EncodedString> operator[](size_t index) const const EncodedString & operator[](size_t index) const
{ {
return m_args[index]; return m_args[index];
} }
@ -21,9 +21,13 @@ public:
{ {
return m_parse_error; return m_parse_error;
} }
const std::vector<EncodedString> args() const
{
return m_args;
}
protected: protected:
std::vector<std::shared_ptr<EncodedString>> m_args; std::vector<EncodedString> m_args;
std::string m_parse_error; std::string m_parse_error;
}; };

View File

@ -46,6 +46,10 @@ public:
}; };
EncodedString(const uint8_t * data, size_t size, Encoding::Type encoding = Encoding::UTF_8); 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 begin() const;
iterator end() const; iterator end() const;
@ -61,6 +65,16 @@ public:
{ {
return (*m_data)[index]; 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 bool operator==(const std::string & s) const
{ {
return (m_size == s.size()) && (memcmp(&(*m_data)[0], &s[0], m_size) == 0); return (m_size == s.size()) && (memcmp(&(*m_data)[0], &s[0], m_size) == 0);

View File

@ -0,0 +1,90 @@
#include "gtest/gtest.h"
#include "CommandParser.h"
#include <iostream>
typedef std::string ss;
void dump(const std::vector<EncodedString> & 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<EncodedString> & expected,
const std::vector<EncodedString> & 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<EncodedString>({
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<EncodedString>({
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<EncodedString>({
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());
}