polybar/tests/unit_tests/tags/parser.cpp

496 lines
12 KiB
C++

#include "tags/parser.hpp"
#include "common/test.hpp"
using namespace polybar;
using namespace tags;
/**
* Helper class to test parsed data.
*
* The expect_* functions will check that the current element corresponds to
* what is expected and then move to the next element.
*
* The assert_* functions are used internally to check certain properties of the
* current element.
*/
class TestableTagParser : public parser {
public:
TestableTagParser() : TestableTagParser(""){};
TestableTagParser(const string&& input) {
setup_parser_test(std::move(input));
}
void setup_parser_test(const string& input) {
this->set(std::move(input));
}
void expect_done() {
EXPECT_FALSE(has_next_element());
}
void expect_text(const string&& exp) {
set_current();
assert_is_tag(false);
EXPECT_EQ(exp, current.data);
}
void expect_color_reset(syntaxtag t) {
set_current();
assert_format(t);
EXPECT_EQ(color_type::RESET, current.tag_data.color.type);
}
void expect_color(syntaxtag t, const string& color) {
set_current();
assert_format(t);
rgba c{color};
EXPECT_EQ(color_type::COLOR, current.tag_data.color.type);
EXPECT_EQ(c, current.tag_data.color.val);
}
void expect_action_closing(mousebtn exp = mousebtn::NONE) {
set_current();
assert_format(syntaxtag::A);
EXPECT_TRUE(current.tag_data.action.closing);
EXPECT_EQ(exp, current.tag_data.action.btn);
}
void expect_action(const string& exp, mousebtn btn) {
set_current();
assert_format(syntaxtag::A);
EXPECT_FALSE(current.tag_data.action.closing);
EXPECT_EQ(btn, current.tag_data.action.btn);
EXPECT_EQ(exp, current.data);
}
void expect_font_reset() {
expect_font(0);
}
void expect_font(unsigned exp) {
set_current();
assert_format(syntaxtag::T);
EXPECT_EQ(exp, current.tag_data.font);
}
void expect_offset_pixel(int exp) {
set_current();
assert_format(syntaxtag::O);
EXPECT_EQ(extent_type::PIXEL, current.tag_data.offset.type);
EXPECT_EQ(exp, current.tag_data.offset.value);
}
void expect_offset_points(float exp) {
set_current();
assert_format(syntaxtag::O);
EXPECT_EQ(extent_type::POINT, current.tag_data.offset.type);
EXPECT_EQ(exp, current.tag_data.offset.value);
}
void expect_ctrl(controltag exp) {
set_current();
assert_format(syntaxtag::P);
EXPECT_EQ(exp, current.tag_data.ctrl);
}
void expect_alignment(syntaxtag exp) {
set_current();
assert_format(exp);
}
void expect_activation(attr_activation act, attribute attr) {
set_current();
assert_type(tag_type::ATTR);
EXPECT_EQ(act, current.tag_data.subtype.activation);
EXPECT_EQ(attr, current.tag_data.attr);
}
void expect_reverse() {
set_current();
assert_format(syntaxtag::R);
}
private:
void assert_format(syntaxtag exp) {
assert_type(tag_type::FORMAT);
ASSERT_EQ(exp, current.tag_data.subtype.format);
}
void assert_type(tag_type exp) {
assert_is_tag(true);
ASSERT_EQ(exp, current.tag_data.type);
}
void assert_is_tag(bool exp) {
ASSERT_EQ(exp, current.is_tag);
}
void assert_has() {
if (!has_next_element()) {
throw std::runtime_error("no next element");
}
}
void set_current() {
assert_has();
current = next_element();
}
element current;
};
class TagParserTest : public ::testing::Test {
protected:
TestableTagParser p;
};
TEST_F(TagParserTest, empty) {
p.setup_parser_test("");
p.expect_done();
}
TEST_F(TagParserTest, text) {
p.setup_parser_test("text");
p.expect_text("text");
p.expect_done();
}
// Single Tag {{{
// Parse Single Color {{{
/**
* <input, tag, colorstring>
*
* If the color string is empty, this is supposed to be a color reset
*/
using single_color = std::tuple<string, syntaxtag, string>;
class ParseSingleColorTest : public TagParserTest, public ::testing::WithParamInterface<single_color> {};
vector<single_color> parse_single_color_list = {
{"%{B-}", syntaxtag::B, ""},
{"%{F-}", syntaxtag::F, ""},
{"%{o-}", syntaxtag::o, ""},
{"%{u-}", syntaxtag::u, ""},
{"%{B}", syntaxtag::B, ""},
{"%{F}", syntaxtag::F, ""},
{"%{o}", syntaxtag::o, ""},
{"%{u}", syntaxtag::u, ""},
{"%{B#f0f0f0}", syntaxtag::B, "#f0f0f0"},
{"%{F#abc}", syntaxtag::F, "#abc"},
{"%{o#abcd}", syntaxtag::o, "#abcd"},
{"%{u#FDE}", syntaxtag::u, "#FDE"},
{"%{ u#FDE}", syntaxtag::u, "#FDE"},
};
INSTANTIATE_TEST_SUITE_P(Inst, ParseSingleColorTest, ::testing::ValuesIn(parse_single_color_list));
TEST_P(ParseSingleColorTest, correctness) {
string input;
syntaxtag t;
string color;
std::tie(input, t, color) = GetParam();
p.setup_parser_test(std::move(input));
if (color.empty()) {
p.expect_color_reset(t);
} else {
p.expect_color(t, color);
}
p.expect_done();
}
// }}}
// Parse Single Action {{{
/**
* If the third element is an empty string, this is a closing tag.
*/
using single_action = std::tuple<string, mousebtn, string>;
class ParseSingleActionTest : public TagParserTest, public ::testing::WithParamInterface<single_action> {};
vector<single_action> parse_single_action_list = {
{"%{A:cmd:}", mousebtn::LEFT, "cmd"},
{"%{A1:cmd:}", mousebtn::LEFT, "cmd"},
{"%{A2:cmd:}", mousebtn::MIDDLE, "cmd"},
{"%{A3:cmd:}", mousebtn::RIGHT, "cmd"},
{"%{A4:cmd:}", mousebtn::SCROLL_UP, "cmd"},
{"%{A5:cmd:}", mousebtn::SCROLL_DOWN, "cmd"},
{"%{A6:cmd:}", mousebtn::DOUBLE_LEFT, "cmd"},
{"%{A7:cmd:}", mousebtn::DOUBLE_MIDDLE, "cmd"},
{"%{A8:cmd:}", mousebtn::DOUBLE_RIGHT, "cmd"},
{"%{A}", mousebtn::NONE, ""},
{"%{A1}", mousebtn::LEFT, ""},
{"%{A2}", mousebtn::MIDDLE, ""},
{"%{A3}", mousebtn::RIGHT, ""},
{"%{A4}", mousebtn::SCROLL_UP, ""},
{"%{A5}", mousebtn::SCROLL_DOWN, ""},
{"%{A6}", mousebtn::DOUBLE_LEFT, ""},
{"%{A7}", mousebtn::DOUBLE_MIDDLE, ""},
{"%{A8}", mousebtn::DOUBLE_RIGHT, ""},
{"%{A1:a\\:b:}", mousebtn::LEFT, "a:b"},
{"%{A1:\\:\\:\\::}", mousebtn::LEFT, ":::"},
{"%{A1:#apps.open.0:}", mousebtn::LEFT, "#apps.open.0"},
// https://github.com/polybar/polybar/issues/2040
{"%{A1:cmd | awk '{ print $NF }'):}", mousebtn::LEFT, "cmd | awk '{ print $NF }')"},
};
INSTANTIATE_TEST_SUITE_P(Inst, ParseSingleActionTest, ::testing::ValuesIn(parse_single_action_list));
TEST_P(ParseSingleActionTest, correctness) {
string input;
mousebtn btn;
string cmd;
std::tie(input, btn, cmd) = GetParam();
p.setup_parser_test(std::move(input));
if (cmd.empty()) {
p.expect_action_closing(btn);
} else {
p.expect_action(cmd, btn);
}
p.expect_done();
}
// }}}
// Parse Single Activation {{{
using single_activation = std::tuple<string, attribute, attr_activation>;
class ParseSingleActivationTest : public TagParserTest, public ::testing::WithParamInterface<single_activation> {};
vector<single_activation> parse_single_activation_list = {
{"%{+u}", attribute::UNDERLINE, attr_activation::ON},
{"%{-u}", attribute::UNDERLINE, attr_activation::OFF},
{"%{!u}", attribute::UNDERLINE, attr_activation::TOGGLE},
{"%{+o}", attribute::OVERLINE, attr_activation::ON},
{"%{-o}", attribute::OVERLINE, attr_activation::OFF},
{"%{!o}", attribute::OVERLINE, attr_activation::TOGGLE},
};
INSTANTIATE_TEST_SUITE_P(Inst, ParseSingleActivationTest, ::testing::ValuesIn(parse_single_activation_list));
TEST_P(ParseSingleActivationTest, correctness) {
string input;
attribute attr;
attr_activation act;
std::tie(input, attr, act) = GetParam();
p.setup_parser_test(std::move(input));
p.expect_activation(act, attr);
p.expect_done();
}
// }}}
TEST_F(TagParserTest, reverse) {
p.setup_parser_test("%{R}");
p.expect_reverse();
p.expect_done();
}
TEST_F(TagParserTest, font) {
p.setup_parser_test("%{T}");
p.expect_font_reset();
p.expect_done();
p.setup_parser_test("%{T-}");
p.expect_font_reset();
p.expect_done();
p.setup_parser_test("%{T-123}");
p.expect_font_reset();
p.expect_done();
p.setup_parser_test("%{T123}");
p.expect_font(123);
p.expect_done();
}
TEST_F(TagParserTest, offset) {
p.setup_parser_test("%{O}");
p.expect_offset_pixel(0);
p.expect_done();
p.setup_parser_test("%{O0}");
p.expect_offset_pixel(0);
p.expect_done();
p.setup_parser_test("%{O-112}");
p.expect_offset_pixel(-112);
p.expect_done();
p.setup_parser_test("%{O123}");
p.expect_offset_pixel(123);
p.expect_done();
p.setup_parser_test("%{O0pt}");
p.expect_offset_points(0);
p.expect_done();
p.setup_parser_test("%{O-112pt}");
p.expect_offset_points(-112);
p.expect_done();
p.setup_parser_test("%{O123pt}");
p.expect_offset_points(123);
p.expect_done();
p.setup_parser_test("%{O1.5pt}");
p.expect_offset_points(1.5);
p.expect_done();
p.setup_parser_test("%{O1.1px}");
p.expect_offset_pixel(1);
p.expect_done();
p.setup_parser_test("%{O1.1}");
p.expect_offset_pixel(1);
p.expect_done();
}
TEST_F(TagParserTest, alignment) {
p.setup_parser_test("%{l}");
p.expect_alignment(syntaxtag::l);
p.expect_done();
p.setup_parser_test("%{c}");
p.expect_alignment(syntaxtag::c);
p.expect_done();
p.setup_parser_test("%{r}");
p.expect_alignment(syntaxtag::r);
p.expect_done();
}
TEST_F(TagParserTest, ctrl) {
p.setup_parser_test("%{PR}");
p.expect_ctrl(controltag::R);
p.expect_done();
}
/**
* Tests the the legacy %{U...} tag first produces %{u...} and then %{o...}
*/
TEST_F(TagParserTest, UnderOverLine) {
p.setup_parser_test("%{U-}");
p.expect_color_reset(syntaxtag::u);
p.expect_color_reset(syntaxtag::o);
p.expect_done();
p.setup_parser_test("%{U#12ab}");
p.expect_color(syntaxtag::u, "#12ab");
p.expect_color(syntaxtag::o, "#12ab");
p.expect_done();
}
// }}}
TEST_F(TagParserTest, compoundTags) {
p.setup_parser_test("%{F- B#ff0000 A:cmd:}");
p.expect_color_reset(syntaxtag::F);
p.expect_color(syntaxtag::B, "#ff0000");
p.expect_action("cmd", mousebtn::LEFT);
p.expect_done();
}
TEST_F(TagParserTest, combinations) {
p.setup_parser_test("%{r}%{u#4bffdc +u u#4bffdc} 20% abc%{-u u- PR}");
p.expect_alignment(syntaxtag::r);
p.expect_color(syntaxtag::u, "#4bffdc");
p.expect_activation(attr_activation::ON, attribute::UNDERLINE);
p.expect_color(syntaxtag::u, "#4bffdc");
p.expect_text(" 20% abc");
p.expect_activation(attr_activation::OFF, attribute::UNDERLINE);
p.expect_color_reset(syntaxtag::u);
p.expect_ctrl(controltag::R);
p.expect_done();
}
/**
* The type of exception we expect.
*
* Since we can't directly pass typenames, we go through this enum.
*/
enum class exc { ERR, TOKEN, TAG, TAG_END, COLOR, ATTR, FONT, CTRL, OFFSET, BTN };
using exception_test = pair<string, enum exc>;
class ParseErrorTest : public TagParserTest, public ::testing::WithParamInterface<exception_test> {};
vector<exception_test> parse_error_test = {
{"%{F-", exc::TAG_END},
{"%{Q", exc::TAG},
{"%{", exc::TOKEN},
{"%{F#xyz}", exc::COLOR},
{"%{Ffoo}", exc::COLOR},
{"%{F-abc}", exc::COLOR},
{"%{+z}", exc::ATTR},
{"%{T-abc}", exc::FONT},
{"%{T12a}", exc::FONT},
{"%{Tabc}", exc::FONT},
{"%{?u", exc::TAG},
{"%{PRabc}", exc::CTRL},
{"%{P}", exc::CTRL},
{"%{PA}", exc::CTRL},
{"%{Oabc}", exc::OFFSET},
{"%{O123foo}", exc::OFFSET},
{"%{O0ptx}", exc::OFFSET},
{"%{O0a}", exc::OFFSET},
{"%{A2:cmd:cmd:}", exc::TAG_END},
{"%{A9}", exc::BTN},
{"%{rQ}", exc::TAG_END},
};
INSTANTIATE_TEST_SUITE_P(Inst, ParseErrorTest, ::testing::ValuesIn(parse_error_test));
TEST_P(ParseErrorTest, correctness) {
string input;
exc exception;
std::tie(input, exception) = GetParam();
p.setup_parser_test(input);
ASSERT_TRUE(p.has_next_element());
switch (exception) {
case exc::ERR:
ASSERT_THROW(p.next_element(), tags::error);
break;
case exc::TOKEN:
ASSERT_THROW(p.next_element(), tags::token_error);
break;
case exc::TAG:
ASSERT_THROW(p.next_element(), tags::unrecognized_tag);
break;
case exc::TAG_END:
ASSERT_THROW(p.next_element(), tags::tag_end_error);
break;
case exc::COLOR:
ASSERT_THROW(p.next_element(), tags::color_error);
break;
case exc::ATTR:
ASSERT_THROW(p.next_element(), tags::unrecognized_attr);
break;
case exc::FONT:
ASSERT_THROW(p.next_element(), tags::font_error);
break;
case exc::CTRL:
ASSERT_THROW(p.next_element(), tags::control_error);
break;
case exc::OFFSET:
ASSERT_THROW(p.next_element(), tags::offset_error);
break;
case exc::BTN:
ASSERT_THROW(p.next_element(), tags::btn_error);
break;
default:
FAIL();
}
}